logger.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package logger defines a type for writing to logs. It's just a
  4. // convenience type so that we don't have to pass verbose func(...)
  5. // types around.
  6. package logger
  7. import (
  8. "bufio"
  9. "bytes"
  10. "container/list"
  11. "encoding/json"
  12. "fmt"
  13. "io"
  14. "log"
  15. "strings"
  16. "sync"
  17. "time"
  18. "context"
  19. "tailscale.com/envknob"
  20. )
  21. // Logf is the basic Tailscale logger type: a printf-like func.
  22. // Like log.Printf, the format need not end in a newline.
  23. // Logf functions must be safe for concurrent use.
  24. type Logf func(format string, args ...any)
  25. // A Context is a context.Context that should contain a custom log function, obtainable from FromContext.
  26. // If no log function is present, FromContext will return log.Printf.
  27. // To construct a Context, use Add
  28. type Context context.Context
  29. type logfKey struct{}
  30. // jenc is a json.Encode + bytes.Buffer pair wired up to be reused in a pool.
  31. type jenc struct {
  32. buf bytes.Buffer
  33. enc *json.Encoder
  34. }
  35. var jencPool = &sync.Pool{New: func() any {
  36. je := new(jenc)
  37. je.enc = json.NewEncoder(&je.buf)
  38. return je
  39. }}
  40. // JSON marshals v as JSON and writes it to logf formatted with the annotation to make logtail
  41. // treat it as a structured log.
  42. //
  43. // The recType is the record type and becomes the key of the wrapper
  44. // JSON object that is logged. That is, if recType is "foo" and v is
  45. // 123, the value logged is {"foo":123}.
  46. //
  47. // Do not use recType "logtail", "v", "text", or "metrics", with any case.
  48. // Those are reserved for the logging system.
  49. //
  50. // The level can be from 0 to 9. Levels from 1 to 9 are included in
  51. // the logged JSON object, like {"foo":123,"v":2}.
  52. func (logf Logf) JSON(level int, recType string, v any) {
  53. je := jencPool.Get().(*jenc)
  54. defer jencPool.Put(je)
  55. je.buf.Reset()
  56. je.buf.WriteByte('{')
  57. je.enc.Encode(recType)
  58. je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode
  59. je.buf.WriteByte(':')
  60. if err := je.enc.Encode(v); err != nil {
  61. logf("[unexpected]: failed to encode structured JSON log record of type %q / %T: %v", recType, v, err)
  62. return
  63. }
  64. je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode
  65. je.buf.WriteByte('}')
  66. // Magic prefix recognized by logtail:
  67. logf("[v\x00JSON]%d%s", level%10, je.buf.Bytes())
  68. }
  69. // FromContext extracts a log function from ctx.
  70. func FromContext(ctx Context) Logf {
  71. v := ctx.Value(logfKey{})
  72. if v == nil {
  73. return log.Printf
  74. }
  75. return v.(Logf)
  76. }
  77. // Ctx constructs a Context from ctx with fn as its custom log function.
  78. func Ctx(ctx context.Context, fn Logf) Context {
  79. return context.WithValue(ctx, logfKey{}, fn)
  80. }
  81. // WithPrefix wraps f, prefixing each format with the provided prefix.
  82. func WithPrefix(f Logf, prefix string) Logf {
  83. return func(format string, args ...any) {
  84. f(prefix+format, args...)
  85. }
  86. }
  87. // FuncWriter returns an io.Writer that writes to f.
  88. func FuncWriter(f Logf) io.Writer {
  89. return funcWriter{f}
  90. }
  91. // StdLogger returns a standard library logger from a Logf.
  92. func StdLogger(f Logf) *log.Logger {
  93. return log.New(FuncWriter(f), "", 0)
  94. }
  95. type funcWriter struct{ f Logf }
  96. func (w funcWriter) Write(p []byte) (int, error) {
  97. w.f("%s", p)
  98. return len(p), nil
  99. }
  100. // Discard is a Logf that throws away the logs given to it.
  101. func Discard(string, ...any) {}
  102. // limitData is used to keep track of each format string's associated
  103. // rate-limiting data.
  104. type limitData struct {
  105. bucket *tokenBucket // the token bucket associated with this string
  106. nBlocked int // number of messages skipped
  107. ele *list.Element // list element used to access this string in the cache
  108. }
  109. // rateFree are format string substrings that are exempt from rate limiting.
  110. // Things should not be added to this unless they're already limited otherwise
  111. // or are critical for generating important stats from the logs.
  112. var rateFree = []string{
  113. "magicsock: disco: ",
  114. "magicsock: ParseEndpoint:",
  115. // grinder stats lines
  116. "SetPrefs: %v",
  117. "peer keys: %s",
  118. "v%v peers: %v",
  119. // debug messages printed by 'tailscale bugreport'
  120. "diag: ",
  121. }
  122. // RateLimitedFn is a wrapper for RateLimitedFnWithClock that includes the
  123. // current time automatically. This is mainly for backward compatibility.
  124. func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
  125. return RateLimitedFnWithClock(logf, f, burst, maxCache, time.Now)
  126. }
  127. // RateLimitedFnWithClock returns a rate-limiting Logf wrapping the given
  128. // logf. Messages are allowed through at a maximum of one message every f
  129. // (where f is a time.Duration), in bursts of up to burst messages at a
  130. // time. Up to maxCache format strings will be tracked separately.
  131. // timeNow is a function that returns the current time, used for calculating
  132. // rate limits.
  133. func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int, timeNow func() time.Time) Logf {
  134. if envknob.String("TS_DEBUG_LOG_RATE") == "all" {
  135. return logf
  136. }
  137. var (
  138. mu sync.Mutex
  139. msgLim = make(map[string]*limitData) // keyed by logf format
  140. msgCache = list.New() // a rudimentary LRU that limits the size of the map
  141. )
  142. return func(format string, args ...any) {
  143. // Shortcut for formats with no rate limit
  144. for _, sub := range rateFree {
  145. if strings.Contains(format, sub) {
  146. logf(format, args...)
  147. return
  148. }
  149. }
  150. mu.Lock()
  151. rl, ok := msgLim[format]
  152. if ok {
  153. msgCache.MoveToFront(rl.ele)
  154. } else {
  155. rl = &limitData{
  156. bucket: newTokenBucket(f, burst, timeNow()),
  157. ele: msgCache.PushFront(format),
  158. }
  159. msgLim[format] = rl
  160. if msgCache.Len() > maxCache {
  161. delete(msgLim, msgCache.Back().Value.(string))
  162. msgCache.Remove(msgCache.Back())
  163. }
  164. }
  165. rl.bucket.AdvanceTo(timeNow())
  166. // Make sure there's enough room for at least a few
  167. // more logs before we unblock, so we don't alternate
  168. // between blocking and unblocking.
  169. if rl.nBlocked > 0 && rl.bucket.remaining >= 2 {
  170. // Only print this if we dropped more than 1
  171. // message. Otherwise we'd *increase* the total
  172. // number of log lines printed.
  173. if rl.nBlocked > 1 {
  174. logf("[RATELIMIT] format(%q) (%d dropped)",
  175. format, rl.nBlocked-1)
  176. }
  177. rl.nBlocked = 0
  178. }
  179. if rl.nBlocked == 0 && rl.bucket.Get() {
  180. hitLimit := rl.bucket.remaining == 0
  181. if hitLimit {
  182. // Enter "blocked" mode immediately after
  183. // reaching the burst limit. We want to
  184. // always accompany the format() message
  185. // with an example of the format, which is
  186. // effectively the same as printing the
  187. // message anyway. But this way they can
  188. // be on two separate lines and we don't
  189. // corrupt the original message.
  190. rl.nBlocked = 1
  191. }
  192. mu.Unlock() // release before calling logf
  193. logf(format, args...)
  194. if hitLimit {
  195. logf("[RATELIMIT] format(%q)", format)
  196. }
  197. } else {
  198. rl.nBlocked++
  199. mu.Unlock()
  200. }
  201. }
  202. }
  203. // SlowLoggerWithClock is a logger that applies rate limits similar to
  204. // RateLimitedFnWithClock, but instead of dropping logs will sleep until they
  205. // can be written. This should only be used for debug logs, and not in a hot path.
  206. //
  207. // The provided context, if canceled, will cause all logs to be dropped and
  208. // prevent any sleeps.
  209. func SlowLoggerWithClock(ctx context.Context, logf Logf, f time.Duration, burst int, timeNow func() time.Time) Logf {
  210. var (
  211. mu sync.Mutex
  212. tb = newTokenBucket(f, burst, timeNow())
  213. )
  214. return func(format string, args ...any) {
  215. if ctx.Err() != nil {
  216. return
  217. }
  218. // Hold the mutex for the entire length of the check + log
  219. // since our token bucket isn't concurrency-safe.
  220. mu.Lock()
  221. defer mu.Unlock()
  222. tb.AdvanceTo(timeNow())
  223. // If we can get a token, then do that and return.
  224. if tb.Get() {
  225. logf(format, args...)
  226. return
  227. }
  228. // Otherwise, sleep for 2x the duration so that we don't
  229. // immediately sleep again on the next call.
  230. tmr := time.NewTimer(2 * f)
  231. defer tmr.Stop()
  232. select {
  233. case curr := <-tmr.C:
  234. tb.AdvanceTo(curr)
  235. case <-ctx.Done():
  236. return
  237. }
  238. if !tb.Get() {
  239. log.Printf("[unexpected] error rate-limiting in SlowLoggerWithClock")
  240. return
  241. }
  242. logf(format, args...)
  243. }
  244. }
  245. // LogOnChange logs a given line only if line != lastLine, or if maxInterval has passed
  246. // since the last time this identical line was logged.
  247. func LogOnChange(logf Logf, maxInterval time.Duration, timeNow func() time.Time) Logf {
  248. var (
  249. mu sync.Mutex
  250. sLastLogged string
  251. tLastLogged = timeNow()
  252. )
  253. return func(format string, args ...any) {
  254. s := fmt.Sprintf(format, args...)
  255. mu.Lock()
  256. if s == sLastLogged && timeNow().Sub(tLastLogged) < maxInterval {
  257. mu.Unlock()
  258. return
  259. }
  260. sLastLogged = s
  261. tLastLogged = timeNow()
  262. mu.Unlock()
  263. // Re-stringify it (instead of using "%s", s) so something like "%s"
  264. // doesn't end up getting rate-limited. (And can't use 's' as the pattern,
  265. // as it might contain formatting directives.)
  266. logf(format, args...)
  267. }
  268. }
  269. // ArgWriter is a fmt.Formatter that can be passed to any Logf func to
  270. // efficiently write to a %v argument without allocations.
  271. type ArgWriter func(*bufio.Writer)
  272. func (fn ArgWriter) Format(f fmt.State, _ rune) {
  273. bw := argBufioPool.Get().(*bufio.Writer)
  274. bw.Reset(f)
  275. fn(bw)
  276. bw.Flush()
  277. argBufioPool.Put(bw)
  278. }
  279. var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(io.Discard, 1024) }}
  280. // Filtered returns a Logf that silently swallows some log lines.
  281. // Each inbound format and args is evaluated and printed to a string s.
  282. // The original format and args are passed to logf if and only if allow(s) returns true.
  283. func Filtered(logf Logf, allow func(s string) bool) Logf {
  284. return func(format string, args ...any) {
  285. msg := fmt.Sprintf(format, args...)
  286. if !allow(msg) {
  287. return
  288. }
  289. logf(format, args...)
  290. }
  291. }
  292. // LogfCloser wraps logf to create a logger that can be closed.
  293. // Calling close makes all future calls to newLogf into no-ops.
  294. func LogfCloser(logf Logf) (newLogf Logf, close func()) {
  295. var (
  296. mu sync.Mutex
  297. closed bool
  298. )
  299. close = func() {
  300. mu.Lock()
  301. defer mu.Unlock()
  302. closed = true
  303. }
  304. newLogf = func(msg string, args ...any) {
  305. mu.Lock()
  306. if closed {
  307. mu.Unlock()
  308. return
  309. }
  310. mu.Unlock()
  311. logf(msg, args...)
  312. }
  313. return newLogf, close
  314. }