// Package logging provides structured logging for ja4sentinel service components package logging import ( "encoding/json" "log" "os" "strings" "sync" "time" "ja4sentinel/api" ) // ServiceLogger handles structured logging for the ja4sentinel service type ServiceLogger struct { level string mutex sync.Mutex out *log.Logger formatter func(api.ServiceLog) ([]byte, error) } // NewServiceLogger creates a new service logger func NewServiceLogger(level string) *ServiceLogger { logger := &ServiceLogger{ level: strings.ToLower(level), out: log.New(os.Stdout, "", 0), formatter: func(s api.ServiceLog) ([]byte, error) { logData := map[string]interface{}{ "timestamp": time.Now().UnixNano(), "level": strings.ToUpper(s.Level), "component": s.Component, "message": s.Message, } if s.Details != nil && len(s.Details) > 0 { for k, v := range s.Details { logData[k] = v } } return json.Marshal(logData) }, } return logger } // Log emits a structured log entry to stdout in JSON format func (l *ServiceLogger) Log(component, level, message string, details map[string]string) { normalizedLevel := strings.ToLower(level) if !l.isLogLevelEnabled(normalizedLevel) { return } // Lock to prevent concurrent writes to stdout l.mutex.Lock() defer l.mutex.Unlock() serviceLog := api.ServiceLog{ Level: normalizedLevel, Component: component, Message: message, Details: details, } jsonData, err := l.formatter(serviceLog) if err != nil { // Fallback to simple logging if JSON formatting fails l.out.Printf(`{"timestamp":%d,"level":"ERROR","component":"logging","message":"%s","original_message":"%s"}`+"\n", time.Now().UnixNano(), err.Error(), message) return } l.out.Println(string(jsonData)) } // Debug logs a debug level entry func (l *ServiceLogger) Debug(component, message string, details map[string]string) { l.Log(component, "debug", message, details) } // Info logs an info level entry func (l *ServiceLogger) Info(component, message string, details map[string]string) { l.Log(component, "info", message, details) } // Warn logs a warning level entry func (l *ServiceLogger) Warn(component, message string, details map[string]string) { l.Log(component, "warn", message, details) } // Error logs an error level entry func (l *ServiceLogger) Error(component, message string, details map[string]string) { l.Log(component, "error", message, details) } // isLogLevelEnabled checks if a log level should be emitted based on configured level func (l *ServiceLogger) isLogLevelEnabled(messageLevel string) bool { switch l.level { case "debug": return true case "info": return messageLevel != "debug" case "warn": return messageLevel != "debug" && messageLevel != "info" case "error": return messageLevel == "error" default: return false // If level is invalid, don't log anything } }