package logging import ( "context" "encoding/json" "fmt" "log" "os" "time" ) // Logger provides structured logging type Logger struct { level string fields map[string]interface{} } // NewLogger creates a new logger func NewLogger(level string) *Logger { return &Logger{ level: level, fields: make(map[string]interface{}), } } // WithField adds a field to the logger func (l *Logger) WithField(key string, value interface{}) *Logger { newLogger := &Logger{ level: l.level, fields: make(map[string]interface{}), } for k, v := range l.fields { newLogger.fields[k] = v } newLogger.fields[key] = value return newLogger } // Info logs an info message func (l *Logger) Info(ctx context.Context, message string) { l.log(ctx, "info", message, nil) } // Error logs an error message func (l *Logger) Error(ctx context.Context, message string, err error) { l.log(ctx, "error", message, map[string]interface{}{ "error": err.Error(), }) } // Warn logs a warning message func (l *Logger) Warn(ctx context.Context, message string) { l.log(ctx, "warn", message, nil) } // Debug logs a debug message func (l *Logger) Debug(ctx context.Context, message string) { l.log(ctx, "debug", message, nil) } // log logs a message with structured fields func (l *Logger) log(ctx context.Context, level, message string, extraFields map[string]interface{}) { entry := map[string]interface{}{ "timestamp": time.Now().UTC().Format(time.RFC3339), "level": level, "message": message, } // Add logger fields for k, v := range l.fields { entry[k] = v } // Add extra fields if extraFields != nil { for k, v := range extraFields { entry[k] = v } } // Sanitize PII entry = sanitizePII(entry) // Output as JSON jsonBytes, err := json.Marshal(entry) if err != nil { log.Printf("Failed to marshal log entry: %v", err) return } fmt.Fprintln(os.Stdout, string(jsonBytes)) } // sanitizePII removes or masks PII from log entries func sanitizePII(entry map[string]interface{}) map[string]interface{} { sanitized := make(map[string]interface{}) for k, v := range entry { // Mask sensitive fields if k == "password" || k == "api_key" || k == "token" { sanitized[k] = "***REDACTED***" } else { sanitized[k] = v } } return sanitized }