Initialize base

This commit is contained in:
hdbar
2025-07-27 16:20:21 +03:30
commit ad9db7bcce
31 changed files with 8268 additions and 0 deletions
+304
View File
@@ -0,0 +1,304 @@
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()
}