305 lines
7.3 KiB
Go
305 lines
7.3 KiB
Go
package logger
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
|
|
"tm/internal/infrastructure"
|
|
)
|
|
|
|
// Logger interface defines logging methods
|
|
type Logger interface {
|
|
Debug(msg string, fields map[string]interface{})
|
|
Info(msg string, fields map[string]interface{})
|
|
Warn(msg string, fields map[string]interface{})
|
|
Error(msg string, fields map[string]interface{})
|
|
Fatal(msg string, fields map[string]interface{})
|
|
WithFields(fields map[string]interface{}) Logger
|
|
Sync() error // Flush any buffered log entries
|
|
}
|
|
|
|
// ZapLogger wraps zap logger
|
|
type ZapLogger struct {
|
|
logger *zap.Logger
|
|
sugar *zap.SugaredLogger
|
|
fields map[string]interface{}
|
|
}
|
|
|
|
// NewLogger creates a new logger instance with Zap and Lumberjack
|
|
func NewLogger(config *infrastructure.LoggingConfig) Logger {
|
|
// Parse log level
|
|
level := parseLogLevel(config.Level)
|
|
|
|
// Create encoder config
|
|
encoderConfig := createEncoderConfig(config.Format)
|
|
|
|
// Create encoder
|
|
encoder := createEncoder(config.Format, encoderConfig)
|
|
|
|
// Create writer
|
|
writer := createWriter(config)
|
|
|
|
// Create core
|
|
core := zapcore.NewCore(encoder, writer, level)
|
|
|
|
// Create logger with caller information
|
|
logger := zap.New(core,
|
|
zap.AddCaller(),
|
|
zap.AddCallerSkip(1),
|
|
zap.AddStacktrace(zapcore.ErrorLevel),
|
|
)
|
|
|
|
return &ZapLogger{
|
|
logger: logger,
|
|
sugar: logger.Sugar(),
|
|
fields: make(map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
// createWriter creates the appropriate writer based on configuration
|
|
func createWriter(config *infrastructure.LoggingConfig) zapcore.WriteSyncer {
|
|
switch config.Output {
|
|
case "stdout":
|
|
return zapcore.AddSync(os.Stdout)
|
|
case "stderr":
|
|
return zapcore.AddSync(os.Stderr)
|
|
case "file":
|
|
return createFileWriter(config.File)
|
|
default:
|
|
// Default to stdout
|
|
return zapcore.AddSync(os.Stdout)
|
|
}
|
|
}
|
|
|
|
// createFileWriter creates a file writer with rotation using lumberjack
|
|
func createFileWriter(fileConfig infrastructure.LogFileConfig) zapcore.WriteSyncer {
|
|
// Ensure log directory exists
|
|
logDir := filepath.Dir(fileConfig.Path)
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
// Fallback to stdout if can't create directory
|
|
return zapcore.AddSync(os.Stdout)
|
|
}
|
|
|
|
// Create lumberjack logger for rotation
|
|
lumberjackLogger := &lumberjack.Logger{
|
|
Filename: fileConfig.Path,
|
|
MaxSize: fileConfig.MaxSize, // MB
|
|
MaxBackups: fileConfig.MaxBackups,
|
|
MaxAge: fileConfig.MaxAge, // days
|
|
Compress: fileConfig.Compress,
|
|
}
|
|
|
|
// Create multi-writer to write to both file and stdout
|
|
multiWriter := io.MultiWriter(lumberjackLogger, os.Stdout)
|
|
|
|
return zapcore.AddSync(multiWriter)
|
|
}
|
|
|
|
// createEncoderConfig creates encoder configuration
|
|
func createEncoderConfig(format string) zapcore.EncoderConfig {
|
|
config := zap.NewProductionEncoderConfig()
|
|
|
|
// Customize timestamp format
|
|
config.TimeKey = "timestamp"
|
|
config.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
|
|
// Customize level encoding
|
|
config.EncodeLevel = zapcore.LowercaseLevelEncoder
|
|
|
|
// Customize caller encoding
|
|
config.EncodeCaller = zapcore.ShortCallerEncoder
|
|
|
|
// Customize duration encoding
|
|
config.EncodeDuration = zapcore.StringDurationEncoder
|
|
|
|
return config
|
|
}
|
|
|
|
// createEncoder creates the appropriate encoder
|
|
func createEncoder(format string, config zapcore.EncoderConfig) zapcore.Encoder {
|
|
switch format {
|
|
case "json":
|
|
return zapcore.NewJSONEncoder(config)
|
|
case "console", "text":
|
|
return zapcore.NewConsoleEncoder(config)
|
|
default:
|
|
return zapcore.NewJSONEncoder(config)
|
|
}
|
|
}
|
|
|
|
// parseLogLevel converts string level to zapcore.Level
|
|
func parseLogLevel(level string) zapcore.Level {
|
|
switch level {
|
|
case "debug":
|
|
return zapcore.DebugLevel
|
|
case "info":
|
|
return zapcore.InfoLevel
|
|
case "warn", "warning":
|
|
return zapcore.WarnLevel
|
|
case "error":
|
|
return zapcore.ErrorLevel
|
|
case "fatal":
|
|
return zapcore.FatalLevel
|
|
case "panic":
|
|
return zapcore.PanicLevel
|
|
default:
|
|
return zapcore.InfoLevel
|
|
}
|
|
}
|
|
|
|
// convertFields converts map[string]interface{} to zap fields
|
|
func convertFields(fields map[string]interface{}) []zap.Field {
|
|
zapFields := make([]zap.Field, 0, len(fields))
|
|
for key, value := range fields {
|
|
zapFields = append(zapFields, zap.Any(key, value))
|
|
}
|
|
return zapFields
|
|
}
|
|
|
|
// Debug logs a debug message
|
|
func (l *ZapLogger) Debug(msg string, fields map[string]interface{}) {
|
|
allFields := l.mergeFields(fields)
|
|
l.logger.Debug(msg, convertFields(allFields)...)
|
|
}
|
|
|
|
// Info logs an info message
|
|
func (l *ZapLogger) Info(msg string, fields map[string]interface{}) {
|
|
allFields := l.mergeFields(fields)
|
|
l.logger.Info(msg, convertFields(allFields)...)
|
|
}
|
|
|
|
// Warn logs a warning message
|
|
func (l *ZapLogger) Warn(msg string, fields map[string]interface{}) {
|
|
allFields := l.mergeFields(fields)
|
|
l.logger.Warn(msg, convertFields(allFields)...)
|
|
}
|
|
|
|
// Error logs an error message
|
|
func (l *ZapLogger) Error(msg string, fields map[string]interface{}) {
|
|
allFields := l.mergeFields(fields)
|
|
l.logger.Error(msg, convertFields(allFields)...)
|
|
}
|
|
|
|
// Fatal logs a fatal message and exits
|
|
func (l *ZapLogger) Fatal(msg string, fields map[string]interface{}) {
|
|
allFields := l.mergeFields(fields)
|
|
l.logger.Fatal(msg, convertFields(allFields)...)
|
|
}
|
|
|
|
// WithFields returns a logger with predefined fields
|
|
func (l *ZapLogger) WithFields(fields map[string]interface{}) Logger {
|
|
newFields := make(map[string]interface{})
|
|
|
|
// Copy existing fields
|
|
for k, v := range l.fields {
|
|
newFields[k] = v
|
|
}
|
|
|
|
// Add new fields
|
|
for k, v := range fields {
|
|
newFields[k] = v
|
|
}
|
|
|
|
return &ZapLogger{
|
|
logger: l.logger,
|
|
sugar: l.sugar,
|
|
fields: newFields,
|
|
}
|
|
}
|
|
|
|
// Sync flushes any buffered log entries
|
|
func (l *ZapLogger) Sync() error {
|
|
return l.logger.Sync()
|
|
}
|
|
|
|
// mergeFields merges instance fields with provided fields
|
|
func (l *ZapLogger) mergeFields(fields map[string]interface{}) map[string]interface{} {
|
|
if len(l.fields) == 0 {
|
|
return fields
|
|
}
|
|
|
|
merged := make(map[string]interface{})
|
|
|
|
// Add instance fields first
|
|
for k, v := range l.fields {
|
|
merged[k] = v
|
|
}
|
|
|
|
// Add provided fields (they override instance fields)
|
|
for k, v := range fields {
|
|
merged[k] = v
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
// Global logger instance
|
|
var globalLogger Logger
|
|
|
|
// InitLogger initializes the global logger
|
|
func InitLogger(config *infrastructure.LoggingConfig) {
|
|
globalLogger = NewLogger(config)
|
|
}
|
|
|
|
// GetLogger returns the global logger instance
|
|
func GetLogger() Logger {
|
|
if globalLogger == nil {
|
|
// Fallback configuration
|
|
config := &infrastructure.LoggingConfig{
|
|
Level: "info",
|
|
Format: "json",
|
|
Output: "stdout",
|
|
File: infrastructure.LogFileConfig{
|
|
Path: "./logs/app.log",
|
|
MaxSize: 100,
|
|
MaxBackups: 5,
|
|
MaxAge: 30,
|
|
Compress: true,
|
|
},
|
|
}
|
|
globalLogger = NewLogger(config)
|
|
}
|
|
return globalLogger
|
|
}
|
|
|
|
// Cleanup flushes and closes the logger
|
|
func Cleanup() {
|
|
if globalLogger != nil {
|
|
globalLogger.Sync()
|
|
}
|
|
}
|
|
|
|
// Helper functions for convenience
|
|
func Debug(msg string, fields map[string]interface{}) {
|
|
GetLogger().Debug(msg, fields)
|
|
}
|
|
|
|
func Info(msg string, fields map[string]interface{}) {
|
|
GetLogger().Info(msg, fields)
|
|
}
|
|
|
|
func Warn(msg string, fields map[string]interface{}) {
|
|
GetLogger().Warn(msg, fields)
|
|
}
|
|
|
|
func Error(msg string, fields map[string]interface{}) {
|
|
GetLogger().Error(msg, fields)
|
|
}
|
|
|
|
func Fatal(msg string, fields map[string]interface{}) {
|
|
GetLogger().Fatal(msg, fields)
|
|
}
|
|
|
|
func WithFields(fields map[string]interface{}) Logger {
|
|
return GetLogger().WithFields(fields)
|
|
}
|
|
|
|
func Sync() error {
|
|
return GetLogger().Sync()
|
|
}
|