add opt-in leveled logging
Add a log package with debug, info, warn, and error levels. It wraps a single process-wide logger that the gumble and audio packages can call into without importing a logging library. Logging is off unless -logfile names a file. Anything written to stderr while the terminal UI is running corrupts the display, so a logger is only installed once an explicit destination exists. -log sets the level and defaults to warn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
30a02eba14
commit
995ee1bffc
+130
@@ -0,0 +1,130 @@
|
|||||||
|
// Package log provides debug logging for the barnard audio pipeline.
|
||||||
|
// Logging is disabled by default and can be enabled via SetLogger.
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level represents logging severity.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LevelDebug Level = iota
|
||||||
|
LevelInfo
|
||||||
|
LevelWarn
|
||||||
|
LevelError
|
||||||
|
)
|
||||||
|
|
||||||
|
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 "???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger receives log messages. The default logger is a no-op.
|
||||||
|
type Logger interface {
|
||||||
|
Log(level Level, format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerState struct {
|
||||||
|
logger Logger
|
||||||
|
level Level
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger atomic.Pointer[loggerState]
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logger.Store(&loggerState{logger: &nopLogger{}, level: LevelError + 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopLogger struct{}
|
||||||
|
|
||||||
|
func (n *nopLogger) Log(level Level, format string, args ...interface{}) {}
|
||||||
|
|
||||||
|
// SetLogger sets the destination for log messages. Pass nil to disable.
|
||||||
|
func SetLogger(l Logger) {
|
||||||
|
state := &loggerState{logger: l, level: LevelDebug}
|
||||||
|
if l == nil {
|
||||||
|
state.logger = &nopLogger{}
|
||||||
|
state.level = LevelError + 1
|
||||||
|
} else if writer, ok := l.(*WriterLogger); ok {
|
||||||
|
state.level = writer.level
|
||||||
|
}
|
||||||
|
logger.Store(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled reports whether messages at level will be emitted. Callers should
|
||||||
|
// use it to avoid computing expensive log arguments when logging is disabled.
|
||||||
|
func Enabled(level Level) bool {
|
||||||
|
return level >= logger.Load().level
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriterLogger is a simple Logger that writes to an io.Writer.
|
||||||
|
type WriterLogger struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
w io.Writer
|
||||||
|
level Level
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriterLogger creates a logger that writes to w, filtering below level.
|
||||||
|
func NewWriterLogger(w io.Writer, level Level) *WriterLogger {
|
||||||
|
if w == nil {
|
||||||
|
w = os.Stderr
|
||||||
|
}
|
||||||
|
return &WriterLogger{w: w, level: level}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *WriterLogger) Log(level Level, format string, args ...interface{}) {
|
||||||
|
if level < wl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wl.mu.Lock()
|
||||||
|
defer wl.mu.Unlock()
|
||||||
|
now := time.Now().Format("15:04:05.000")
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
fmt.Fprintf(wl.w, "%s [%-5s] %s\n", now, level.String(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(format string, args ...interface{}) {
|
||||||
|
state := logger.Load()
|
||||||
|
if LevelDebug >= state.level {
|
||||||
|
state.logger.Log(LevelDebug, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(format string, args ...interface{}) {
|
||||||
|
state := logger.Load()
|
||||||
|
if LevelInfo >= state.level {
|
||||||
|
state.logger.Log(LevelInfo, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(format string, args ...interface{}) {
|
||||||
|
state := logger.Load()
|
||||||
|
if LevelWarn >= state.level {
|
||||||
|
state.logger.Log(LevelWarn, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(format string, args ...interface{}) {
|
||||||
|
state := logger.Load()
|
||||||
|
if LevelError >= state.level {
|
||||||
|
state.logger.Log(LevelError, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
barnlog "git.stormux.org/storm/barnard/log"
|
||||||
|
|
||||||
"git.stormux.org/storm/barnard/config"
|
"git.stormux.org/storm/barnard/config"
|
||||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||||
@@ -114,9 +116,37 @@ func main() {
|
|||||||
buffers := flag.Int("buffers", 16, "number of audio buffers to use")
|
buffers := flag.Int("buffers", 16, "number of audio buffers to use")
|
||||||
profile := flag.Bool("profile", false, "add http server to serve profiles")
|
profile := flag.Bool("profile", false, "add http server to serve profiles")
|
||||||
noiseSuppressionEnabled := flag.Bool("noise-suppression", false, "enable noise suppression for microphone input")
|
noiseSuppressionEnabled := flag.Bool("noise-suppression", false, "enable noise suppression for microphone input")
|
||||||
|
logLevel := flag.String("log", "warn", "log level: debug, info, warn, error")
|
||||||
|
logFile := flag.String("logfile", "", "write logs to this file (logging is disabled when omitted)")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Set up logging
|
||||||
|
var level barnlog.Level
|
||||||
|
switch strings.ToLower(*logLevel) {
|
||||||
|
case "debug":
|
||||||
|
level = barnlog.LevelDebug
|
||||||
|
case "info":
|
||||||
|
level = barnlog.LevelInfo
|
||||||
|
case "warn":
|
||||||
|
level = barnlog.LevelWarn
|
||||||
|
case "error":
|
||||||
|
level = barnlog.LevelError
|
||||||
|
default:
|
||||||
|
level = barnlog.LevelWarn
|
||||||
|
}
|
||||||
|
// Logging is opt-in. Select /dev/stderr explicitly when terminal logging is
|
||||||
|
// desired; otherwise library diagnostics must not corrupt terminal output.
|
||||||
|
barnlog.SetLogger(nil)
|
||||||
|
if *logFile != "" {
|
||||||
|
f, err := os.OpenFile(*logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "cannot open log file %s: %v\n", *logFile, err)
|
||||||
|
} else {
|
||||||
|
barnlog.SetLogger(barnlog.NewWriterLogger(f, level))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if *profile == true {
|
if *profile == true {
|
||||||
go func() {
|
go func() {
|
||||||
log.Println(http.ListenAndServe("localhost:6060", nil))
|
log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||||
|
|||||||
Reference in New Issue
Block a user