412 lines
9.1 KiB
Go
412 lines
9.1 KiB
Go
package frpLogger
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (s *InstanceLogStreamer) streamInitDLogs() {
|
|
s.sendInfo(fmt.Sprintf("Starting to stream logs for init.d service: %s", s.serviceName))
|
|
|
|
instance, err := DBQueryFrpcInstanceByID(s.instanceID)
|
|
if err != nil {
|
|
s.sendError(fmt.Sprintf("Failed to get instance info: %v", err))
|
|
return
|
|
}
|
|
|
|
startTime := getInitDServiceStartTime(s.serviceName)
|
|
|
|
logFilePath := s.getLogFilePathFromConfig(instance.ConfigPath)
|
|
|
|
if logFilePath != "" {
|
|
if _, err := os.Stat(logFilePath); err == nil {
|
|
if !startTime.IsZero() {
|
|
s.streamLogFileSince(logFilePath, startTime)
|
|
} else {
|
|
s.streamLogFile(logFilePath)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
possibleLogPaths := s.getPossibleInitDLogPaths()
|
|
for _, logPath := range possibleLogPaths {
|
|
if _, err := os.Stat(logPath); err == nil {
|
|
s.sendInfo(fmt.Sprintf("Found log file: %s", logPath))
|
|
if !startTime.IsZero() {
|
|
s.streamLogFileSince(logPath, startTime)
|
|
} else {
|
|
s.streamLogFile(logPath)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
s.sendInfo("No log file found, streaming service status instead...")
|
|
s.streamInitDStatus()
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) getPossibleInitDLogPaths() []string {
|
|
var paths []string
|
|
|
|
paths = append(paths, fmt.Sprintf("/var/log/%s.log", s.serviceName))
|
|
paths = append(paths, fmt.Sprintf("/var/log/superfrpc/%s.log", s.serviceName))
|
|
paths = append(paths, "/var/log/frpc.log")
|
|
paths = append(paths, "/var/log/superfrpc/frpc.log")
|
|
|
|
instance, err := DBQueryFrpcInstanceByID(s.instanceID)
|
|
if err == nil {
|
|
configDir := filepath.Dir(instance.ConfigPath)
|
|
paths = append(paths, filepath.Join(configDir, s.serviceName+".log"))
|
|
paths = append(paths, filepath.Join(configDir, "frpc.log"))
|
|
}
|
|
|
|
pidFile := fmt.Sprintf("/var/run/%s.pid", s.serviceName)
|
|
if pid, err := os.ReadFile(pidFile); err == nil {
|
|
pidStr := strings.TrimSpace(string(pid))
|
|
paths = append(paths, fmt.Sprintf("/proc/%s/fd/1", pidStr))
|
|
paths = append(paths, fmt.Sprintf("/proc/%s/fd/2", pidStr))
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) streamInitDStatus() {
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
lastStatus := ""
|
|
|
|
for {
|
|
select {
|
|
case <-s.stopChan:
|
|
s.sendInfo("Service status monitoring stopped")
|
|
return
|
|
case <-ticker.C:
|
|
status := s.getInitDServiceStatus()
|
|
if status != lastStatus {
|
|
s.sendInfo(fmt.Sprintf("Service status: %s", status))
|
|
lastStatus = status
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) getInitDServiceStatus() string {
|
|
servicePath := fmt.Sprintf("/etc/init.d/%s", s.serviceName)
|
|
|
|
if _, err := os.Stat(servicePath); os.IsNotExist(err) {
|
|
return "NOT_INSTALLED"
|
|
}
|
|
|
|
cmd := exec.Command(servicePath, "status")
|
|
output, err := cmd.CombinedOutput()
|
|
outputStr := strings.TrimSpace(string(output))
|
|
|
|
if err != nil {
|
|
if strings.Contains(outputStr, "is running") {
|
|
return "RUNNING"
|
|
}
|
|
return "STOPPED"
|
|
}
|
|
|
|
if strings.Contains(outputStr, "is running") {
|
|
return "RUNNING"
|
|
}
|
|
|
|
return "STOPPED"
|
|
}
|
|
|
|
func getInitDServiceLogs(serviceName string, lines int) ([]string, error) {
|
|
startTime := getInitDServiceStartTime(serviceName)
|
|
|
|
possibleLogPaths := []string{
|
|
fmt.Sprintf("/var/log/%s.log", serviceName),
|
|
fmt.Sprintf("/var/log/superfrpc/%s.log", serviceName),
|
|
"/var/log/frpc.log",
|
|
}
|
|
|
|
for _, logPath := range possibleLogPaths {
|
|
if _, err := os.Stat(logPath); err == nil {
|
|
if !startTime.IsZero() {
|
|
return readLinesSinceTime(logPath, lines, startTime)
|
|
}
|
|
return readLastLines(logPath, lines)
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("no log file found for service %s", serviceName)
|
|
}
|
|
|
|
func readLinesSinceTime(filePath string, maxLines int, sinceTime time.Time) ([]string, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var result []string
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
allLines := make([]string, 0)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
allLines = append(allLines, line)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("error reading log file: %w", err)
|
|
}
|
|
|
|
for i := len(allLines) - 1; i >= 0 && len(result) < maxLines; i-- {
|
|
line := allLines[i]
|
|
lineTime := parseLogLineTime(line)
|
|
if lineTime.IsZero() || lineTime.After(sinceTime) || lineTime.Equal(sinceTime) {
|
|
result = append([]string{line}, result...)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func parseLogLineTime(line string) time.Time {
|
|
layouts := []string{
|
|
"2006/01/02 15:04:05",
|
|
"2006-01-02 15:04:05",
|
|
"Jan 2 15:04:05",
|
|
"Jan 02 15:04:05",
|
|
"02/Jan/2006:15:04:05",
|
|
time.RFC3339,
|
|
}
|
|
|
|
for _, layout := range layouts {
|
|
if len(line) >= len(layout) {
|
|
t, err := time.Parse(layout, strings.TrimSpace(line[:min(len(line), len(layout))]))
|
|
if err == nil {
|
|
return t
|
|
}
|
|
}
|
|
}
|
|
|
|
return time.Time{}
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func readLastLines(filePath string, lines int) ([]string, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var result []string
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
allLines := make([]string, 0)
|
|
for scanner.Scan() {
|
|
allLines = append(allLines, scanner.Text())
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("error reading log file: %w", err)
|
|
}
|
|
|
|
start := 0
|
|
if len(allLines) > lines {
|
|
start = len(allLines) - lines
|
|
}
|
|
|
|
result = allLines[start:]
|
|
return result, nil
|
|
}
|
|
|
|
func isServiceRunningInitD(serviceName string) bool {
|
|
servicePath := fmt.Sprintf("/etc/init.d/%s", serviceName)
|
|
|
|
if _, err := os.Stat(servicePath); os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
|
|
cmd := exec.Command(servicePath, "status")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return strings.Contains(string(output), "is running")
|
|
}
|
|
|
|
func getInitDServicePid(serviceName string) int {
|
|
pidFile := fmt.Sprintf("/var/run/%s.pid", serviceName)
|
|
if content, err := os.ReadFile(pidFile); err == nil {
|
|
pidStr := strings.TrimSpace(string(content))
|
|
var pid int
|
|
if _, err := fmt.Sscanf(pidStr, "%d", &pid); err == nil {
|
|
return pid
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func getProcessStartTime(pid int) time.Time {
|
|
if pid <= 0 {
|
|
return time.Time{}
|
|
}
|
|
|
|
statPath := fmt.Sprintf("/proc/%d/stat", pid)
|
|
content, err := os.ReadFile(statPath)
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
|
|
fields := strings.Fields(string(content))
|
|
if len(fields) < 22 {
|
|
return time.Time{}
|
|
}
|
|
|
|
starttimeTicks, err := fmt.Sscanf(fields[21], "%d", new(uint64))
|
|
if err != nil || starttimeTicks != 1 {
|
|
return time.Time{}
|
|
}
|
|
|
|
var ticks uint64
|
|
fmt.Sscanf(fields[21], "%d", &ticks)
|
|
|
|
clkTck := uint64(100)
|
|
uptimeBytes, err := os.ReadFile("/proc/uptime")
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
uptimeParts := strings.Fields(string(uptimeBytes))
|
|
if len(uptimeParts) < 1 {
|
|
return time.Time{}
|
|
}
|
|
var uptimeSeconds float64
|
|
fmt.Sscanf(uptimeParts[0], "%f", &uptimeSeconds)
|
|
|
|
secondsSinceBoot := float64(ticks) / float64(clkTck)
|
|
processStartTime := time.Now().Add(-time.Duration(uptimeSeconds-secondsSinceBoot) * time.Second)
|
|
|
|
return processStartTime
|
|
}
|
|
|
|
func getInitDServiceStartTime(serviceName string) time.Time {
|
|
pid := getInitDServicePid(serviceName)
|
|
if pid <= 0 {
|
|
return time.Time{}
|
|
}
|
|
return getProcessStartTime(pid)
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) streamInitDLogsFromProc() {
|
|
pid := getInitDServicePid(s.serviceName)
|
|
if pid == 0 {
|
|
s.sendError("Failed to get service PID")
|
|
return
|
|
}
|
|
|
|
stdoutPath := fmt.Sprintf("/proc/%d/fd/1", pid)
|
|
stderrPath := fmt.Sprintf("/proc/%d/fd/2", pid)
|
|
|
|
s.sendInfo(fmt.Sprintf("Attempting to stream from /proc/%d", pid))
|
|
|
|
go s.streamProcFile(stdoutPath)
|
|
go s.streamProcFile(stderrPath)
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) streamProcFile(path string) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
s.sendError(fmt.Sprintf("Failed to open %s: %v", path, err))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
reader := bufio.NewReader(file)
|
|
|
|
for {
|
|
select {
|
|
case <-s.stopChan:
|
|
return
|
|
default:
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
time.Sleep(100 * time.Millisecond)
|
|
continue
|
|
}
|
|
return
|
|
}
|
|
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
s.sendLog(s.parseLogLevel(line), line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *InstanceLogStreamer) streamInitDLogsViaSyslog() {
|
|
s.sendInfo("Attempting to stream logs via syslog...")
|
|
|
|
cmd := exec.Command("tail", "-f", "/var/log/syslog")
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
s.sendError(fmt.Sprintf("Failed to create stdout pipe: %v", err))
|
|
return
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
s.sendError(fmt.Sprintf("Failed to start tail: %v", err))
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
<-s.stopChan
|
|
cmd.Process.Kill()
|
|
cmd.Wait()
|
|
}()
|
|
|
|
reader := bufio.NewReader(stdout)
|
|
|
|
for {
|
|
select {
|
|
case <-s.stopChan:
|
|
cmd.Process.Kill()
|
|
cmd.Wait()
|
|
return
|
|
default:
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
time.Sleep(100 * time.Millisecond)
|
|
continue
|
|
}
|
|
return
|
|
}
|
|
|
|
line = strings.TrimSpace(line)
|
|
if line != "" && strings.Contains(line, s.serviceName) {
|
|
s.sendLog(s.parseLogLevel(line), line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
log.Printf("[frpLogger] Init.d log streamer initialized")
|
|
}
|