Files
l-ui/internal/logger/logger.go
T
Muse 6a5ee8f77b rename: 3x-ui/x-ui -> L-UI/l-ui/lui-agent (paths, services, env, assets)
Display L-UI; panel binary/process l-ui, agent lui-agent.
Dirs /usr/local/l-ui, /etc/l-ui, /var/log/l-ui; DB /etc/l-ui/l-ui.db
(name file drives it via GetName), probe.db beside it, cert
/etc/l-ui/cert, bin /usr/local/l-ui/bin. Services l-ui.service /
lui-agent.service, OpenRC lui-agent, l-ui.sh, cmd/lui-agent.
Env prefix XUI_ -> LUI_ (code reads new prefix only). Release
assets l-ui-linux-* / lui-agent-linux-*; Gitea repo rogue/l-ui is
a placeholder pending user confirmation (see selfupdate.go note).
Go module path github.com/mhsanaei/3x-ui/v3 intentionally kept.
Versions unchanged: panel V2.6.0 / agent A2.5.0.
2026-10-24 12:00:55 +00:00

214 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package logger provides logging functionality for the L-UI panel with
// dual-backend logging (console/syslog and file). The in-memory log buffer
// that fed the web UI's log viewer was removed together with that feature;
// logs live on disk (rotating file under the log folder) and in syslog.
package logger
import (
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/config"
"gopkg.in/natefinch/lumberjack.v2"
)
const (
logFileName = "3xui.log" // Log file name
timeFormat = "2006/01/02 15:04:05" // Log timestamp format
// On-disk rotation limits — single file capped, old segments pruned automatically.
maxLogFileMB = 10 // rotate active log when larger than this
maxLogBackups = 5 // rotated files retained (beyond current segment)
maxLogAgeDays = 7 // remove rotated backups older than this (0 disables time-based pruning)
compressRotated = true
)
var (
// Initialized to a usable default so logging never nil-derefs before InitLogger
// runs — the "migrate" and "setting" CLI subcommands log without calling it.
logger = logging.MustGetLogger("l-ui")
fileRotate *lumberjack.Logger // nil when file backend disabled
// logHook, when set, additionally receives every log entry. Production
// never sets it; it exists so tests can assert on emitted logs now that
// the web UI's in-memory buffer is gone.
logHookMu sync.RWMutex
logHook func(level, msg string)
)
// SetLogHook installs a sink receiving every log entry (level name and
// formatted message) in addition to the normal backends, and returns the
// previously installed hook (nil in production). Passing nil clears it.
func SetLogHook(h func(level, msg string)) func(level, msg string) {
logHookMu.Lock()
defer logHookMu.Unlock()
prev := logHook
logHook = h
return prev
}
func emitLogHook(level, msg string) {
logHookMu.RLock()
h := logHook
logHookMu.RUnlock()
if h != nil {
h(level, msg)
}
}
// InitLogger initializes dual logging backends: console/syslog and file.
// Console logging uses the specified level, file logging always uses DEBUG level.
func InitLogger(level logging.Level) {
newLogger := logging.MustGetLogger("l-ui")
backends := make([]logging.Backend, 0, 2)
// Console/syslog backend with configurable level
consoleBackend := initDefaultBackend()
leveledBackend := logging.AddModuleLevel(consoleBackend)
leveledBackend.SetLevel(level, "l-ui")
backends = append(backends, leveledBackend)
// File backend with DEBUG level for comprehensive logging
if fileBackend := initFileBackend(); fileBackend != nil {
leveledBackend := logging.AddModuleLevel(fileBackend)
leveledBackend.SetLevel(logging.DEBUG, "l-ui")
backends = append(backends, leveledBackend)
}
multiBackend := logging.MultiLogger(backends...)
newLogger.SetBackend(multiBackend)
logger = newLogger
}
// initDefaultBackend creates the console/syslog logging backend.
// Windows: Uses stderr directly (no syslog support)
// Unix-like: Attempts syslog, falls back to stderr
func initDefaultBackend() logging.Backend {
var backend logging.Backend
includeTime := false
if runtime.GOOS == "windows" {
// Windows: Use stderr directly (no syslog support)
backend = logging.NewLogBackend(os.Stderr, "", 0)
includeTime = true
} else {
// Unix-like: Try syslog, fallback to stderr
if syslogBackend, err := logging.NewSyslogBackend(""); err != nil {
fmt.Fprintf(os.Stderr, "syslog backend disabled: %v\n", err)
backend = logging.NewLogBackend(os.Stderr, "", 0)
includeTime = os.Getppid() > 0
} else {
backend = syslogBackend
}
}
return logging.NewBackendFormatter(backend, newFormatter(includeTime))
}
// initFileBackend creates the file logging backend with size/age‑bounded rotation
// so log volume cannot grow without limit on disk.
func initFileBackend() logging.Backend {
logDir := config.GetLogFolder()
if err := os.MkdirAll(logDir, 0o750); err != nil {
fmt.Fprintf(os.Stderr, "failed to create log folder %s: %v\n", logDir, err)
return nil
}
logPath := filepath.Join(logDir, logFileName)
fileRotate = &lumberjack.Logger{
Filename: logPath,
MaxSize: maxLogFileMB,
MaxBackups: maxLogBackups,
MaxAge: maxLogAgeDays,
LocalTime: true,
Compress: compressRotated,
}
backend := logging.NewLogBackend(fileRotate, "", 0)
return logging.NewBackendFormatter(backend, newFormatter(true))
}
// newFormatter creates a log formatter with optional timestamp.
func newFormatter(withTime bool) logging.Formatter {
format := `%{level} - %{message}`
if withTime {
format = `%{time:` + timeFormat + `} %{level} - %{message}`
}
return logging.MustStringFormatter(format)
}
// CloseLogger closes the rotating log writer and cleans up resources.
// Should be called during application shutdown.
func CloseLogger() {
if fileRotate != nil {
_ = fileRotate.Close()
fileRotate = nil
}
}
// Debug logs a debug message and emits it to the log hook.
func Debug(args ...any) {
logger.Debug(args...)
emitLogHook("DEBUG", fmt.Sprint(args...))
}
// Debugf logs a formatted debug message and emits it to the log hook.
func Debugf(format string, args ...any) {
logger.Debugf(format, args...)
emitLogHook("DEBUG", fmt.Sprintf(format, args...))
}
// Info logs an info message and emits it to the log hook.
func Info(args ...any) {
logger.Info(args...)
emitLogHook("INFO", fmt.Sprint(args...))
}
// Infof logs a formatted info message and emits it to the log hook.
func Infof(format string, args ...any) {
logger.Infof(format, args...)
emitLogHook("INFO", fmt.Sprintf(format, args...))
}
// Notice logs a notice message and emits it to the log hook.
func Notice(args ...any) {
logger.Notice(args...)
emitLogHook("NOTICE", fmt.Sprint(args...))
}
// Noticef logs a formatted notice message and emits it to the log hook.
func Noticef(format string, args ...any) {
logger.Noticef(format, args...)
emitLogHook("NOTICE", fmt.Sprintf(format, args...))
}
// Warning logs a warning message and emits it to the log hook.
func Warning(args ...any) {
logger.Warning(args...)
emitLogHook("WARNING", fmt.Sprint(args...))
}
// Warningf logs a formatted warning message and emits it to the log hook.
func Warningf(format string, args ...any) {
logger.Warningf(format, args...)
emitLogHook("WARNING", fmt.Sprintf(format, args...))
}
// Error logs an error message and emits it to the log hook.
func Error(args ...any) {
logger.Error(args...)
emitLogHook("ERROR", fmt.Sprint(args...))
}
// Errorf logs a formatted error message and emits it to the log hook.
func Errorf(format string, args ...any) {
logger.Errorf(format, args...)
emitLogHook("ERROR", fmt.Sprintf(format, args...))
}