Mining/pkg/logging/logger.go
Claude 44223f0c67
ax(logging): rename mu to mutex for predictable naming
AX Principle 1: abbreviated field names increase semantic ambiguity.
Logger.mu renamed to Logger.mutex — no comment required to understand it.

Co-Authored-By: Charon <charon@lethean.io>
2026-04-02 14:22:38 +01:00

290 lines
6.9 KiB
Go

// Package logging provides structured logging with log levels and fields.
package logging
import (
"fmt"
"io"
"os"
"strings"
"sync"
"time"
)
// Level represents the severity of a log message.
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.
type Logger struct {
mutex sync.Mutex
output io.Writer
level Level
component string
}
// Config holds configuration for creating a new Logger.
type Config struct {
Output io.Writer
Level Level
Component string
}
// DefaultConfig returns the default logger configuration.
func DefaultConfig() Config {
return Config{
Output: os.Stderr,
Level: LevelInfo,
Component: "",
}
}
// New creates a Logger with the given configuration.
// logger := logging.New(logging.Config{Output: os.Stderr, Level: logging.LevelInfo, Component: "mining"})
func New(cfg Config) *Logger {
if cfg.Output == nil {
cfg.Output = os.Stderr
}
return &Logger{
output: cfg.Output,
level: cfg.Level,
component: cfg.Component,
}
}
// WithComponent returns a child logger scoped to a sub-system.
// child := logger.WithComponent("xmrig")
// child.Info("miner started")
func (l *Logger) WithComponent(component string) *Logger {
return &Logger{
output: l.output,
level: l.level,
component: component,
}
}
// SetLevel adjusts the minimum level for subsequent log calls.
// logger.SetLevel(logging.LevelDebug)
func (l *Logger) SetLevel(level Level) {
l.mutex.Lock()
defer l.mutex.Unlock()
l.level = level
}
// GetLevel returns the current minimum log level.
// current := logger.GetLevel()
// if current == logging.LevelDebug { logger.SetLevel(logging.LevelInfo) }
func (l *Logger) GetLevel() Level {
l.mutex.Lock()
defer l.mutex.Unlock()
return l.level
}
// Fields represents key-value pairs for structured logging.
type Fields map[string]interface{}
// log writes a log message at the specified level.
func (l *Logger) log(level Level, msg string, fields Fields) {
l.mutex.Lock()
defer l.mutex.Unlock()
if level < l.level {
return
}
// Build the log line
var builder strings.Builder
timestamp := time.Now().Format("2006/01/02 15:04:05")
builder.WriteString(timestamp)
builder.WriteString(" [")
builder.WriteString(level.String())
builder.WriteString("]")
if l.component != "" {
builder.WriteString(" [")
builder.WriteString(l.component)
builder.WriteString("]")
}
builder.WriteString(" ")
builder.WriteString(msg)
// Add fields if present
if len(fields) > 0 {
builder.WriteString(" |")
for k, v := range fields {
builder.WriteString(" ")
builder.WriteString(k)
builder.WriteString("=")
builder.WriteString(fmt.Sprintf("%v", v))
}
}
builder.WriteString("\n")
fmt.Fprint(l.output, builder.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 ...interface{}) {
l.log(LevelDebug, fmt.Sprintf(format, args...), nil)
}
// Infof logs a formatted informational message.
func (l *Logger) Infof(format string, args ...interface{}) {
l.log(LevelInfo, fmt.Sprintf(format, args...), nil)
}
// Warnf logs a formatted warning message.
func (l *Logger) Warnf(format string, args ...interface{}) {
l.log(LevelWarn, fmt.Sprintf(format, args...), nil)
}
// Errorf logs a formatted error message.
func (l *Logger) Errorf(format string, args ...interface{}) {
l.log(LevelError, fmt.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 {
for k, v := range f {
result[k] = v
}
}
return result
}
// --- Global logger for convenience ---
var (
globalLogger = New(DefaultConfig())
globalMutex sync.RWMutex
)
// SetGlobal sets the global logger instance.
func SetGlobal(l *Logger) {
globalMutex.Lock()
defer globalMutex.Unlock()
globalLogger = l
}
// GetGlobal returns the global logger instance.
func GetGlobal() *Logger {
globalMutex.RLock()
defer globalMutex.RUnlock()
return globalLogger
}
// SetGlobalLevel sets the log level of the global logger.
func SetGlobalLevel(level Level) {
globalMutex.RLock()
defer globalMutex.RUnlock()
globalLogger.SetLevel(level)
}
// Global convenience functions that use the global logger
// logging.Debug("hashrate collected", logging.Fields{"rate": 1234, "miner": "xmrig"})
func Debug(msg string, fields ...Fields) {
GetGlobal().Debug(msg, fields...)
}
// logging.Info("miner started", logging.Fields{"miner": "xmrig", "pool": "pool.lthn.io"})
func Info(msg string, fields ...Fields) {
GetGlobal().Info(msg, fields...)
}
// logging.Warn("hashrate dropped below threshold", logging.Fields{"current": 500, "min": 1000})
func Warn(msg string, fields ...Fields) {
GetGlobal().Warn(msg, fields...)
}
// logging.Error("miner process exited unexpectedly", logging.Fields{"code": -1})
func Error(msg string, fields ...Fields) {
GetGlobal().Error(msg, fields...)
}
// logging.Debugf("collected %d hashrate points for %s", len(points), minerName)
func Debugf(format string, args ...interface{}) {
GetGlobal().Debugf(format, args...)
}
// logging.Infof("miner %s started on pool %s", minerName, poolURL)
func Infof(format string, args ...interface{}) {
GetGlobal().Infof(format, args...)
}
// logging.Warnf("hashrate %d H/s below minimum %d H/s", current, minimum)
func Warnf(format string, args ...interface{}) {
GetGlobal().Warnf(format, args...)
}
// logging.Errorf("failed to connect to pool %s: %v", poolURL, err)
func Errorf(format string, args ...interface{}) {
GetGlobal().Errorf(format, args...)
}
// ParseLevel parses a string into a log level.
func ParseLevel(s string) (Level, error) {
switch strings.ToUpper(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, fmt.Errorf("unknown log level: %s", s)
}
}