Extracted from ardenone-cluster/containers/zai-proxy and ardenone-cluster/containers/zai-proxy-dashboard. - proxy/: OpenAI-compatible ZAI reverse proxy (Go, v1.10.0) - Token counting, rate limiting, Prometheus metrics, canary support - dashboard/: Metrics dashboard backend + React frontend (Go, v1.0.0) - Prometheus collector, SQLite storage, SSE live updates - docs/: Operational notes, research, and plan subdirs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
3 KiB
Go
137 lines
3 KiB
Go
// Package logger provides structured JSON logging for the zai-proxy-dashboard.
|
|
package logger
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
defaultLogger *slog.Logger
|
|
once sync.Once
|
|
)
|
|
|
|
// Config holds logger configuration.
|
|
type Config struct {
|
|
// Format: "json" or "text" (default: json)
|
|
Format string
|
|
// Level: "debug", "info", "warn", "error" (default: info)
|
|
Level string
|
|
// AddSource adds source file/line to log entries (default: false)
|
|
AddSource bool
|
|
}
|
|
|
|
// DefaultConfig returns the default logger configuration.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
Format: getEnvOrDefault("LOG_FORMAT", "json"),
|
|
Level: getEnvOrDefault("LOG_LEVEL", "info"),
|
|
AddSource: getEnvOrDefault("LOG_ADD_SOURCE", "false") == "true",
|
|
}
|
|
}
|
|
|
|
func getEnvOrDefault(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
// Init initializes the default logger with the given configuration.
|
|
func Init(cfg Config) {
|
|
once.Do(func() {
|
|
var level slog.Level
|
|
switch cfg.Level {
|
|
case "debug":
|
|
level = slog.LevelDebug
|
|
case "info":
|
|
level = slog.LevelInfo
|
|
case "warn":
|
|
level = slog.LevelWarn
|
|
case "error":
|
|
level = slog.LevelError
|
|
default:
|
|
level = slog.LevelInfo
|
|
}
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: level,
|
|
AddSource: cfg.AddSource,
|
|
}
|
|
|
|
var handler slog.Handler
|
|
if cfg.Format == "text" {
|
|
handler = slog.NewTextHandler(os.Stdout, opts)
|
|
} else {
|
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
|
}
|
|
|
|
defaultLogger = slog.New(handler)
|
|
slog.SetDefault(defaultLogger)
|
|
})
|
|
}
|
|
|
|
// Get returns the default logger.
|
|
func Get() *slog.Logger {
|
|
if defaultLogger == nil {
|
|
Init(DefaultConfig())
|
|
}
|
|
return defaultLogger
|
|
}
|
|
|
|
// Debug logs at debug level.
|
|
func Debug(msg string, args ...any) {
|
|
Get().Debug(msg, args...)
|
|
}
|
|
|
|
// DebugCtx logs at debug level with context.
|
|
func DebugCtx(ctx context.Context, msg string, args ...any) {
|
|
Get().DebugContext(ctx, msg, args...)
|
|
}
|
|
|
|
// Info logs at info level.
|
|
func Info(msg string, args ...any) {
|
|
Get().Info(msg, args...)
|
|
}
|
|
|
|
// InfoCtx logs at info level with context.
|
|
func InfoCtx(ctx context.Context, msg string, args ...any) {
|
|
Get().InfoContext(ctx, msg, args...)
|
|
}
|
|
|
|
// Warn logs at warn level.
|
|
func Warn(msg string, args ...any) {
|
|
Get().Warn(msg, args...)
|
|
}
|
|
|
|
// WarnCtx logs at warn level with context.
|
|
func WarnCtx(ctx context.Context, msg string, args ...any) {
|
|
Get().WarnContext(ctx, msg, args...)
|
|
}
|
|
|
|
// Error logs at error level.
|
|
func Error(msg string, args ...any) {
|
|
Get().Error(msg, args...)
|
|
}
|
|
|
|
// ErrorCtx logs at error level with context.
|
|
func ErrorCtx(ctx context.Context, msg string, args ...any) {
|
|
Get().ErrorContext(ctx, msg, args...)
|
|
}
|
|
|
|
// With returns a logger with additional context.
|
|
func With(args ...any) *slog.Logger {
|
|
return Get().With(args...)
|
|
}
|
|
|
|
// WithGroup returns a logger with a group.
|
|
func WithGroup(name string) *slog.Logger {
|
|
return Get().WithGroup(name)
|
|
}
|
|
|
|
// Component returns a logger with a component label.
|
|
func Component(name string) *slog.Logger {
|
|
return Get().With("component", name)
|
|
}
|