Files
kandev/backend/internal/common/logger/logger.go
T
jcfs ba919305c2 Initial commit: Kandev with SQLite persistence and agent integration
Features:
- Unified Go binary running Task Service, Agent Manager, and Orchestrator
- SQLite persistence for boards, columns, and tasks
- Docker-based agent execution with ACP message streaming
- Augment Agent integration with session resumption support
- RESTful API for all operations (port 8080)
- In-memory event bus for service communication

Architecture:
- cmd/kandev: Single entry point for all services
- internal/task: Task, board, and column management
- internal/agent: Docker container lifecycle and ACP streaming
- internal/orchestrator: Agent coordination and monitoring
- internal/events: In-memory pub/sub event bus

Documentation:
- README.md: Project overview and quick start
- docs/ARCHITECTURE.md: System architecture (current + planned)
- docs/GETTING_STARTED.md: Development setup guide
- docs/IMPLEMENTATION_PLAN.md: Original microservices plan
- backend/NEXT_STEPS.md: Development roadmap
2026-01-10 01:30:24 +00:00

198 lines
5.3 KiB
Go

// Package logger provides structured logging using go.uber.org/zap.
package logger
import (
"context"
"os"
"sync"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Context keys for extracting values from context.
type contextKey string
const (
CorrelationIDKey contextKey = "correlation_id"
RequestIDKey contextKey = "request_id"
)
// LoggingConfig holds the configuration for the logger.
type LoggingConfig struct {
Level string `mapstructure:"level"` // debug, info, warn, error
Format string `mapstructure:"format"` // json, console
OutputPath string `mapstructure:"output_path"` // stdout, stderr, or file path
}
// Logger wraps zap.Logger to provide structured logging with helper methods.
type Logger struct {
zap *zap.Logger
sugar *zap.SugaredLogger
fields []zap.Field
}
var (
defaultLogger *Logger
defaultLoggerOnce sync.Once
)
// Default returns the global default logger.
// This logger is initialized with default settings (info level, json format, stdout).
func Default() *Logger {
defaultLoggerOnce.Do(func() {
var err error
defaultLogger, err = NewLogger(LoggingConfig{
Level: "info",
Format: "json",
OutputPath: "stdout",
})
if err != nil {
// Fallback to a basic logger if configuration fails
zapLogger, _ := zap.NewProduction()
defaultLogger = &Logger{
zap: zapLogger,
sugar: zapLogger.Sugar(),
}
}
})
return defaultLogger
}
// SetDefault sets the global default logger.
func SetDefault(logger *Logger) {
defaultLogger = logger
}
// NewLogger creates a new Logger with the given configuration.
func NewLogger(cfg LoggingConfig) (*Logger, error) {
level, err := parseLevel(cfg.Level)
if err != nil {
level = zapcore.InfoLevel
}
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.TimeKey = "timestamp"
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.LowercaseLevelEncoder
var encoder zapcore.Encoder
if cfg.Format == "console" {
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
encoder = zapcore.NewConsoleEncoder(encoderConfig)
} else {
encoder = zapcore.NewJSONEncoder(encoderConfig)
}
var writeSyncer zapcore.WriteSyncer
switch cfg.OutputPath {
case "", "stdout":
writeSyncer = zapcore.AddSync(os.Stdout)
case "stderr":
writeSyncer = zapcore.AddSync(os.Stderr)
default:
file, err := os.OpenFile(cfg.OutputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
writeSyncer = zapcore.AddSync(file)
}
core := zapcore.NewCore(encoder, writeSyncer, level)
zapLogger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
return &Logger{
zap: zapLogger,
sugar: zapLogger.Sugar(),
}, nil
}
func parseLevel(level string) (zapcore.Level, error) {
var l zapcore.Level
err := l.UnmarshalText([]byte(level))
return l, err
}
// Sync flushes any buffered log entries.
func (l *Logger) Sync() error {
return l.zap.Sync()
}
// WithFields returns a new Logger with the given fields added.
func (l *Logger) WithFields(fields ...zap.Field) *Logger {
return &Logger{
zap: l.zap.With(fields...),
sugar: l.zap.With(fields...).Sugar(),
fields: append(l.fields, fields...),
}
}
// WithContext returns a new Logger with context values (correlation ID, etc.) added.
func (l *Logger) WithContext(ctx context.Context) *Logger {
fields := []zap.Field{}
if correlationID, ok := ctx.Value(CorrelationIDKey).(string); ok && correlationID != "" {
fields = append(fields, zap.String("correlation_id", correlationID))
}
if requestID, ok := ctx.Value(RequestIDKey).(string); ok && requestID != "" {
fields = append(fields, zap.String("request_id", requestID))
}
if len(fields) == 0 {
return l
}
return l.WithFields(fields...)
}
// WithError returns a new Logger with the error field added.
func (l *Logger) WithError(err error) *Logger {
return l.WithFields(zap.Error(err))
}
// WithTaskID returns a new Logger with the task_id field added.
func (l *Logger) WithTaskID(taskID string) *Logger {
return l.WithFields(zap.String("task_id", taskID))
}
// WithAgentID returns a new Logger with the agent_id field added.
func (l *Logger) WithAgentID(agentID string) *Logger {
return l.WithFields(zap.String("agent_id", agentID))
}
// Debug logs a message at debug level with optional structured fields.
func (l *Logger) Debug(msg string, fields ...zap.Field) {
l.zap.Debug(msg, fields...)
}
// Info logs a message at info level with optional structured fields.
func (l *Logger) Info(msg string, fields ...zap.Field) {
l.zap.Info(msg, fields...)
}
// Warn logs a message at warn level with optional structured fields.
func (l *Logger) Warn(msg string, fields ...zap.Field) {
l.zap.Warn(msg, fields...)
}
// Error logs a message at error level with optional structured fields.
func (l *Logger) Error(msg string, fields ...zap.Field) {
l.zap.Error(msg, fields...)
}
// Fatal logs a message at fatal level with optional structured fields,
// then calls os.Exit(1).
func (l *Logger) Fatal(msg string, fields ...zap.Field) {
l.zap.Fatal(msg, fields...)
}
// Zap returns the underlying zap.Logger for advanced use cases.
func (l *Logger) Zap() *zap.Logger {
return l.zap
}
// Sugar returns the underlying zap.SugaredLogger for printf-style logging.
func (l *Logger) Sugar() *zap.SugaredLogger {
return l.sugar
}