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 }