go-p2p/logging/logger.go
Virgil ca885ff386
All checks were successful
Security Scan / security (push) Successful in 9s
Test / test (push) Successful in 1m31s
refactor(node): clarify AX filesystem and message names
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-30 20:52:19 +00:00

352 lines
7.8 KiB
Go

// Package logging provides structured logging with log levels and fields.
package logging
import (
"io"
"maps"
"sync"
"syscall"
"time"
core "dappco.re/go/core"
)
// Level represents the severity of a log message.
//
// level := LevelInfo
type Level int
const (
// LevelDebug is the most verbose log level.
LevelDebug Level = iota
// LevelInfo is for general informational messages.
LevelInfo
// LevelWarn is for warning messages.
LevelWarn
// LevelError is for error messages.
LevelError
)
// String returns the string representation of the log level.
func (l Level) String() string {
switch l {
case LevelDebug:
return "DEBUG"
case LevelInfo:
return "INFO"
case LevelWarn:
return "WARN"
case LevelError:
return "ERROR"
default:
return "UNKNOWN"
}
}
// Logger provides structured logging with configurable output and level.
//
// logger := New(DefaultConfig())
type Logger struct {
mu sync.RWMutex
output io.Writer
level Level
component string
}
// Config holds configuration for creating a new Logger.
//
// config := Config{Output: io.Discard, Level: LevelDebug, Component: "sync"}
type Config struct {
Output io.Writer
Level Level
Component string
}
// DefaultConfig returns the default logger configuration.
//
// config := DefaultConfig()
func DefaultConfig() Config {
return Config{
Output: defaultOutput,
Level: LevelInfo,
Component: "",
}
}
// New creates a logger from an explicit configuration.
//
// logger := New(DefaultConfig())
func New(config Config) *Logger {
if config.Output == nil {
config.Output = defaultOutput
}
return &Logger{
output: config.Output,
level: config.Level,
component: config.Component,
}
}
// ComponentLogger returns a new Logger scoped to one component.
//
// transportLogger := logger.ComponentLogger("transport")
func (l *Logger) ComponentLogger(component string) *Logger {
return &Logger{
output: l.output,
level: l.level,
component: component,
}
}
// Deprecated: use ComponentLogger.
func (l *Logger) WithComponent(component string) *Logger {
return l.ComponentLogger(component)
}
// SetLevel changes the minimum log level.
//
// logger.SetLevel(LevelDebug)
func (l *Logger) SetLevel(level Level) {
l.mu.Lock()
defer l.mu.Unlock()
l.level = level
}
// Level returns the current log level.
//
// level := logger.Level()
func (l *Logger) Level() Level {
l.mu.RLock()
defer l.mu.RUnlock()
return l.level
}
// Deprecated: use Level.
func (l *Logger) GetLevel() Level {
return l.Level()
}
// Fields represents key-value pairs for structured logging.
//
// fields := Fields{"peer_id": "node-1", "attempt": 2}
type Fields map[string]any
type stderrWriter struct{}
func (stderrWriter) Write(p []byte) (int, error) {
written, err := syscall.Write(syscall.Stderr, p)
if err != nil {
return written, core.E("logging.stderrWriter.Write", "failed to write log line", err)
}
return written, nil
}
var defaultOutput io.Writer = stderrWriter{}
// log writes a log message at the specified level.
func (l *Logger) log(level Level, message string, fields Fields) {
l.mu.Lock()
defer l.mu.Unlock()
if level < l.level {
return
}
// Build the log line
sb := core.NewBuilder()
timestamp := time.Now().Format("2006/01/02 15:04:05")
sb.WriteString(timestamp)
sb.WriteString(" [")
sb.WriteString(level.String())
sb.WriteString("]")
if l.component != "" {
sb.WriteString(" [")
sb.WriteString(l.component)
sb.WriteString("]")
}
sb.WriteString(" ")
sb.WriteString(message)
// Add fields if present
if len(fields) > 0 {
sb.WriteString(" |")
for k, v := range fields {
sb.WriteString(" ")
sb.WriteString(k)
sb.WriteString("=")
sb.WriteString(core.Sprint(v))
}
}
sb.WriteString("\n")
_, _ = l.output.Write([]byte(sb.String()))
}
// Debug logs a debug message.
func (l *Logger) Debug(message string, fields ...Fields) {
l.log(LevelDebug, message, mergeFields(fields))
}
// Info logs an informational message.
func (l *Logger) Info(message string, fields ...Fields) {
l.log(LevelInfo, message, mergeFields(fields))
}
// Warn logs a warning message.
func (l *Logger) Warn(message string, fields ...Fields) {
l.log(LevelWarn, message, mergeFields(fields))
}
// Error logs an error message.
func (l *Logger) Error(message string, fields ...Fields) {
l.log(LevelError, message, mergeFields(fields))
}
// Debugf logs a formatted debug message.
func (l *Logger) Debugf(format string, args ...any) {
l.log(LevelDebug, core.Sprintf(format, args...), nil)
}
// Infof logs a formatted informational message.
func (l *Logger) Infof(format string, args ...any) {
l.log(LevelInfo, core.Sprintf(format, args...), nil)
}
// Warnf logs a formatted warning message.
func (l *Logger) Warnf(format string, args ...any) {
l.log(LevelWarn, core.Sprintf(format, args...), nil)
}
// Errorf logs a formatted error message.
func (l *Logger) Errorf(format string, args ...any) {
l.log(LevelError, core.Sprintf(format, args...), nil)
}
// mergeFields combines multiple Fields maps into one.
func mergeFields(fields []Fields) Fields {
if len(fields) == 0 {
return nil
}
result := make(Fields)
for _, f := range fields {
maps.Copy(result, f)
}
return result
}
// --- Global logger for convenience ---
var (
globalLogger = New(DefaultConfig())
globalMu sync.RWMutex
)
// SetGlobal installs the global logger instance.
//
// SetGlobal(New(DefaultConfig()))
func SetGlobal(l *Logger) {
globalMu.Lock()
defer globalMu.Unlock()
globalLogger = l
}
// Global returns the global logger instance.
//
// logger := Global()
func Global() *Logger {
globalMu.RLock()
defer globalMu.RUnlock()
return globalLogger
}
// Deprecated: use Global.
func GetGlobal() *Logger {
return Global()
}
// SetGlobalLevel changes the global logger level.
//
// SetGlobalLevel(LevelDebug)
func SetGlobalLevel(level Level) {
globalMu.RLock()
defer globalMu.RUnlock()
globalLogger.SetLevel(level)
}
// Global convenience functions that use the global logger
// Debug logs a debug message using the global logger.
//
// Debug("connected", Fields{"peer_id": "node-1"})
func Debug(message string, fields ...Fields) {
Global().Debug(message, fields...)
}
// Info logs an informational message using the global logger.
//
// Info("worker started", Fields{"component": "transport"})
func Info(message string, fields ...Fields) {
Global().Info(message, fields...)
}
// Warn logs a warning message using the global logger.
//
// Warn("peer rate limited", Fields{"peer_id": "node-1"})
func Warn(message string, fields ...Fields) {
Global().Warn(message, fields...)
}
// Error logs an error message using the global logger.
//
// Error("send failed", Fields{"peer_id": "node-1"})
func Error(message string, fields ...Fields) {
Global().Error(message, fields...)
}
// Debugf logs a formatted debug message using the global logger.
//
// Debugf("connected peer %s", "node-1")
func Debugf(format string, args ...any) {
Global().Debugf(format, args...)
}
// Infof logs a formatted informational message using the global logger.
//
// Infof("worker %s ready", "node-1")
func Infof(format string, args ...any) {
Global().Infof(format, args...)
}
// Warnf logs a formatted warning message using the global logger.
//
// Warnf("peer %s is slow", "node-1")
func Warnf(format string, args ...any) {
Global().Warnf(format, args...)
}
// Errorf logs a formatted error message using the global logger.
//
// Errorf("peer %s failed", "node-1")
func Errorf(format string, args ...any) {
Global().Errorf(format, args...)
}
// ParseLevel parses a string into a log level.
//
// level, err := ParseLevel("warn")
func ParseLevel(s string) (Level, error) {
switch core.Upper(s) {
case "DEBUG":
return LevelDebug, nil
case "INFO":
return LevelInfo, nil
case "WARN", "WARNING":
return LevelWarn, nil
case "ERROR":
return LevelError, nil
default:
return LevelInfo, core.E("logging.ParseLevel", "unknown log level: "+s, nil)
}
}