logger.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
  2. // is governed by an MIT-style license that can be found in the LICENSE file.
  3. //go:generate -command counterfeiter go run github.com/maxbrunsfeld/counterfeiter/v6
  4. //go:generate counterfeiter -o mocks/logger.go --fake-name Recorder . Recorder
  5. // Package logger implements a standardized logger with callback functionality
  6. package logger
  7. import (
  8. "fmt"
  9. "io"
  10. "log"
  11. "os"
  12. "slices"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. // This package uses stdlib sync as it may be used to debug syncthing/lib/sync
  18. // and that would cause an implosion of the universe.
  19. type LogLevel int
  20. const (
  21. LevelDebug LogLevel = iota
  22. LevelVerbose
  23. LevelInfo
  24. LevelWarn
  25. NumLevels
  26. )
  27. const (
  28. DefaultFlags = log.Ltime | log.Ldate
  29. DebugFlags = log.Ltime | log.Ldate | log.Lmicroseconds | log.Lshortfile
  30. )
  31. // A MessageHandler is called with the log level and message text.
  32. type MessageHandler func(l LogLevel, msg string)
  33. type Logger interface {
  34. AddHandler(level LogLevel, h MessageHandler)
  35. SetFlags(flag int)
  36. SetPrefix(prefix string)
  37. Debugln(vals ...interface{})
  38. Debugf(format string, vals ...interface{})
  39. Verboseln(vals ...interface{})
  40. Verbosef(format string, vals ...interface{})
  41. Infoln(vals ...interface{})
  42. Infof(format string, vals ...interface{})
  43. Warnln(vals ...interface{})
  44. Warnf(format string, vals ...interface{})
  45. ShouldDebug(facility string) bool
  46. SetDebug(facility string, enabled bool)
  47. Facilities() map[string]string
  48. FacilityDebugging() []string
  49. NewFacility(facility, description string) Logger
  50. }
  51. type logger struct {
  52. logger *log.Logger
  53. handlers [NumLevels][]MessageHandler
  54. facilities map[string]string // facility name => description
  55. debug map[string]struct{} // only facility names with debugging enabled
  56. traces []string
  57. mut sync.Mutex
  58. }
  59. // DefaultLogger logs to standard output with a time prefix.
  60. var DefaultLogger = New()
  61. func New() Logger {
  62. if os.Getenv("LOGGER_DISCARD") != "" {
  63. // Hack to completely disable logging, for example when running
  64. // benchmarks.
  65. return newLogger(io.Discard)
  66. }
  67. return newLogger(controlStripper{os.Stdout})
  68. }
  69. func newLogger(w io.Writer) Logger {
  70. traces := strings.FieldsFunc(os.Getenv("STTRACE"), func(r rune) bool {
  71. return strings.ContainsRune(",; ", r)
  72. })
  73. if len(traces) > 0 {
  74. if slices.Contains(traces, "all") {
  75. traces = []string{"all"}
  76. } else {
  77. slices.Sort(traces)
  78. }
  79. }
  80. return &logger{
  81. logger: log.New(w, "", DefaultFlags),
  82. traces: traces,
  83. facilities: make(map[string]string),
  84. debug: make(map[string]struct{}),
  85. }
  86. }
  87. // AddHandler registers a new MessageHandler to receive messages with the
  88. // specified log level or above.
  89. func (l *logger) AddHandler(level LogLevel, h MessageHandler) {
  90. l.mut.Lock()
  91. defer l.mut.Unlock()
  92. l.handlers[level] = append(l.handlers[level], h)
  93. }
  94. // See log.SetFlags
  95. func (l *logger) SetFlags(flag int) {
  96. l.logger.SetFlags(flag)
  97. }
  98. // See log.SetPrefix
  99. func (l *logger) SetPrefix(prefix string) {
  100. l.logger.SetPrefix(prefix)
  101. }
  102. func (l *logger) callHandlers(level LogLevel, s string) {
  103. for ll := LevelDebug; ll <= level; ll++ {
  104. for _, h := range l.handlers[ll] {
  105. h(level, strings.TrimSpace(s))
  106. }
  107. }
  108. }
  109. // Debugln logs a line with a DEBUG prefix.
  110. func (l *logger) Debugln(vals ...interface{}) {
  111. l.debugln(3, vals...)
  112. }
  113. func (l *logger) debugln(level int, vals ...interface{}) {
  114. s := fmt.Sprintln(vals...)
  115. l.mut.Lock()
  116. defer l.mut.Unlock()
  117. l.logger.Output(level, "DEBUG: "+s)
  118. l.callHandlers(LevelDebug, s)
  119. }
  120. // Debugf logs a formatted line with a DEBUG prefix.
  121. func (l *logger) Debugf(format string, vals ...interface{}) {
  122. l.debugf(3, format, vals...)
  123. }
  124. func (l *logger) debugf(level int, format string, vals ...interface{}) {
  125. s := fmt.Sprintf(format, vals...)
  126. l.mut.Lock()
  127. defer l.mut.Unlock()
  128. l.logger.Output(level, "DEBUG: "+s)
  129. l.callHandlers(LevelDebug, s)
  130. }
  131. // Infoln logs a line with a VERBOSE prefix.
  132. func (l *logger) Verboseln(vals ...interface{}) {
  133. s := fmt.Sprintln(vals...)
  134. l.mut.Lock()
  135. defer l.mut.Unlock()
  136. l.logger.Output(2, "VERBOSE: "+s)
  137. l.callHandlers(LevelVerbose, s)
  138. }
  139. // Infof logs a formatted line with a VERBOSE prefix.
  140. func (l *logger) Verbosef(format string, vals ...interface{}) {
  141. s := fmt.Sprintf(format, vals...)
  142. l.mut.Lock()
  143. defer l.mut.Unlock()
  144. l.logger.Output(2, "VERBOSE: "+s)
  145. l.callHandlers(LevelVerbose, s)
  146. }
  147. // Infoln logs a line with an INFO prefix.
  148. func (l *logger) Infoln(vals ...interface{}) {
  149. s := fmt.Sprintln(vals...)
  150. l.mut.Lock()
  151. defer l.mut.Unlock()
  152. l.logger.Output(2, "INFO: "+s)
  153. l.callHandlers(LevelInfo, s)
  154. }
  155. // Infof logs a formatted line with an INFO prefix.
  156. func (l *logger) Infof(format string, vals ...interface{}) {
  157. s := fmt.Sprintf(format, vals...)
  158. l.mut.Lock()
  159. defer l.mut.Unlock()
  160. l.logger.Output(2, "INFO: "+s)
  161. l.callHandlers(LevelInfo, s)
  162. }
  163. // Warnln logs a formatted line with a WARNING prefix.
  164. func (l *logger) Warnln(vals ...interface{}) {
  165. s := fmt.Sprintln(vals...)
  166. l.mut.Lock()
  167. defer l.mut.Unlock()
  168. l.logger.Output(2, "WARNING: "+s)
  169. l.callHandlers(LevelWarn, s)
  170. }
  171. // Warnf logs a formatted line with a WARNING prefix.
  172. func (l *logger) Warnf(format string, vals ...interface{}) {
  173. s := fmt.Sprintf(format, vals...)
  174. l.mut.Lock()
  175. defer l.mut.Unlock()
  176. l.logger.Output(2, "WARNING: "+s)
  177. l.callHandlers(LevelWarn, s)
  178. }
  179. // ShouldDebug returns true if the given facility has debugging enabled.
  180. func (l *logger) ShouldDebug(facility string) bool {
  181. l.mut.Lock()
  182. _, res := l.debug[facility]
  183. l.mut.Unlock()
  184. return res
  185. }
  186. // SetDebug enabled or disables debugging for the given facility name.
  187. func (l *logger) SetDebug(facility string, enabled bool) {
  188. l.mut.Lock()
  189. defer l.mut.Unlock()
  190. if _, ok := l.debug[facility]; enabled && !ok {
  191. l.SetFlags(DebugFlags)
  192. l.debug[facility] = struct{}{}
  193. } else if !enabled && ok {
  194. delete(l.debug, facility)
  195. if len(l.debug) == 0 {
  196. l.SetFlags(DefaultFlags)
  197. }
  198. }
  199. }
  200. // isTraced returns whether the facility name is contained in STTRACE.
  201. func (l *logger) isTraced(facility string) bool {
  202. if len(l.traces) > 0 {
  203. if l.traces[0] == "all" {
  204. return true
  205. }
  206. _, found := slices.BinarySearch(l.traces, facility)
  207. return found
  208. }
  209. return false
  210. }
  211. // FacilityDebugging returns the set of facilities that have debugging
  212. // enabled.
  213. func (l *logger) FacilityDebugging() []string {
  214. enabled := make([]string, 0, len(l.debug))
  215. l.mut.Lock()
  216. for facility := range l.debug {
  217. enabled = append(enabled, facility)
  218. }
  219. l.mut.Unlock()
  220. return enabled
  221. }
  222. // Facilities returns the currently known set of facilities and their
  223. // descriptions.
  224. func (l *logger) Facilities() map[string]string {
  225. l.mut.Lock()
  226. res := make(map[string]string, len(l.facilities))
  227. for facility, descr := range l.facilities {
  228. res[facility] = descr
  229. }
  230. l.mut.Unlock()
  231. return res
  232. }
  233. // NewFacility returns a new logger bound to the named facility.
  234. func (l *logger) NewFacility(facility, description string) Logger {
  235. l.SetDebug(facility, l.isTraced(facility))
  236. l.mut.Lock()
  237. l.facilities[facility] = description
  238. l.mut.Unlock()
  239. return &facilityLogger{
  240. logger: l,
  241. facility: facility,
  242. }
  243. }
  244. // A facilityLogger is a regular logger but bound to a facility name. The
  245. // Debugln and Debugf methods are no-ops unless debugging has been enabled for
  246. // this facility on the parent logger.
  247. type facilityLogger struct {
  248. *logger
  249. facility string
  250. }
  251. // Debugln logs a line with a DEBUG prefix.
  252. func (l *facilityLogger) Debugln(vals ...interface{}) {
  253. if !l.ShouldDebug(l.facility) {
  254. return
  255. }
  256. l.logger.debugln(3, vals...)
  257. }
  258. // Debugf logs a formatted line with a DEBUG prefix.
  259. func (l *facilityLogger) Debugf(format string, vals ...interface{}) {
  260. if !l.ShouldDebug(l.facility) {
  261. return
  262. }
  263. l.logger.debugf(3, format, vals...)
  264. }
  265. // A Recorder keeps a size limited record of log events.
  266. type Recorder interface {
  267. Since(t time.Time) []Line
  268. Clear()
  269. }
  270. type recorder struct {
  271. lines []Line
  272. initial int
  273. mut sync.Mutex
  274. }
  275. // A Line represents a single log entry.
  276. type Line struct {
  277. When time.Time `json:"when"`
  278. Message string `json:"message"`
  279. Level LogLevel `json:"level"`
  280. }
  281. func NewRecorder(l Logger, level LogLevel, size, initial int) Recorder {
  282. r := &recorder{
  283. lines: make([]Line, 0, size),
  284. initial: initial,
  285. }
  286. l.AddHandler(level, r.append)
  287. return r
  288. }
  289. func (r *recorder) Since(t time.Time) []Line {
  290. r.mut.Lock()
  291. defer r.mut.Unlock()
  292. res := r.lines
  293. for i := 0; i < len(res); i++ {
  294. if res[i].When.After(t) {
  295. // We must copy the result as r.lines can be mutated as soon as the lock
  296. // is released.
  297. res = res[i:]
  298. cp := make([]Line, len(res))
  299. copy(cp, res)
  300. return cp
  301. }
  302. }
  303. return nil
  304. }
  305. func (r *recorder) Clear() {
  306. r.mut.Lock()
  307. r.lines = r.lines[:0]
  308. r.mut.Unlock()
  309. }
  310. func (r *recorder) append(l LogLevel, msg string) {
  311. line := Line{
  312. When: time.Now(), // intentionally high precision
  313. Message: msg,
  314. Level: l,
  315. }
  316. r.mut.Lock()
  317. defer r.mut.Unlock()
  318. if len(r.lines) == cap(r.lines) {
  319. if r.initial > 0 {
  320. // Shift all lines one step to the left, keeping the "initial" first intact.
  321. copy(r.lines[r.initial+1:], r.lines[r.initial+2:])
  322. } else {
  323. copy(r.lines, r.lines[1:])
  324. }
  325. // Add the new one at the end
  326. r.lines[len(r.lines)-1] = line
  327. return
  328. }
  329. r.lines = append(r.lines, line)
  330. if len(r.lines) == r.initial {
  331. r.lines = append(r.lines, Line{time.Now(), "...", l})
  332. }
  333. }
  334. // controlStripper is a Writer that replaces control characters
  335. // with spaces.
  336. type controlStripper struct {
  337. io.Writer
  338. }
  339. func (s controlStripper) Write(data []byte) (int, error) {
  340. for i, b := range data {
  341. if b == '\n' || b == '\r' {
  342. // Newlines are OK
  343. continue
  344. }
  345. if b < 32 {
  346. // Characters below 32 are control characters
  347. data[i] = ' '
  348. }
  349. }
  350. return s.Writer.Write(data)
  351. }