feat: add structured logging throughout audio pipeline

Add a lightweight log package (log/log.go) with debug/info/warn/error
levels. Wire it up via a -log flag (default: warn). Logging covers:

UDP transport:
- Socket open/failure, CryptSetup receipt, crypto initialization
- First UDP audio send confirmation
- UDP read errors and packet counts
- TCP fallback reason (no socket, waiting for crypto)

Audio pipeline:
- Source (mic) routine start with config details
- Audio stream start/end per remote user
- First audio stream creation for each user
- Packet loss detection with sequence gaps and PLC generation
- Decoder reset on sequence reordering

Usage: barnard -log=debug -server=mumble.example.com
Levels: debug, info, warn, error
This commit is contained in:
Brandon McGinty (deepseek)
2026-08-08 22:19:42 -04:00
committed by Brandon McGinty
parent a218493eb0
commit e483512f74
7 changed files with 198 additions and 5 deletions
+116
View File
@@ -0,0 +1,116 @@
// 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"
"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{})
}
var (
mu sync.Mutex
logger Logger = &nopLogger{}
)
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) {
mu.Lock()
defer mu.Unlock()
if l == nil {
logger = &nopLogger{}
} else {
logger = l
}
}
// 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{}) {
mu.Lock()
l := logger
mu.Unlock()
l.Log(LevelDebug, format, args...)
}
func Info(format string, args ...interface{}) {
mu.Lock()
l := logger
mu.Unlock()
l.Log(LevelInfo, format, args...)
}
func Warn(format string, args ...interface{}) {
mu.Lock()
l := logger
mu.Unlock()
l.Log(LevelWarn, format, args...)
}
func Error(format string, args ...interface{}) {
mu.Lock()
l := logger
mu.Unlock()
l.Log(LevelError, format, args...)
}