Files

171 lines
3.1 KiB
Go

// Package applog is the process-wide logger for HA Emby MCP.
package applog
import (
"log"
"os"
"strings"
"sync"
)
type Level int
const (
LevelDebug Level = iota
LevelInfo
LevelWarn
)
type Settings struct {
Level Level
DebugREST bool
DebugMCP bool
DebugEmby bool
}
var (
mu sync.RWMutex
cur = Settings{Level: LevelInfo}
std = log.New(os.Stderr, "", log.LstdFlags)
)
func Configure(s Settings) {
mu.Lock()
cur = s
mu.Unlock()
Infof("log level=%s debug[rest=%v mcp=%v emby=%v]", s.Level, s.DebugREST, s.DebugMCP, s.DebugEmby)
}
func Current() Settings {
mu.RLock()
defer mu.RUnlock()
return cur
}
func ParseLevel(s string) Level {
switch strings.ToUpper(strings.TrimSpace(s)) {
case "DEBUG":
return LevelDebug
case "WARN", "WARNING":
return LevelWarn
default:
return LevelInfo
}
}
func (l Level) String() string {
switch l {
case LevelDebug:
return "DEBUG"
case LevelWarn:
return "WARN"
default:
return "INFO"
}
}
func enabled(min Level) bool {
return Current().Level <= min
}
func Infof(format string, args ...any) {
if enabled(LevelInfo) {
std.Printf("INFO "+format, args...)
}
}
func Warnf(format string, args ...any) {
if enabled(LevelWarn) {
std.Printf("WARN "+format, args...)
}
}
func Debugf(format string, args ...any) {
if enabled(LevelDebug) {
std.Printf("DEBUG "+format, args...)
}
}
func REST(format string, args ...any) {
s := Current()
if s.Level == LevelDebug && s.DebugREST {
std.Printf("DEBUG rest "+format, args...)
}
}
func MCP(format string, args ...any) {
s := Current()
if s.Level == LevelDebug && s.DebugMCP {
std.Printf("DEBUG mcp "+format, args...)
}
}
func Emby(format string, args ...any) {
s := Current()
if s.Level == LevelDebug && s.DebugEmby {
std.Printf("DEBUG emby "+format, args...)
}
}
// Body returns a log-safe body: truncated and with secrets stripped.
func Body(b []byte) string {
s := string(b)
s = redact(s)
const max = 8192
if len(s) > max {
return s[:max] + "…(truncated)"
}
return s
}
func redact(s string) string {
// crude redaction of common secret fields
lower := strings.ToLower(s)
if strings.Contains(lower, "\"pw\"") || strings.Contains(lower, "password") || strings.Contains(lower, "accesstoken") {
s = replaceJSONField(s, "Pw")
s = replaceJSONField(s, "pw")
s = replaceJSONField(s, "Password")
s = replaceJSONField(s, "password")
s = replaceJSONField(s, "AccessToken")
s = replaceJSONField(s, "accessToken")
}
return s
}
func replaceJSONField(s, key string) string {
// "key":"value" → "key":"***"
needle := `"` + key + `"`
out := s
for {
i := strings.Index(out, needle)
if i < 0 {
return out
}
rest := out[i+len(needle):]
colon := strings.Index(rest, ":")
if colon < 0 {
return out
}
rest = rest[colon+1:]
j := 0
for j < len(rest) && (rest[j] == ' ' || rest[j] == '\t' || rest[j] == '\n') {
j++
}
if j >= len(rest) || rest[j] != '"' {
return out
}
k := j + 1
for k < len(rest) && rest[k] != '"' {
if rest[k] == '\\' && k+1 < len(rest) {
k += 2
continue
}
k++
}
if k >= len(rest) {
return out
}
out = out[:i+len(needle)] + rest[:j] + `"***"` + rest[k+1:]
}
}