114 lines
2.6 KiB
Go
114 lines
2.6 KiB
Go
package logger
|
|
|
|
import (
|
|
"os"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
// Logger wraps zap.SugaredLogger for structured logging.
|
|
// Corresponds to Chatwoot's Rails Logger but with structured output.
|
|
var globalLogger *zap.SugaredLogger
|
|
|
|
// Config for logger initialization
|
|
type Config struct {
|
|
Level string // debug, info, warn, error
|
|
Format string // json, console
|
|
Output string // stdout, stderr, or file path
|
|
ErrorOutput string // stderr or file path
|
|
}
|
|
|
|
// Init initializes the global logger based on config.
|
|
func Init(cfg Config) error {
|
|
level, err := parseLevel(cfg.Level)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
encoderConfig := zap.NewProductionEncoderConfig()
|
|
encoderConfig.TimeKey = "timestamp"
|
|
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
|
|
|
var encoder zapcore.Encoder
|
|
if cfg.Format == "console" {
|
|
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
|
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
|
} else {
|
|
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
|
}
|
|
|
|
outputWriter, err := getOutput(cfg.Output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
errorWriter, err := getOutput(cfg.ErrorOutput)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
core := zapcore.NewCore(
|
|
encoder,
|
|
zapcore.NewMultiWriteSyncer(zapcore.AddSync(outputWriter)),
|
|
level,
|
|
)
|
|
|
|
// Also send error-level logs to error output
|
|
errorCore := zapcore.NewCore(
|
|
encoder,
|
|
zapcore.AddSync(errorWriter),
|
|
zapcore.ErrorLevel,
|
|
)
|
|
|
|
combinedCore := zapcore.NewTee(core, errorCore)
|
|
|
|
zapLogger := zap.New(combinedCore, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
|
globalLogger = zapLogger.Sugar()
|
|
|
|
return nil
|
|
}
|
|
|
|
// L returns the global sugared logger
|
|
func L() *zap.SugaredLogger {
|
|
if globalLogger == nil {
|
|
// Fallback: if Init wasn't called, use a default logger
|
|
zapLogger, _ := zap.NewProduction()
|
|
globalLogger = zapLogger.Sugar()
|
|
}
|
|
return globalLogger
|
|
}
|
|
|
|
// Sync flushes any buffered log entries. Should be called before program exit.
|
|
func Sync() {
|
|
if globalLogger != nil {
|
|
_ = globalLogger.Sync()
|
|
}
|
|
}
|
|
|
|
func parseLevel(levelStr string) (zapcore.Level, error) {
|
|
switch levelStr {
|
|
case "debug":
|
|
return zapcore.DebugLevel, nil
|
|
case "info":
|
|
return zapcore.InfoLevel, nil
|
|
case "warn":
|
|
return zapcore.WarnLevel, nil
|
|
case "error":
|
|
return zapcore.ErrorLevel, nil
|
|
default:
|
|
return zapcore.InfoLevel, nil
|
|
}
|
|
}
|
|
|
|
func getOutput(output string) (*os.File, error) {
|
|
switch output {
|
|
case "stdout":
|
|
return os.Stdout, nil
|
|
case "stderr":
|
|
return os.Stderr, nil
|
|
default:
|
|
return os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
}
|
|
}
|