338 lines
7.6 KiB
Go
338 lines
7.6 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.Mutex
|
|
output io.Writer
|
|
level Level
|
|
component string
|
|
}
|
|
|
|
// Config holds configuration for creating a new Logger.
|
|
//
|
|
// cfg := Config{Output: io.Discard, Level: LevelDebug, Component: "sync"}
|
|
type Config struct {
|
|
Output io.Writer
|
|
Level Level
|
|
Component string
|
|
}
|
|
|
|
// DefaultConfig returns the default logger configuration.
|
|
//
|
|
// cfg := DefaultConfig()
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
Output: defaultOutput,
|
|
Level: LevelInfo,
|
|
Component: "",
|
|
}
|
|
}
|
|
|
|
// New creates a new Logger with the given configuration.
|
|
//
|
|
// logger := New(DefaultConfig())
|
|
func New(cfg Config) *Logger {
|
|
if cfg.Output == nil {
|
|
cfg.Output = defaultOutput
|
|
}
|
|
return &Logger{
|
|
output: cfg.Output,
|
|
level: cfg.Level,
|
|
component: cfg.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 sets the minimum log level.
|
|
func (l *Logger) SetLevel(level Level) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.level = level
|
|
}
|
|
|
|
// GetLevel returns the current log level.
|
|
func (l *Logger) GetLevel() Level {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
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, msg 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(msg)
|
|
|
|
// 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(msg string, fields ...Fields) {
|
|
l.log(LevelDebug, msg, mergeFields(fields))
|
|
}
|
|
|
|
// Info logs an informational message.
|
|
func (l *Logger) Info(msg string, fields ...Fields) {
|
|
l.log(LevelInfo, msg, mergeFields(fields))
|
|
}
|
|
|
|
// Warn logs a warning message.
|
|
func (l *Logger) Warn(msg string, fields ...Fields) {
|
|
l.log(LevelWarn, msg, mergeFields(fields))
|
|
}
|
|
|
|
// Error logs an error message.
|
|
func (l *Logger) Error(msg string, fields ...Fields) {
|
|
l.log(LevelError, msg, 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 sets the global logger instance.
|
|
//
|
|
// SetGlobal(New(DefaultConfig()))
|
|
func SetGlobal(l *Logger) {
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
globalLogger = l
|
|
}
|
|
|
|
// GetGlobal returns the global logger instance.
|
|
//
|
|
// logger := GetGlobal()
|
|
func GetGlobal() *Logger {
|
|
globalMu.RLock()
|
|
defer globalMu.RUnlock()
|
|
return globalLogger
|
|
}
|
|
|
|
// SetGlobalLevel sets the log level of the global logger.
|
|
//
|
|
// 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(msg string, fields ...Fields) {
|
|
GetGlobal().Debug(msg, fields...)
|
|
}
|
|
|
|
// Info logs an informational message using the global logger.
|
|
//
|
|
// Info("worker started", Fields{"component": "transport"})
|
|
func Info(msg string, fields ...Fields) {
|
|
GetGlobal().Info(msg, fields...)
|
|
}
|
|
|
|
// Warn logs a warning message using the global logger.
|
|
//
|
|
// Warn("peer rate limited", Fields{"peer_id": "node-1"})
|
|
func Warn(msg string, fields ...Fields) {
|
|
GetGlobal().Warn(msg, fields...)
|
|
}
|
|
|
|
// Error logs an error message using the global logger.
|
|
//
|
|
// Error("send failed", Fields{"peer_id": "node-1"})
|
|
func Error(msg string, fields ...Fields) {
|
|
GetGlobal().Error(msg, fields...)
|
|
}
|
|
|
|
// Debugf logs a formatted debug message using the global logger.
|
|
//
|
|
// Debugf("connected peer %s", "node-1")
|
|
func Debugf(format string, args ...any) {
|
|
GetGlobal().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) {
|
|
GetGlobal().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) {
|
|
GetGlobal().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) {
|
|
GetGlobal().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)
|
|
}
|
|
}
|