Files
syncthing/internal/slogutil/leveler.go
Jakob Borg 36254473a3 chore(slogutil): add configurable logging format (fixes #10352) (#10354)
This adds several options for configuring the log format of timestamps
and severity levels, making it more suitable for integration with log
systems like systemd.

      --log-format-timestamp="2006-01-02 15:04:05"
         Format for timestamp, set to empty to disable timestamps ($STLOGFORMATTIMESTAMP)

      --[no-]log-format-level-string
         Whether to include level string in log line ($STLOGFORMATLEVELSTRING)

      --[no-]log-format-level-syslog
         Whether to include level as syslog prefix in log line ($STLOGFORMATLEVELSYSLOG)

So, to get a timestamp suitable for systemd (syslog prefix, no level
string, no timestamp) we can pass `--log-format-timestamp=""
--no-log-format-level-string --log-format-level-syslog` or,
equivalently, set `STLOGFORMATTIMESTAMP="" STLOGFORMATLEVELSTRING=false
STLOGFORMATLEVELSYSLOG=true`.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-09-05 10:52:49 +02:00

124 lines
3.0 KiB
Go

// Copyright (C) 2025 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package slogutil
import (
"log/slog"
"maps"
"strings"
"sync"
)
// A levelTracker keeps track of log level per package. This enables the
// traditional STTRACE variable to set certain packages to debug level, but
// also allows setting packages to other levels such as WARN to silence
// INFO-level messages.
//
// The STTRACE environment variable is one way of controlling this, where
// mentioning a package makes it DEBUG level:
// STTRACE="model,protocol" # model and protocol are at DEBUG level
// however you can also give specific levels after a colon:
// STTRACE="model:WARNING,protocol:DEBUG"
func PackageDescrs() map[string]string {
return globalLevels.Descrs()
}
func PackageLevels() map[string]slog.Level {
return globalLevels.Levels()
}
func SetPackageLevel(pkg string, level slog.Level) {
globalLevels.Set(pkg, level)
}
func SetDefaultLevel(level slog.Level) {
globalLevels.SetDefault(level)
}
func SetLevelOverrides(sttrace string) {
pkgs := strings.Split(sttrace, ",")
for _, pkg := range pkgs {
pkg = strings.TrimSpace(pkg)
if pkg == "" {
continue
}
level := slog.LevelDebug
if cutPkg, levelStr, ok := strings.Cut(pkg, ":"); ok {
pkg = cutPkg
if err := level.UnmarshalText([]byte(levelStr)); err != nil {
slog.Warn("Bad log level requested in STTRACE", slog.String("pkg", pkg), slog.String("level", levelStr), Error(err))
}
}
globalLevels.Set(pkg, level)
}
}
type levelTracker struct {
mut sync.RWMutex
defLevel slog.Level
descrs map[string]string // package name to description
levels map[string]slog.Level // package name to level
}
func (t *levelTracker) Get(pkg string) slog.Level {
t.mut.RLock()
defer t.mut.RUnlock()
if level, ok := t.levels[pkg]; ok {
return level
}
return t.defLevel
}
func (t *levelTracker) Set(pkg string, level slog.Level) {
t.mut.Lock()
changed := t.levels[pkg] != level
t.levels[pkg] = level
t.mut.Unlock()
if changed {
slog.Info("Changed package log level", "package", pkg, "level", level)
}
}
func (t *levelTracker) SetDefault(level slog.Level) {
t.mut.Lock()
changed := t.defLevel != level
t.defLevel = level
t.mut.Unlock()
if changed {
slog.Info("Changed default log level", "level", level)
}
}
func (t *levelTracker) SetDescr(pkg, descr string) {
t.mut.Lock()
t.descrs[pkg] = descr
t.mut.Unlock()
}
func (t *levelTracker) Descrs() map[string]string {
t.mut.RLock()
defer t.mut.RUnlock()
m := make(map[string]string, len(t.descrs))
maps.Copy(m, t.descrs)
return m
}
func (t *levelTracker) Levels() map[string]slog.Level {
t.mut.RLock()
defer t.mut.RUnlock()
m := make(map[string]slog.Level, len(t.descrs))
for pkg := range t.descrs {
if level, ok := t.levels[pkg]; ok {
m[pkg] = level
} else {
m[pkg] = t.defLevel
}
}
return m
}