package observability import ( "log" "os" "sync" ) // Logger provides structured logging. type Logger struct { mu sync.Mutex logger *log.Logger prefix string fields map[string]any } // NewLogger creates a new logger. func NewLogger(prefix string) *Logger { return &Logger{ logger: log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds), prefix: prefix, fields: make(map[string]any), } } // WithFields returns a new logger with additional fields. func (l *Logger) WithFields(fields map[string]any) *Logger { newLogger := &Logger{ logger: l.logger, prefix: l.prefix, fields: make(map[string]any), } for k, v := range l.fields { newLogger.fields[k] = v } for k, v := range fields { newLogger.fields[k] = v } return newLogger } // Info logs an info message. func (l *Logger) Info(msg string) { l.mu.Lock() defer l.mu.Unlock() l.log("INFO", msg) } // Error logs an error message. func (l *Logger) Error(msg string, err error) { l.mu.Lock() defer l.mu.Unlock() if err != nil { l.log("ERROR", msg+" "+err.Error()) } else { l.log("ERROR", msg) } } // Debug logs a debug message. func (l *Logger) Debug(msg string) { l.mu.Lock() defer l.mu.Unlock() l.log("DEBUG", msg) } func (l *Logger) log(level, msg string) { prefix := l.prefix if prefix != "" { prefix = "[" + prefix + "] " } l.logger.SetPrefix(prefix + level + " ") var args []any for k, v := range l.fields { args = append(args, k, v) } if len(args) > 0 { l.logger.Printf(msg+" %+v", args...) } else { l.logger.Print(msg) } }