Files
CHORUS/internal/logging/logger.go
anthonyrawlins 7c6cbd562a Initial CHORUS project setup
🎭 CHORUS - Container-First P2P Task Coordination System

- Docker-first architecture designed from ground up
- Environment variable-based configuration (no config files)
- Structured logging to stdout/stderr for container runtimes
- License validation required for operation
- Clean separation from BZZZ legacy systemd approach

Core features implemented:
- Container-optimized logging system
- Environment-based configuration management
- License validation with KACHING integration
- Basic HTTP API and health endpoints
- Docker build and deployment configuration

Ready for P2P protocol development and AI integration.

🤖 Generated with Claude Code
2025-09-02 19:53:33 +10:00

210 lines
4.9 KiB
Go

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...)
}