171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
// @tag logging
|
|
// @tag core
|
|
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Level represents the minimum severity for log messages.
|
|
type Level int
|
|
|
|
// Log severity levels from most verbose to most critical.
|
|
const (
|
|
LevelDebug Level = iota // Debug logs everything including diagnostic messages
|
|
LevelInfo // Info logs informational messages and above (default)
|
|
LevelWarn // Warn logs warnings, errors, and critical messages
|
|
LevelError // LevelError logs only errors and critical messages
|
|
)
|
|
|
|
var (
|
|
currentLevel = LevelInfo
|
|
logger = log.New(os.Stdout, "", 0)
|
|
logFile *os.File
|
|
logPath string
|
|
pid int
|
|
mu sync.Once
|
|
loggerMu sync.Mutex // Protects logger and logFile
|
|
)
|
|
|
|
// initPID gets the process ID for log messages
|
|
func initPID() {
|
|
pid = os.Getpid()
|
|
}
|
|
|
|
// formatLog formats a message with timestamp, log level, and pid.
|
|
// Format: timestamp level pekoe[pid]: message
|
|
func formatLog(level string, message string) string {
|
|
mu.Do(initPID)
|
|
|
|
// Timestamp format: "YYYY Mmm dd hh:mm:ss"
|
|
// e.g., "2024 Apr 2 14:30:15"
|
|
now := time.Now()
|
|
timestamp := now.Format("2006 Jan _2 15:04:05")
|
|
|
|
// Format: timestamp level pekoe[pid]: message
|
|
// e.g., "2024 Apr 2 14:30:15 INFO pekoe[12345]: event=startup"
|
|
return fmt.Sprintf("%s %s pekoe[%d]: %s", timestamp, level, pid, message)
|
|
}
|
|
|
|
// SetOutput configures the logger to write to the specified file.
|
|
// An empty path resets to stdout.
|
|
// Returns an error if the file cannot be opened/created.
|
|
// nolint: gosec
|
|
func SetOutput(path string) error {
|
|
loggerMu.Lock()
|
|
defer loggerMu.Unlock()
|
|
|
|
// Close existing log file if any
|
|
if logFile != nil {
|
|
_ = logFile.Close()
|
|
logFile = nil
|
|
}
|
|
|
|
if path == "" {
|
|
logPath = ""
|
|
logger = log.New(os.Stdout, "", 0)
|
|
return nil
|
|
}
|
|
|
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
logPath = path
|
|
logFile = file
|
|
logger = log.New(file, "", 0)
|
|
return nil
|
|
}
|
|
|
|
// Reopen reopens the log file.
|
|
// This is useful for log rotation - after the log file is moved/renamed,
|
|
// sending SIGUSR1 will cause the server to reopen the log file.
|
|
// Returns an error if the log file cannot be opened.
|
|
func Reopen() error {
|
|
loggerMu.Lock()
|
|
defer loggerMu.Unlock()
|
|
|
|
// If logging to stdout, nothing to reopen
|
|
if logPath == "" {
|
|
return nil
|
|
}
|
|
|
|
// Close existing file
|
|
if logFile != nil {
|
|
_ = logFile.Close()
|
|
logFile = nil
|
|
}
|
|
|
|
// Open the file again (create if doesn't exist)
|
|
//nolint: gosec // logPath is configured by admin, not user input
|
|
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
// Fall back to stdout if we can't reopen
|
|
logger = log.New(os.Stdout, "", 0)
|
|
return fmt.Errorf("failed to reopen log file %s: %w", logPath, err)
|
|
}
|
|
|
|
logFile = file
|
|
logger = log.New(file, "", 0)
|
|
return nil
|
|
}
|
|
|
|
// SetLevel configures the minimum log level from a string name.
|
|
// Accepted values: "debug", "info", "warn", "error".
|
|
func SetLevel(level string) {
|
|
switch level {
|
|
case "debug":
|
|
currentLevel = LevelDebug
|
|
case "info":
|
|
currentLevel = LevelInfo
|
|
case "warn":
|
|
currentLevel = LevelWarn
|
|
case "error":
|
|
currentLevel = LevelError
|
|
}
|
|
}
|
|
|
|
// Debug logs a message at debug level. Uses fmt.Sprintf formatting.
|
|
func Debug(format string, v ...any) {
|
|
if currentLevel <= LevelDebug {
|
|
msg := fmt.Sprintf(format, v...)
|
|
logger.Println(formatLog("DEBUG", msg))
|
|
}
|
|
}
|
|
|
|
// Info logs a message at info level. Uses fmt.Sprintf formatting.
|
|
func Info(format string, v ...any) {
|
|
if currentLevel <= LevelInfo {
|
|
msg := fmt.Sprintf(format, v...)
|
|
logger.Println(formatLog("INFO", msg))
|
|
}
|
|
}
|
|
|
|
// Warn logs a message at warning level. Uses fmt.Sprintf formatting.
|
|
func Warn(format string, v ...any) {
|
|
if currentLevel <= LevelWarn {
|
|
msg := fmt.Sprintf(format, v...)
|
|
logger.Println(formatLog("WARN", msg))
|
|
}
|
|
}
|
|
|
|
// Error logs a message at error level. Uses fmt.Sprintf formatting.
|
|
func Error(format string, v ...any) {
|
|
if currentLevel <= LevelError {
|
|
msg := fmt.Sprintf(format, v...)
|
|
logger.Println(formatLog("ERROR", msg))
|
|
}
|
|
}
|
|
|
|
// Fatal logs a message at critical level and terminates the process with exit code 1.
|
|
func Fatal(format string, v ...any) {
|
|
msg := fmt.Sprintf(format, v...)
|
|
logger.Println(formatLog("CRITICAL", msg))
|
|
os.Exit(1)
|
|
}
|