217 lines
4.8 KiB
Go
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
|
|
}
|
|
}
|