leveler.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package slogutil
  7. import (
  8. "log/slog"
  9. "maps"
  10. "strings"
  11. "sync"
  12. )
  13. // A levelTracker keeps track of log level per package. This enables the
  14. // traditional STTRACE variable to set certain packages to debug level, but
  15. // also allows setting packages to other levels such as WARN to silence
  16. // INFO-level messages.
  17. //
  18. // The STTRACE environment variable is one way of controlling this, where
  19. // mentioning a package makes it DEBUG level:
  20. // STTRACE="model,protocol" # model and protocol are at DEBUG level
  21. // however you can also give specific levels after a colon:
  22. // STTRACE="model:WARNING,protocol:DEBUG"
  23. func PackageDescrs() map[string]string {
  24. return globalLevels.Descrs()
  25. }
  26. func PackageLevels() map[string]slog.Level {
  27. return globalLevels.Levels()
  28. }
  29. func SetPackageLevel(pkg string, level slog.Level) {
  30. globalLevels.Set(pkg, level)
  31. }
  32. func SetDefaultLevel(level slog.Level) {
  33. globalLevels.SetDefault(level)
  34. }
  35. func SetLevelOverrides(sttrace string) {
  36. pkgs := strings.Split(sttrace, ",")
  37. for _, pkg := range pkgs {
  38. pkg = strings.TrimSpace(pkg)
  39. if pkg == "" {
  40. continue
  41. }
  42. level := slog.LevelDebug
  43. if cutPkg, levelStr, ok := strings.Cut(pkg, ":"); ok {
  44. pkg = cutPkg
  45. if err := level.UnmarshalText([]byte(levelStr)); err != nil {
  46. slog.Warn("Bad log level requested in STTRACE", slog.String("pkg", pkg), slog.String("level", levelStr), Error(err))
  47. }
  48. }
  49. globalLevels.Set(pkg, level)
  50. }
  51. }
  52. type levelTracker struct {
  53. mut sync.RWMutex
  54. defLevel slog.Level
  55. descrs map[string]string // package name to description
  56. levels map[string]slog.Level // package name to level
  57. }
  58. func (t *levelTracker) Get(pkg string) slog.Level {
  59. t.mut.RLock()
  60. defer t.mut.RUnlock()
  61. if level, ok := t.levels[pkg]; ok {
  62. return level
  63. }
  64. return t.defLevel
  65. }
  66. func (t *levelTracker) Set(pkg string, level slog.Level) {
  67. t.mut.Lock()
  68. changed := t.levels[pkg] != level
  69. t.levels[pkg] = level
  70. t.mut.Unlock()
  71. if changed {
  72. slog.Info("Changed package log level", "package", pkg, "level", level)
  73. }
  74. }
  75. func (t *levelTracker) SetDefault(level slog.Level) {
  76. t.mut.Lock()
  77. changed := t.defLevel != level
  78. t.defLevel = level
  79. t.mut.Unlock()
  80. if changed {
  81. slog.Info("Changed default log level", "level", level)
  82. }
  83. }
  84. func (t *levelTracker) SetDescr(pkg, descr string) {
  85. t.mut.Lock()
  86. t.descrs[pkg] = descr
  87. t.mut.Unlock()
  88. }
  89. func (t *levelTracker) Descrs() map[string]string {
  90. t.mut.RLock()
  91. defer t.mut.RUnlock()
  92. m := make(map[string]string, len(t.descrs))
  93. maps.Copy(m, t.descrs)
  94. return m
  95. }
  96. func (t *levelTracker) Levels() map[string]slog.Level {
  97. t.mut.RLock()
  98. defer t.mut.RUnlock()
  99. m := make(map[string]slog.Level, len(t.descrs))
  100. for pkg := range t.descrs {
  101. if level, ok := t.levels[pkg]; ok {
  102. m[pkg] = level
  103. } else {
  104. m[pkg] = t.defLevel
  105. }
  106. }
  107. return m
  108. }