2025-03-28 20:13:37 +01:00

217 lines
4.8 KiB
Go

// internal/logging/logging.go
package logging
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"sync"
)
var (
// Global logger instance
logger *Logger
mu sync.Mutex // Mutex to protect logger initialization and replacement
)
// LogLevel represents the severity of a log message
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
FATAL
)
// String returns the string representation of the log level
func (l LogLevel) String() string {
switch l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
// Logger provides logging functionality
type Logger struct {
logLevel LogLevel
output io.Writer
stdLog *log.Logger
errLog *log.Logger
file *os.File // Store file reference for closing
mu sync.Mutex
}
// InitLogger initializes or reinitializes the global logger instance
func InitLogger(level LogLevel, logFilePath string) {
mu.Lock()
defer mu.Unlock()
// Close the existing logger if there is one
if logger != nil {
logger.Close()
}
fmt.Printf("Initializing logger with level %s and log file: %s\n", level.String(), logFilePath)
var output io.Writer = os.Stdout
var fileHandle *os.File
// Set up log file if specified
if logFilePath != "" {
logDir := filepath.Dir(logFilePath)
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Failed to create log directory %s: %v\n", logDir, err)
os.Exit(1)
}
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open log file %s: %v\n", logFilePath, err)
os.Exit(1)
}
fmt.Printf("Successfully opened log file: %s\n", logFilePath)
fileHandle = file
output = io.MultiWriter(os.Stdout, file)
}
logger = &Logger{
logLevel: level,
output: output,
stdLog: log.New(output, "", log.LstdFlags),
errLog: log.New(output, "ERROR: ", log.LstdFlags),
file: fileHandle,
}
// Log initialization message through the logger itself
logger.Info("Logger initialized with level %s and log file %s", level.String(), logFilePath)
}
// GetLogger returns the global logger instance, initializing it if necessary
func GetLogger() *Logger {
mu.Lock()
defer mu.Unlock()
if logger == nil {
// Initialize with default settings if not already initialized
// We need to unlock before calling InitLogger since it will lock mu
mu.Unlock()
InitLogger(INFO, "")
mu.Lock()
}
return logger
}
// SetLogLevel changes the current log level
func (l *Logger) SetLogLevel(level LogLevel) {
l.mu.Lock()
defer l.mu.Unlock()
l.logLevel = level
l.Info("Log level set to %s", level.String())
}
// log is the internal logging function
func (l *Logger) log(level LogLevel, format string, v ...interface{}) {
if level < l.logLevel {
return
}
l.mu.Lock()
defer l.mu.Unlock()
// Get caller information (skip 2 levels to get the actual calling function)
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "unknown"
line = 0
}
// Format message with source location and level
prefix := fmt.Sprintf("[%s] %s:%d: ", level.String(), filepath.Base(file), line)
message := fmt.Sprintf(format, v...)
fullMessage := prefix + message
if level >= ERROR {
l.errLog.Println(fullMessage)
} else {
l.stdLog.Println(fullMessage)
}
}
// Debug logs a debug message
func (l *Logger) Debug(format string, v ...interface{}) {
l.log(DEBUG, format, v...)
}
// Info logs an info message
func (l *Logger) Info(format string, v ...interface{}) {
l.log(INFO, format, v...)
}
// Warn logs a warning message
func (l *Logger) Warn(format string, v ...interface{}) {
l.log(WARN, format, v...)
}
// Error logs an error message
func (l *Logger) Error(format string, v ...interface{}) {
l.log(ERROR, format, v...)
}
// Fatal logs a fatal message and exits the program
func (l *Logger) Fatal(format string, v ...interface{}) {
l.log(FATAL, format, v...)
l.Close() // Ensure logs are flushed before exit
os.Exit(1)
}
// ErrorWithStack logs an error with its stack trace
func (l *Logger) ErrorWithStack(err error, format string, v ...interface{}) {
if err == nil {
return
}
message := fmt.Sprintf(format, v...)
buf := make([]byte, 4096) // Larger buffer for more stack trace info
n := runtime.Stack(buf, false)
l.Error("%s: %v\n%s", message, err, buf[:n])
}
// Close flushes and closes the log file if one is being used
func (l *Logger) Close() {
l.mu.Lock()
defer l.mu.Unlock()
if l.file != nil {
l.file.Sync() // Flush any buffered data
l.file.Close() // Close the file
l.file = nil // Clear the reference
}
}
// Shutdown properly closes the logger
func Shutdown() {
mu.Lock()
defer mu.Unlock()
if logger != nil {
logger.Close()
logger = nil
}
}