fix(frpLogger): replace all logs fetching method to read corresponding file in the logsPath
This commit is contained in:
@@ -51,3 +51,4 @@ super-frpc.exe
|
|||||||
database.db
|
database.db
|
||||||
logs.html
|
logs.html
|
||||||
logs.db-journal
|
logs.db-journal
|
||||||
|
CLAUDE.md
|
||||||
@@ -74,7 +74,7 @@ For detailed API documentation, please see [docs/api.md](docs/api.md)
|
|||||||
- [x] Enhance speed of listing instances
|
- [x] Enhance speed of listing instances
|
||||||
- [x] Increase speed of fetching instance logs
|
- [x] Increase speed of fetching instance logs
|
||||||
- [x] Refactor systemd service log output method to local file
|
- [x] Refactor systemd service log output method to local file
|
||||||
- [ ] Fix no log output in instanceDetail view
|
- [x] Fix no log output in instanceDetail view
|
||||||
- [ ] Add update frp binary function
|
- [ ] Add update frp binary function
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
+5
-12
@@ -5,10 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"super-frpc/global"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,18 +89,8 @@ func (s *InstanceLogStreamer) streamLogs() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
initSystem := GetInitSystem()
|
logFilePath := filepath.Join(global.CurrentConfig.LogsPath, fmt.Sprintf("superfrpc_%d.log", s.instanceID))
|
||||||
|
s.streamLogFile(logFilePath)
|
||||||
switch initSystem {
|
|
||||||
case "windows":
|
|
||||||
s.streamWindowsLogs()
|
|
||||||
case "systemd":
|
|
||||||
s.streamSystemdLogs()
|
|
||||||
case "init.d":
|
|
||||||
s.streamInitDLogs()
|
|
||||||
default:
|
|
||||||
s.sendError(fmt.Sprintf("Unsupported init system: %s", initSystem))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) sendLog(level, content string) {
|
func (s *InstanceLogStreamer) sendLog(level, content string) {
|
||||||
|
|||||||
@@ -1,411 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package frpLogger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *InstanceLogStreamer) streamLogFile(logFilePath string) {
|
||||||
|
s.streamLogFileSince(logFilePath, time.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstanceLogStreamer) streamLogFileSince(logFilePath string, sinceTime time.Time) {
|
||||||
|
s.sendInfo(fmt.Sprintf("Streaming log file: %s", logFilePath))
|
||||||
|
|
||||||
|
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
||||||
|
s.sendError(fmt.Sprintf("Log file not found: %s", logFilePath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(logFilePath)
|
||||||
|
if err != nil {
|
||||||
|
s.sendError(fmt.Sprintf("Failed to open log file: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if !sinceTime.IsZero() {
|
||||||
|
s.sendInfo(fmt.Sprintf("Filtering logs since: %s", sinceTime.Format("2006-01-02 15:04:05")))
|
||||||
|
}
|
||||||
|
s.streamLogFileFromBeginning(file, sinceTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstanceLogStreamer) streamLogFileFromBeginning(file *os.File, sinceTime time.Time) {
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.stopChan:
|
||||||
|
s.sendInfo("Log streaming stopped")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.sendError(fmt.Sprintf("Error reading log file: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
lineTime := s.parseLogLineTime(line)
|
||||||
|
if lineTime.IsZero() || lineTime.After(sinceTime) || lineTime.Equal(sinceTime) {
|
||||||
|
s.sendLog(s.parseLogLevel(line), line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstanceLogStreamer) parseLogLineTime(line string) time.Time {
|
||||||
|
layouts := []string{
|
||||||
|
"2006/01/02 15:04:05",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
time.RFC3339,
|
||||||
|
time.RFC3339Nano,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, layout := range layouts {
|
||||||
|
minLen := len(layout)
|
||||||
|
if len(line) >= minLen {
|
||||||
|
t, err := time.Parse(layout, strings.TrimSpace(line[:minLen]))
|
||||||
|
if err == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstanceLogStreamer) parseLogLevel(line string) string {
|
||||||
|
lowerLine := strings.ToLower(line)
|
||||||
|
if strings.Contains(lowerLine, "error") || strings.Contains(lowerLine, "err") {
|
||||||
|
return "ERROR"
|
||||||
|
}
|
||||||
|
if strings.Contains(lowerLine, "warn") || strings.Contains(lowerLine, "warning") {
|
||||||
|
return "WARN"
|
||||||
|
}
|
||||||
|
if strings.Contains(lowerLine, "debug") {
|
||||||
|
return "DEBUG"
|
||||||
|
}
|
||||||
|
if strings.Contains(lowerLine, "info") {
|
||||||
|
return "INFO"
|
||||||
|
}
|
||||||
|
return "INFO"
|
||||||
|
}
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
package frpLogger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamSystemdLogs() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Starting to stream logs for systemd 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 := getSystemdServiceStartTime(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streamJournalLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSystemdServiceStartTime(serviceName string) time.Time {
|
|
||||||
cmd := exec.Command("systemctl", "show", serviceName, "-p", "ExecMainStartTimestamp", "--value")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
timestampStr := strings.TrimSpace(string(output))
|
|
||||||
if timestampStr == "" || timestampStr == "n/a" {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts := []string{
|
|
||||||
"Mon 2006-01-02 15:04:05 MST",
|
|
||||||
"Mon 2006-01-02 15:04:05 -0700",
|
|
||||||
"2006-01-02 15:04:05 MST",
|
|
||||||
time.RFC3339,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, layout := range layouts {
|
|
||||||
t, err := time.Parse(layout, timestampStr)
|
|
||||||
if err == nil {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamJournalLogs() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Streaming journal logs for service: %s", s.serviceName))
|
|
||||||
|
|
||||||
startTime := getSystemdServiceStartTime(s.serviceName)
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if !startTime.IsZero() {
|
|
||||||
sinceStr := startTime.Format("2006-01-02 15:04:05")
|
|
||||||
s.sendInfo(fmt.Sprintf("Service started at: %s, fetching logs since then", sinceStr))
|
|
||||||
cmd = exec.Command("journalctl", "-u", s.serviceName, "-f", "--since", sinceStr, "--no-pager")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("journalctl", "-u", s.serviceName, "-f", "-n", "100", "--no-pager")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 journalctl: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-s.stopChan
|
|
||||||
cmd.Process.Kill()
|
|
||||||
cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(stdout)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.stopChan:
|
|
||||||
s.sendInfo("Journal log streaming stopped")
|
|
||||||
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
|
|
||||||
}
|
|
||||||
s.sendError(fmt.Sprintf("Error reading journal: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line != "" {
|
|
||||||
s.sendLog(s.parseSystemdLogLevel(line), line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) parseSystemdLogLevel(line string) string {
|
|
||||||
if strings.Contains(line, " error") || strings.Contains(line, " ERROR") || strings.Contains(line, " err]") {
|
|
||||||
return "ERROR"
|
|
||||||
}
|
|
||||||
if strings.Contains(line, " warning") || strings.Contains(line, " WARNING") || strings.Contains(line, " warn]") {
|
|
||||||
return "WARN"
|
|
||||||
}
|
|
||||||
if strings.Contains(line, " debug") || strings.Contains(line, " DEBUG") || strings.Contains(line, " debug]") {
|
|
||||||
return "DEBUG"
|
|
||||||
}
|
|
||||||
if strings.Contains(line, " info") || strings.Contains(line, " INFO") || strings.Contains(line, " info]") {
|
|
||||||
return "INFO"
|
|
||||||
}
|
|
||||||
return "INFO"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSystemdServiceLogs(serviceName string, lines int) ([]string, error) {
|
|
||||||
startTime := getSystemdServiceStartTime(serviceName)
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if !startTime.IsZero() {
|
|
||||||
sinceStr := startTime.Format("2006-01-02 15:04:05")
|
|
||||||
cmd = exec.Command("journalctl", "-u", serviceName, "--since", sinceStr, "-n", fmt.Sprintf("%d", lines), "--no-pager", "-o", "cat")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("journalctl", "-u", serviceName, "-n", fmt.Sprintf("%d", lines), "--no-pager", "-o", "cat")
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get systemd logs: %v, output: %s", err, string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
var logs []string
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(string(output)))
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if line != "" {
|
|
||||||
logs = append(logs, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamSystemdLogsAlternative() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Starting alternative systemd log streaming for service: %s", s.serviceName))
|
|
||||||
|
|
||||||
startTime := getSystemdServiceStartTime(s.serviceName)
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if !startTime.IsZero() {
|
|
||||||
sinceStr := startTime.Format("2006-01-02 15:04:05")
|
|
||||||
cmd = exec.Command("journalctl", "-u", s.serviceName, "--since", sinceStr, "-n", "50", "--no-pager", "-o", "json")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("journalctl", "-u", s.serviceName, "-n", "50", "--no-pager", "-o", "json")
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
s.sendError(fmt.Sprintf("Failed to get initial logs: %v", err))
|
|
||||||
} else {
|
|
||||||
s.parseAndSendJournalJSON(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streamJournalLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) parseAndSendJournalJSON(jsonOutput string) {
|
|
||||||
lines := strings.Split(jsonOutput, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line == "" || line == "[" || line == "]" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSuffix(line, ",")
|
|
||||||
if strings.HasPrefix(line, "{") && strings.HasSuffix(line, "}") {
|
|
||||||
var entry map[string]interface{}
|
|
||||||
if err := json.Unmarshal([]byte(line), &entry); err == nil {
|
|
||||||
if msg, ok := entry["MESSAGE"].(string); ok {
|
|
||||||
level := "INFO"
|
|
||||||
if priority, ok := entry["PRIORITY"].(string); ok {
|
|
||||||
switch priority {
|
|
||||||
case "3":
|
|
||||||
level = "ERROR"
|
|
||||||
case "4":
|
|
||||||
level = "WARN"
|
|
||||||
case "5":
|
|
||||||
level = "INFO"
|
|
||||||
case "6":
|
|
||||||
level = "INFO"
|
|
||||||
case "7":
|
|
||||||
level = "DEBUG"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.sendLog(level, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isServiceRunningSystemd(serviceName string) bool {
|
|
||||||
cmd := exec.Command("systemctl", "is-active", serviceName)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(output)) == "active"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSystemdServiceStatus(serviceName string) string {
|
|
||||||
cmd := exec.Command("systemctl", "is-active", serviceName)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamSystemdStatus() {
|
|
||||||
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 := getSystemdServiceStatus(s.serviceName)
|
|
||||||
if status != lastStatus {
|
|
||||||
s.sendInfo(fmt.Sprintf("Service status: %s", status))
|
|
||||||
lastStatus = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) checkAndStreamLogFile() bool {
|
|
||||||
instance, err := DBQueryFrpcInstanceByID(s.instanceID)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
logFilePath := s.getLogFilePathFromConfig(instance.ConfigPath)
|
|
||||||
if logFilePath == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.streamLogFile(logFilePath)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Printf("[frpLogger] Systemd log streamer initialized")
|
|
||||||
}
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
package frpLogger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamWindowsLogs() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Starting to stream logs for Windows 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 := getWindowsServiceStartTime(s.serviceName)
|
|
||||||
|
|
||||||
logFilePath := s.getLogFilePathFromConfig(instance.ConfigPath)
|
|
||||||
|
|
||||||
if logFilePath != "" {
|
|
||||||
if !startTime.IsZero() {
|
|
||||||
s.streamLogFileSince(logFilePath, startTime)
|
|
||||||
} else {
|
|
||||||
s.streamLogFile(logFilePath)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streamWindowsEventLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWindowsServiceStartTime(serviceName string) time.Time {
|
|
||||||
cmd := exec.Command("wmic", "service", "where", fmt.Sprintf("name='%s'", serviceName), "get", "ProcessId", "/value")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStr := string(output)
|
|
||||||
var pid int
|
|
||||||
lines := strings.Split(outputStr, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "ProcessId=") {
|
|
||||||
pidStr := strings.TrimPrefix(line, "ProcessId=")
|
|
||||||
pid, _ = fmt.Sscanf(pidStr, "%d", &pid)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pid <= 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = exec.Command("wmic", "process", "where", fmt.Sprintf("ProcessId=%d", pid), "get", "CreationDate", "/value")
|
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStr = string(output)
|
|
||||||
lines = strings.Split(outputStr, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "CreationDate=") {
|
|
||||||
dateStr := strings.TrimPrefix(line, "CreationDate=")
|
|
||||||
t, err := time.Parse("20060102150405.999999-0700", dateStr)
|
|
||||||
if err == nil {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) getLogFilePathFromConfig(configPath string) string {
|
|
||||||
file, err := os.Open(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if strings.HasPrefix(line, "log_file") || strings.HasPrefix(line, "logFile") || strings.HasPrefix(line, "log.to") {
|
|
||||||
parts := strings.SplitN(line, "=", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
logPath := strings.TrimSpace(parts[1])
|
|
||||||
logPath = strings.Trim(logPath, "\"'")
|
|
||||||
if logPath != "" && logPath != "console" {
|
|
||||||
return logPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamLogFile(logFilePath string) {
|
|
||||||
s.streamLogFileSince(logFilePath, time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamLogFileSince(logFilePath string, sinceTime time.Time) {
|
|
||||||
s.sendInfo(fmt.Sprintf("Streaming log file: %s", logFilePath))
|
|
||||||
|
|
||||||
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
|
||||||
s.sendError(fmt.Sprintf("Log file not found: %s", logFilePath))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(logFilePath)
|
|
||||||
if err != nil {
|
|
||||||
s.sendError(fmt.Sprintf("Failed to open log file: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if !sinceTime.IsZero() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Filtering logs since: %s", sinceTime.Format("2006-01-02 15:04:05")))
|
|
||||||
s.streamLogFileFromBeginning(file, sinceTime)
|
|
||||||
} else {
|
|
||||||
file.Seek(0, io.SeekEnd)
|
|
||||||
s.streamLogFileFromEnd(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamLogFileFromBeginning(file *os.File, sinceTime time.Time) {
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.stopChan:
|
|
||||||
s.sendInfo("Log streaming stopped")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.sendError(fmt.Sprintf("Error reading log file: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line != "" {
|
|
||||||
lineTime := s.parseLogLineTime(line)
|
|
||||||
if lineTime.IsZero() || lineTime.After(sinceTime) || lineTime.Equal(sinceTime) {
|
|
||||||
s.sendLog(s.parseLogLevel(line), line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamLogFileFromEnd(file *os.File) {
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.stopChan:
|
|
||||||
s.sendInfo("Log streaming stopped")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.sendError(fmt.Sprintf("Error reading log file: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line != "" {
|
|
||||||
s.sendLog(s.parseLogLevel(line), line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) parseLogLineTime(line string) time.Time {
|
|
||||||
layouts := []string{
|
|
||||||
"2006/01/02 15:04:05",
|
|
||||||
"2006-01-02 15:04:05",
|
|
||||||
"2006-01-02T15:04:05",
|
|
||||||
time.RFC3339,
|
|
||||||
time.RFC3339Nano,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, layout := range layouts {
|
|
||||||
minLen := len(layout)
|
|
||||||
if len(line) >= minLen {
|
|
||||||
t, err := time.Parse(layout, strings.TrimSpace(line[:minLen]))
|
|
||||||
if err == nil {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) parseLogLevel(line string) string {
|
|
||||||
lowerLine := strings.ToLower(line)
|
|
||||||
if strings.Contains(lowerLine, "error") || strings.Contains(lowerLine, "err") {
|
|
||||||
return "ERROR"
|
|
||||||
}
|
|
||||||
if strings.Contains(lowerLine, "warn") || strings.Contains(lowerLine, "warning") {
|
|
||||||
return "WARN"
|
|
||||||
}
|
|
||||||
if strings.Contains(lowerLine, "debug") {
|
|
||||||
return "DEBUG"
|
|
||||||
}
|
|
||||||
if strings.Contains(lowerLine, "info") {
|
|
||||||
return "INFO"
|
|
||||||
}
|
|
||||||
return "INFO"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamWindowsEventLogs() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Streaming Windows Event Logs for service: %s", s.serviceName))
|
|
||||||
|
|
||||||
s.sendInfo("Attempting to read from Windows Event Log...")
|
|
||||||
|
|
||||||
cmd := exec.Command("wevtutil", "qe", "Application", "/q:*[System[Provider[@Name='"+s.serviceName+"']]]", "/c:50", "/rd:true", "/f:text")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
s.sendInfo("No events found in Application log, trying System log...")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(output) > 0 {
|
|
||||||
s.parseAndSendEventLogOutput(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = exec.Command("wevtutil", "qe", "System", "/q:*[System[Provider[@Name='"+s.serviceName+"']]]", "/c:50", "/rd:true", "/f:text")
|
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
s.sendInfo("No events found in System log either")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(output) > 0 {
|
|
||||||
s.parseAndSendEventLogOutput(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sendInfo("Streaming real-time service status...")
|
|
||||||
|
|
||||||
s.streamServiceStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) parseAndSendEventLogOutput(output string) {
|
|
||||||
events := strings.Split(output, "Event[")
|
|
||||||
for _, event := range events {
|
|
||||||
if strings.TrimSpace(event) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var level, msg string
|
|
||||||
lines := strings.Split(event, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "Level:") {
|
|
||||||
level = strings.TrimSpace(strings.TrimPrefix(line, "Level:"))
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "Message:") {
|
|
||||||
msg = strings.TrimSpace(strings.TrimPrefix(line, "Message:"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
if level == "" {
|
|
||||||
level = "INFO"
|
|
||||||
}
|
|
||||||
s.sendLog(level, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamServiceStatus() {
|
|
||||||
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.getWindowsServiceStatus()
|
|
||||||
if status != lastStatus {
|
|
||||||
s.sendInfo(fmt.Sprintf("Service status: %s", status))
|
|
||||||
lastStatus = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) getWindowsServiceStatus() string {
|
|
||||||
cmd := exec.Command("sc", "query", s.serviceName)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("Error querying service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStr := string(output)
|
|
||||||
if strings.Contains(outputStr, "RUNNING") {
|
|
||||||
return "RUNNING"
|
|
||||||
} else if strings.Contains(outputStr, "STOPPED") {
|
|
||||||
return "STOPPED"
|
|
||||||
} else if strings.Contains(outputStr, "PAUSED") {
|
|
||||||
return "PAUSED"
|
|
||||||
} else if strings.Contains(outputStr, "START_PENDING") {
|
|
||||||
return "START_PENDING"
|
|
||||||
} else if strings.Contains(outputStr, "STOP_PENDING") {
|
|
||||||
return "STOP_PENDING"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstanceLogStreamer) streamWindowsLogsAlternative() {
|
|
||||||
s.sendInfo(fmt.Sprintf("Starting alternative log streaming for 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 := getWindowsServiceStartTime(s.serviceName)
|
|
||||||
|
|
||||||
configDir := filepath.Dir(instance.ConfigPath)
|
|
||||||
possibleLogPaths := []string{
|
|
||||||
filepath.Join(configDir, s.serviceName+".log"),
|
|
||||||
filepath.Join(configDir, "frpc.log"),
|
|
||||||
filepath.Join("C:\\ProgramData\\superfrpc", s.serviceName+".log"),
|
|
||||||
filepath.Join(os.TempDir(), s.serviceName+".log"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, logPath := range possibleLogPaths {
|
|
||||||
if _, err := os.Stat(logPath); err == nil {
|
|
||||||
if !startTime.IsZero() {
|
|
||||||
s.streamLogFileSince(logPath, startTime)
|
|
||||||
} else {
|
|
||||||
s.streamLogFile(logPath)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sendInfo("No log file found, streaming service status instead...")
|
|
||||||
s.streamServiceStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWindowsServiceLogs(serviceName string, lines int) ([]string, error) {
|
|
||||||
cmd := exec.Command("wevtutil", "qe", "Application",
|
|
||||||
fmt.Sprintf("/q:*[System[Provider[@Name='%s']]]", serviceName),
|
|
||||||
fmt.Sprintf("/c:%d", lines),
|
|
||||||
"/rd:true",
|
|
||||||
"/f:text")
|
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query event log: %v, stderr: %s", err, stderr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var logs []string
|
|
||||||
output := stdout.String()
|
|
||||||
events := strings.Split(output, "Event[")
|
|
||||||
|
|
||||||
for _, event := range events {
|
|
||||||
if strings.TrimSpace(event) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg string
|
|
||||||
lines := strings.Split(event, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "Message:") {
|
|
||||||
msg = strings.TrimSpace(strings.TrimPrefix(line, "Message:"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
logs = append(logs, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
+2
-2
@@ -94,7 +94,7 @@ func syncRunningInstancesToWatchdog() {
|
|||||||
|
|
||||||
func ensureWatchdogProcess() error {
|
func ensureWatchdogProcess() error {
|
||||||
watchdogName, err := getWatchdogBinaryName()
|
watchdogName, err := getWatchdogBinaryName()
|
||||||
exec.Command("chmod", "+x", filepath.Join(global.CurrentPath, "watchdog", watchdogName)).Run()
|
exec.Command("chmod", "+x", filepath.Join(global.CurrentPath, watchdogName)).Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ func ensureWatchdogProcess() error {
|
|||||||
}
|
}
|
||||||
postLog.Info(fmt.Sprintf("Watchdog process %s is not running, starting it...", watchdogName))
|
postLog.Info(fmt.Sprintf("Watchdog process %s is not running, starting it...", watchdogName))
|
||||||
|
|
||||||
watchdogPath := filepath.Join(global.CurrentPath, "watchdog", watchdogName)
|
watchdogPath := filepath.Join(global.CurrentPath, watchdogName)
|
||||||
if !utils.IsFileExist(watchdogPath) {
|
if !utils.IsFileExist(watchdogPath) {
|
||||||
return fmt.Errorf("watchdog binary not found: %s", watchdogPath)
|
return fmt.Errorf("watchdog binary not found: %s", watchdogPath)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user