148 lines
2.8 KiB
Go
148 lines
2.8 KiB
Go
// internal/logging/logging.go
|
|
package logging
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
// Global logger instance
|
|
logger *Logger
|
|
once sync.Once
|
|
)
|
|
|
|
// LogLevel represents the severity of a log message
|
|
type LogLevel int
|
|
|
|
const (
|
|
DEBUG LogLevel = iota
|
|
INFO
|
|
WARN
|
|
ERROR
|
|
FATAL
|
|
)
|
|
|
|
// Logger provides logging functionality
|
|
type Logger struct {
|
|
logLevel LogLevel
|
|
output io.Writer
|
|
stdLog *log.Logger
|
|
errLog *log.Logger
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// Singleton
|
|
func InitLogger(level LogLevel, logFile string) {
|
|
once.Do(func() {
|
|
var output io.Writer = os.Stdout
|
|
|
|
if logFile != "" {
|
|
logDir := filepath.Dir(logFile)
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to create log directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to open log file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
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),
|
|
}
|
|
})
|
|
}
|
|
|
|
// Singleton
|
|
func GetLogger() *Logger {
|
|
if logger == nil {
|
|
InitLogger(INFO, "")
|
|
}
|
|
return logger
|
|
}
|
|
|
|
func (l *Logger) SetLogLevel(level LogLevel) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.logLevel = level
|
|
}
|
|
|
|
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
|
|
_, file, line, ok := runtime.Caller(2)
|
|
if !ok {
|
|
file = "unknown"
|
|
line = 0
|
|
}
|
|
|
|
// Format message with source location
|
|
message := fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, fmt.Sprintf(format, v...))
|
|
|
|
if level >= ERROR {
|
|
l.errLog.Println(message)
|
|
} else {
|
|
l.stdLog.Println(message)
|
|
}
|
|
}
|
|
|
|
// 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...)
|
|
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, 1024)
|
|
n := runtime.Stack(buf, false)
|
|
|
|
l.Error("%s: %v\n%s", message, err, buf[:n])
|
|
}
|