Files
explorer-monorepo/backend/logging/logger.go

108 lines
2.2 KiB
Go

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
}