package logger import ( "context" "encoding/json" "fmt" "log" "os" "runtime" "time" ) // LogLevel represents the severity level of a log entry type LogLevel string const ( DEBUG LogLevel = "DEBUG" INFO LogLevel = "INFO" WARN LogLevel = "WARN" ERROR LogLevel = "ERROR" FATAL LogLevel = "FATAL" ) // LogEntry represents a structured log entry type LogEntry struct { Timestamp string `json:"timestamp"` Level LogLevel `json:"level"` Message string `json:"message"` Service string `json:"service"` TraceID string `json:"trace_id,omitempty"` UserID string `json:"user_id,omitempty"` Fields map[string]interface{} `json:"fields,omitempty"` Error *ErrorDetails `json:"error,omitempty"` Source *SourceLocation `json:"source,omitempty"` } // ErrorDetails contains error-specific information type ErrorDetails struct { Type string `json:"type"` Message string `json:"message"` StackTrace string `json:"stack_trace,omitempty"` } // SourceLocation contains source code location information type SourceLocation struct { File string `json:"file"` Line int `json:"line"` Function string `json:"function"` } // Logger provides structured logging capabilities type Logger struct { service string level LogLevel } // New creates a new logger instance func New(service string) *Logger { return &Logger{ service: service, level: INFO, // Default level } } // SetLevel sets the minimum log level func (l *Logger) SetLevel(level LogLevel) { l.level = level } // GetLevel returns the current log level func (l *Logger) GetLevel() LogLevel { return l.level } // IsLevelEnabled checks if a log level is enabled func (l *Logger) IsLevelEnabled(level LogLevel) bool { return l.shouldLog(level) } // Debug logs a debug message func (l *Logger) Debug(message string, fields ...map[string]interface{}) { if l.shouldLog(DEBUG) { l.log(DEBUG, message, nil, fields...) } } // Info logs an info message func (l *Logger) Info(message string, fields ...map[string]interface{}) { if l.shouldLog(INFO) { l.log(INFO, message, nil, fields...) } } // Warn logs a warning message func (l *Logger) Warn(message string, fields ...map[string]interface{}) { if l.shouldLog(WARN) { l.log(WARN, message, nil, fields...) } } // Error logs an error message func (l *Logger) Error(message string, err error, fields ...map[string]interface{}) { if l.shouldLog(ERROR) { l.log(ERROR, message, err, fields...) } } // Fatal logs a fatal message and exits func (l *Logger) Fatal(message string, err error, fields ...map[string]interface{}) { l.log(FATAL, message, err, fields...) os.Exit(1) } // WithContext creates a logger with context information func (l *Logger) WithContext(ctx context.Context) *ContextLogger { return &ContextLogger{ logger: l, ctx: ctx, } } // WithFields creates a logger with predefined fields func (l *Logger) WithFields(fields map[string]interface{}) *FieldLogger { return &FieldLogger{ logger: l, fields: fields, } } // log performs the actual logging func (l *Logger) log(level LogLevel, message string, err error, fields ...map[string]interface{}) { entry := LogEntry{ Timestamp: time.Now().UTC().Format(time.RFC3339), Level: level, Message: message, Service: l.service, } // Add fields if provided if len(fields) > 0 && fields[0] != nil { entry.Fields = fields[0] } // Add error details if provided if err != nil { entry.Error = &ErrorDetails{ Type: fmt.Sprintf("%T", err), Message: err.Error(), } // Add stack trace for errors and fatal logs if level == ERROR || level == FATAL { entry.Error.StackTrace = getStackTrace() } } // Add source location for errors and fatal logs if level == ERROR || level == FATAL { entry.Source = getSourceLocation(3) // Skip 3 frames: log, Error/Fatal, caller } // Output the log entry l.output(entry) } // shouldLog checks if the message should be logged based on level func (l *Logger) shouldLog(level LogLevel) bool { levels := map[LogLevel]int{ DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4, } return levels[level] >= levels[l.level] } // output writes the log entry to the output func (l *Logger) output(entry LogEntry) { jsonBytes, err := json.Marshal(entry) if err != nil { // Fallback to standard logging if JSON marshaling fails log.Printf("LOGGER_ERROR: Failed to marshal log entry: %v", err) log.Printf("%s [%s] %s: %s", entry.Timestamp, entry.Level, entry.Service, entry.Message) return } fmt.Println(string(jsonBytes)) } // getStackTrace returns the current stack trace func getStackTrace() string { buf := make([]byte, 4096) n := runtime.Stack(buf, false) return string(buf[:n]) } // getSourceLocation returns the source location information func getSourceLocation(skip int) *SourceLocation { pc, file, line, ok := runtime.Caller(skip) if !ok { return nil } fn := runtime.FuncForPC(pc) if fn == nil { return nil } return &SourceLocation{ File: file, Line: line, Function: fn.Name(), } } // ContextLogger wraps a logger with context information type ContextLogger struct { logger *Logger ctx context.Context } // Debug logs a debug message with context func (cl *ContextLogger) Debug(message string, fields ...map[string]interface{}) { cl.logWithContext(DEBUG, message, nil, fields...) } // Info logs an info message with context func (cl *ContextLogger) Info(message string, fields ...map[string]interface{}) { cl.logWithContext(INFO, message, nil, fields...) } // Warn logs a warning message with context func (cl *ContextLogger) Warn(message string, fields ...map[string]interface{}) { cl.logWithContext(WARN, message, nil, fields...) } // Error logs an error message with context func (cl *ContextLogger) Error(message string, err error, fields ...map[string]interface{}) { cl.logWithContext(ERROR, message, err, fields...) } // logWithContext logs with context information func (cl *ContextLogger) logWithContext(level LogLevel, message string, err error, fields ...map[string]interface{}) { // Extract context information contextFields := make(map[string]interface{}) // Add trace ID if available if traceID := cl.ctx.Value("trace_id"); traceID != nil { contextFields["trace_id"] = traceID } // Add user ID if available if userID := cl.ctx.Value("user_id"); userID != nil { contextFields["user_id"] = userID } // Merge with provided fields if len(fields) > 0 && fields[0] != nil { for k, v := range fields[0] { contextFields[k] = v } } cl.logger.log(level, message, err, contextFields) } // FieldLogger wraps a logger with predefined fields type FieldLogger struct { logger *Logger fields map[string]interface{} } // Debug logs a debug message with predefined fields func (fl *FieldLogger) Debug(message string, additionalFields ...map[string]interface{}) { fl.logWithFields(DEBUG, message, nil, additionalFields...) } // Info logs an info message with predefined fields func (fl *FieldLogger) Info(message string, additionalFields ...map[string]interface{}) { fl.logWithFields(INFO, message, nil, additionalFields...) } // Warn logs a warning message with predefined fields func (fl *FieldLogger) Warn(message string, additionalFields ...map[string]interface{}) { fl.logWithFields(WARN, message, nil, additionalFields...) } // Error logs an error message with predefined fields func (fl *FieldLogger) Error(message string, err error, additionalFields ...map[string]interface{}) { fl.logWithFields(ERROR, message, err, additionalFields...) } // logWithFields logs with predefined fields func (fl *FieldLogger) logWithFields(level LogLevel, message string, err error, additionalFields ...map[string]interface{}) { // Merge predefined fields with additional fields mergedFields := make(map[string]interface{}) // Add predefined fields for k, v := range fl.fields { mergedFields[k] = v } // Add additional fields if len(additionalFields) > 0 && additionalFields[0] != nil { for k, v := range additionalFields[0] { mergedFields[k] = v } } fl.logger.log(level, message, err, mergedFields) } // Global logger instance var globalLogger = New("app") // SetGlobalLevel sets the global logger level func SetGlobalLevel(level LogLevel) { globalLogger.SetLevel(level) } // Debug logs a debug message using the global logger func Debug(message string, fields ...map[string]interface{}) { globalLogger.Debug(message, fields...) } // Info logs an info message using the global logger func Info(message string, fields ...map[string]interface{}) { globalLogger.Info(message, fields...) } // Warn logs a warning message using the global logger func Warn(message string, fields ...map[string]interface{}) { globalLogger.Warn(message, fields...) } // Error logs an error message using the global logger func Error(message string, err error, fields ...map[string]interface{}) { globalLogger.Error(message, err, fields...) } // Fatal logs a fatal message using the global logger and exits func Fatal(message string, err error, fields ...map[string]interface{}) { globalLogger.Fatal(message, err, fields...) }