Add a 'logging' section to config.yaml supporting:
- output: stderr (default), stdout, or a file path
- level: debug, info, warn, error (default: info)
Implementation:
- New internal/logging package with Setup() for output destination
and Logger struct with level-aware Debug/Info/Warn/Error methods
- Config struct extended with Logging section (output + level fields)
- Hub refactored to accept *logging.Logger via constructor injection
- main.go initializes logging early after config load
The leveled logger suppresses messages below the configured threshold
while maintaining the stdlib log format. File output uses append mode
with 0644 permissions for safe log rotation.
🤖 Assisted by the code-assist SOP
127 lines
2.8 KiB
Go
127 lines
2.8 KiB
Go
package logging
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Level represents the severity of a log message.
|
|
type Level int
|
|
|
|
const (
|
|
LevelDebug Level = iota
|
|
LevelInfo
|
|
LevelWarn
|
|
LevelError
|
|
)
|
|
|
|
// Logger provides level-aware logging.
|
|
type Logger struct {
|
|
level Level
|
|
logger *log.Logger
|
|
}
|
|
|
|
// Setup configures the global log output destination.
|
|
// Returns the opened file (if a file path was given) so the caller can defer Close.
|
|
// For "stdout", "stderr", or empty string, returns nil (no file to close).
|
|
func Setup(output string) (*os.File, error) {
|
|
switch strings.ToLower(output) {
|
|
case "", "stderr":
|
|
log.SetOutput(os.Stderr)
|
|
return nil, nil
|
|
case "stdout":
|
|
log.SetOutput(os.Stdout)
|
|
return nil, nil
|
|
default:
|
|
file, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open log file %s: %w", output, err)
|
|
}
|
|
log.SetOutput(file)
|
|
return file, nil
|
|
}
|
|
}
|
|
|
|
// NewLogger creates a new Logger with the given level and output writer.
|
|
// If level is empty or invalid, defaults to LevelInfo.
|
|
func NewLogger(level string, output io.Writer) *Logger {
|
|
return &Logger{
|
|
level: parseLevel(level),
|
|
logger: log.New(output, "", log.LstdFlags),
|
|
}
|
|
}
|
|
|
|
// Debug logs a message at debug level.
|
|
func (l *Logger) Debug(msg string) {
|
|
if l.level <= LevelDebug {
|
|
l.logger.Printf("[DEBUG] %s", msg)
|
|
}
|
|
}
|
|
|
|
// Debugf logs a formatted message at debug level.
|
|
func (l *Logger) Debugf(format string, args ...interface{}) {
|
|
if l.level <= LevelDebug {
|
|
l.logger.Printf("[DEBUG] "+format, args...)
|
|
}
|
|
}
|
|
|
|
// Info logs a message at info level.
|
|
func (l *Logger) Info(msg string) {
|
|
if l.level <= LevelInfo {
|
|
l.logger.Printf("[INFO] %s", msg)
|
|
}
|
|
}
|
|
|
|
// Infof logs a formatted message at info level.
|
|
func (l *Logger) Infof(format string, args ...interface{}) {
|
|
if l.level <= LevelInfo {
|
|
l.logger.Printf("[INFO] "+format, args...)
|
|
}
|
|
}
|
|
|
|
// Warn logs a message at warn level.
|
|
func (l *Logger) Warn(msg string) {
|
|
if l.level <= LevelWarn {
|
|
l.logger.Printf("[WARN] %s", msg)
|
|
}
|
|
}
|
|
|
|
// Warnf logs a formatted message at warn level.
|
|
func (l *Logger) Warnf(format string, args ...interface{}) {
|
|
if l.level <= LevelWarn {
|
|
l.logger.Printf("[WARN] "+format, args...)
|
|
}
|
|
}
|
|
|
|
// Error logs a message at error level.
|
|
func (l *Logger) Error(msg string) {
|
|
if l.level <= LevelError {
|
|
l.logger.Printf("[ERROR] %s", msg)
|
|
}
|
|
}
|
|
|
|
// Errorf logs a formatted message at error level.
|
|
func (l *Logger) Errorf(format string, args ...interface{}) {
|
|
if l.level <= LevelError {
|
|
l.logger.Printf("[ERROR] "+format, args...)
|
|
}
|
|
}
|
|
|
|
func parseLevel(level string) Level {
|
|
switch strings.ToLower(level) {
|
|
case "debug":
|
|
return LevelDebug
|
|
case "info":
|
|
return LevelInfo
|
|
case "warn":
|
|
return LevelWarn
|
|
case "error":
|
|
return LevelError
|
|
default:
|
|
return LevelInfo
|
|
}
|
|
}
|