feat: add comprehensive diagnostic logging subsystem and UI controls
This commit is contained in:
1 parent
517436321b
commit
5f45841936
15 files changed
+495
-23
No files matched your search
@@ -0,0 +1,242 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
DEBUG LogLevel = iota
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
)
|
||||
|
||||
var levelNames = map[LogLevel]string{
|
||||
DEBUG: "DEBUG",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
mu sync.Mutex
|
||||
logFile *os.File
|
||||
logPath string
|
||||
logDir string
|
||||
}
|
||||
|
||||
var (
|
||||
defaultLogger *Logger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Init initializes the centralized logger with file & console outputs.
|
||||
func Init() (*Logger, error) {
|
||||
var initErr error
|
||||
once.Do(func() {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
configDir = "."
|
||||
}
|
||||
|
||||
logDir := filepath.Join(configDir, "g-launcher", "logs")
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
initErr = fmt.Errorf("create log dir: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
logPath := filepath.Join(logDir, "launcher.log")
|
||||
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("open log file: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
defaultLogger = &Logger{
|
||||
logFile: file,
|
||||
logPath: logPath,
|
||||
logDir: logDir,
|
||||
}
|
||||
|
||||
defaultLogger.write(INFO, "=== G-Launcher Log Session Started ===")
|
||||
defaultLogger.write(INFO, fmt.Sprintf("Log Path: %s", logPath))
|
||||
})
|
||||
|
||||
return defaultLogger, initErr
|
||||
}
|
||||
|
||||
// GetInstance returns the default logger, initializing if necessary.
|
||||
func GetInstance() *Logger {
|
||||
if defaultLogger == nil {
|
||||
_, _ = Init()
|
||||
}
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
func (l *Logger) write(level LogLevel, message string) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
|
||||
lvl := levelNames[level]
|
||||
line := fmt.Sprintf("[%s] [%-5s] %s\n", timestamp, lvl, message)
|
||||
|
||||
// Write to console
|
||||
fmt.Print(line)
|
||||
|
||||
// Write to log file
|
||||
if l.logFile != nil {
|
||||
_, _ = l.logFile.WriteString(line)
|
||||
_ = l.logFile.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Log(levelStr string, message string) {
|
||||
lvl := INFO
|
||||
switch strings.ToUpper(strings.TrimSpace(levelStr)) {
|
||||
case "DEBUG":
|
||||
lvl = DEBUG
|
||||
case "WARN", "WARNING":
|
||||
lvl = WARN
|
||||
case "ERROR":
|
||||
lvl = ERROR
|
||||
}
|
||||
l.write(lvl, message)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
||||
l.write(DEBUG, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Info(format string, args ...interface{}) {
|
||||
l.write(INFO, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(format string, args ...interface{}) {
|
||||
l.write(WARN, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Error(format string, args ...interface{}) {
|
||||
l.write(ERROR, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) GetLogPath() string {
|
||||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
return l.logPath
|
||||
}
|
||||
|
||||
func (l *Logger) GetLogDir() string {
|
||||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
return l.logDir
|
||||
}
|
||||
|
||||
func (l *Logger) ReadRecentLogs(maxLines int) (string, error) {
|
||||
if l == nil || l.logPath == "" {
|
||||
return "", fmt.Errorf("logger not initialized")
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(l.logPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
if len(lines) <= maxLines {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
recent := lines[len(lines)-maxLines:]
|
||||
return strings.Join(recent, "\n"), nil
|
||||
}
|
||||
|
||||
func (l *Logger) Close() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.logFile != nil {
|
||||
_ = l.logFile.Close()
|
||||
l.logFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Global package-level wrappers
|
||||
func Debug(format string, args ...interface{}) {
|
||||
GetInstance().Debug(format, args...)
|
||||
}
|
||||
|
||||
func Info(format string, args ...interface{}) {
|
||||
GetInstance().Info(format, args...)
|
||||
}
|
||||
|
||||
func Warn(format string, args ...interface{}) {
|
||||
GetInstance().Warn(format, args...)
|
||||
}
|
||||
|
||||
func Error(format string, args ...interface{}) {
|
||||
GetInstance().Error(format, args...)
|
||||
}
|
||||
|
||||
func GetLogPath() string {
|
||||
return GetInstance().GetLogPath()
|
||||
}
|
||||
|
||||
func GetLogDir() string {
|
||||
return GetInstance().GetLogDir()
|
||||
}
|
||||
|
||||
func ReadRecentLogs(maxLines int) (string, error) {
|
||||
return GetInstance().ReadRecentLogs(maxLines)
|
||||
}
|
||||
|
||||
// CaptureProcessOutput attaches pipes to a command and logs its output in real-time.
|
||||
func (l *Logger) CaptureProcessOutput(prefix string, stdout, stderr io.ReadCloser) {
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := stdout.Read(buf)
|
||||
if n > 0 {
|
||||
msg := strings.TrimSpace(string(buf[:n]))
|
||||
if msg != "" {
|
||||
l.Info("[%s:STDOUT] %s", prefix, msg)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := stderr.Read(buf)
|
||||
if n > 0 {
|
||||
msg := strings.TrimSpace(string(buf[:n]))
|
||||
if msg != "" {
|
||||
l.Warn("[%s:STDERR] %s", prefix, msg)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
Reference in new issue
Block a user