package logging import ( "encoding/json" "fmt" "os" "time" ) // Logger interface for CHORUS logging type Logger interface { Info(msg string, args ...interface{}) Warn(msg string, args ...interface{}) Error(msg string, args ...interface{}) Debug(msg string, args ...interface{}) } // ContainerLogger provides structured logging optimized for container environments // All logs go to stdout/stderr for collection by container runtime (Docker, K8s, etc.) type ContainerLogger struct { name string level LogLevel format LogFormat } // LogLevel represents logging levels type LogLevel int const ( DEBUG LogLevel = iota INFO WARN ERROR ) // LogFormat represents log output formats type LogFormat int const ( STRUCTURED LogFormat = iota // JSON structured logging HUMAN // Human-readable logging ) // LogEntry represents a structured log entry type LogEntry struct { Timestamp string `json:"timestamp"` Level string `json:"level"` Service string `json:"service"` Message string `json:"message"` Data map[string]interface{} `json:"data,omitempty"` } // NewContainerLogger creates a new container-optimized logger func NewContainerLogger(serviceName string) *ContainerLogger { level := INFO format := STRUCTURED // Parse log level from environment if levelStr := os.Getenv("LOG_LEVEL"); levelStr != "" { switch levelStr { case "debug": level = DEBUG case "info": level = INFO case "warn": level = WARN case "error": level = ERROR } } // Parse log format from environment if formatStr := os.Getenv("LOG_FORMAT"); formatStr == "human" { format = HUMAN } return &ContainerLogger{ name: serviceName, level: level, format: format, } } // Info logs informational messages func (l *ContainerLogger) Info(msg string, args ...interface{}) { if l.level <= INFO { l.log(INFO, msg, args...) } } // Warn logs warning messages func (l *ContainerLogger) Warn(msg string, args ...interface{}) { if l.level <= WARN { l.log(WARN, msg, args...) } } // Error logs error messages to stderr func (l *ContainerLogger) Error(msg string, args ...interface{}) { if l.level <= ERROR { l.logToStderr(ERROR, msg, args...) } } // Debug logs debug messages (only when DEBUG level is enabled) func (l *ContainerLogger) Debug(msg string, args ...interface{}) { if l.level <= DEBUG { l.log(DEBUG, msg, args...) } } // log writes log entries to stdout func (l *ContainerLogger) log(level LogLevel, msg string, args ...interface{}) { entry := l.createLogEntry(level, msg, args...) switch l.format { case STRUCTURED: l.writeJSON(os.Stdout, entry) case HUMAN: l.writeHuman(os.Stdout, entry) } } // logToStderr writes log entries to stderr (for errors) func (l *ContainerLogger) logToStderr(level LogLevel, msg string, args ...interface{}) { entry := l.createLogEntry(level, msg, args...) switch l.format { case STRUCTURED: l.writeJSON(os.Stderr, entry) case HUMAN: l.writeHuman(os.Stderr, entry) } } // createLogEntry creates a structured log entry func (l *ContainerLogger) createLogEntry(level LogLevel, msg string, args ...interface{}) LogEntry { return LogEntry{ Timestamp: time.Now().UTC().Format(time.RFC3339Nano), Level: l.levelToString(level), Service: l.name, Message: fmt.Sprintf(msg, args...), Data: make(map[string]interface{}), } } // writeJSON writes the log entry as JSON func (l *ContainerLogger) writeJSON(output *os.File, entry LogEntry) { if jsonData, err := json.Marshal(entry); err == nil { fmt.Fprintln(output, string(jsonData)) } } // writeHuman writes the log entry in human-readable format func (l *ContainerLogger) writeHuman(output *os.File, entry LogEntry) { fmt.Fprintf(output, "[%s] [%s] [%s] %s\n", entry.Timestamp, entry.Level, entry.Service, entry.Message, ) } // levelToString converts LogLevel to string func (l *ContainerLogger) levelToString(level LogLevel) string { switch level { case DEBUG: return "DEBUG" case INFO: return "INFO" case WARN: return "WARN" case ERROR: return "ERROR" default: return "UNKNOWN" } } // WithData creates a logger that includes additional structured data in log entries func (l *ContainerLogger) WithData(data map[string]interface{}) Logger { // Return a new logger instance that includes the data // This is useful for request-scoped logging with context return &dataLogger{ base: l, data: data, } } // dataLogger is a wrapper that adds structured data to log entries type dataLogger struct { base Logger data map[string]interface{} } func (d *dataLogger) Info(msg string, args ...interface{}) { d.base.Info(msg, args...) } func (d *dataLogger) Warn(msg string, args ...interface{}) { d.base.Warn(msg, args...) } func (d *dataLogger) Error(msg string, args ...interface{}) { d.base.Error(msg, args...) } func (d *dataLogger) Debug(msg string, args ...interface{}) { d.base.Debug(msg, args...) }