logger_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package logger
  4. import (
  5. "bufio"
  6. "bytes"
  7. "context"
  8. "fmt"
  9. "log"
  10. "sync"
  11. "testing"
  12. "time"
  13. qt "github.com/frankban/quicktest"
  14. "tailscale.com/tailcfg"
  15. "tailscale.com/version"
  16. )
  17. func TestFuncWriter(t *testing.T) {
  18. w := FuncWriter(t.Logf)
  19. lg := log.New(w, "prefix: ", 0)
  20. lg.Printf("plumbed through")
  21. }
  22. func TestStdLogger(t *testing.T) {
  23. lg := StdLogger(t.Logf)
  24. lg.Printf("plumbed through")
  25. }
  26. func logTester(want []string, t *testing.T, i *int) Logf {
  27. return func(format string, args ...any) {
  28. got := fmt.Sprintf(format, args...)
  29. if *i >= len(want) {
  30. t.Fatalf("Logging continued past end of expected input: %s", got)
  31. }
  32. if got != want[*i] {
  33. t.Fatalf("\nwanted: %s\n got: %s", want[*i], got)
  34. }
  35. t.Log(got)
  36. *i++
  37. }
  38. }
  39. func TestRateLimiter(t *testing.T) {
  40. want := []string{
  41. "boring string with constant formatting (constant)",
  42. "templated format string no. 0",
  43. "boring string with constant formatting (constant)",
  44. "[RATELIMIT] format(\"boring string with constant formatting %s\")",
  45. "templated format string no. 1",
  46. "[RATELIMIT] format(\"templated format string no. %d\")",
  47. "Make sure this string makes it through the rest (that are blocked) 4",
  48. "4 shouldn't get filtered.",
  49. "hello 1",
  50. "hello 2",
  51. "[RATELIMIT] format(\"hello %v\")",
  52. "[RATELIMIT] format(\"hello %v\") (2 dropped)",
  53. "hello 5",
  54. "hello 6",
  55. "[RATELIMIT] format(\"hello %v\")",
  56. "hello 7",
  57. }
  58. var now time.Time
  59. nowf := func() time.Time { return now }
  60. testsRun := 0
  61. lgtest := logTester(want, t, &testsRun)
  62. lg := RateLimitedFnWithClock(lgtest, 1*time.Minute, 2, 50, nowf)
  63. var prefixed Logf
  64. for i := range 10 {
  65. lg("boring string with constant formatting %s", "(constant)")
  66. lg("templated format string no. %d", i)
  67. if i == 4 {
  68. lg("Make sure this string makes it through the rest (that are blocked) %d", i)
  69. prefixed = WithPrefix(lg, string(rune('0'+i)))
  70. prefixed(" shouldn't get filtered.")
  71. }
  72. }
  73. lg("hello %v", 1)
  74. lg("hello %v", 2) // printed, but rate limit starts
  75. lg("hello %v", 3) // rate limited (not printed)
  76. now = now.Add(1 * time.Minute)
  77. lg("hello %v", 4) // still limited (not printed)
  78. now = now.Add(1 * time.Minute)
  79. lg("hello %v", 5) // restriction lifted; prints drop count + message
  80. lg("hello %v", 6) // printed, but rate limit starts
  81. now = now.Add(2 * time.Minute)
  82. lg("hello %v", 7) // restriction lifted; no drop count needed
  83. if testsRun < len(want) {
  84. t.Fatalf("Tests after %s weren't logged.", want[testsRun])
  85. }
  86. }
  87. func testTimer(d time.Duration) func() time.Time {
  88. timeNow := time.Now()
  89. return func() time.Time {
  90. timeNow = timeNow.Add(d)
  91. return timeNow
  92. }
  93. }
  94. func TestLogOnChange(t *testing.T) {
  95. want := []string{
  96. "1 2 3 4 5 6",
  97. "1 2 3 4 5 6",
  98. "1 2 3 4 5 7",
  99. "1 2 3 4 5",
  100. "1 2 3 4 5 6 7",
  101. }
  102. timeNow := testTimer(1 * time.Second)
  103. testsRun := 0
  104. lgtest := logTester(want, t, &testsRun)
  105. lg := LogOnChange(lgtest, 5*time.Second, timeNow)
  106. for range 10 {
  107. lg("%s", "1 2 3 4 5 6")
  108. }
  109. lg("1 2 3 4 5 7")
  110. lg("1 2 3 4 5")
  111. lg("1 2 3 4 5")
  112. lg("1 2 3 4 5 6 7")
  113. if testsRun < len(want) {
  114. t.Fatalf("'Wanted' lines including and after [%s] weren't logged.", want[testsRun])
  115. }
  116. }
  117. func TestArgWriter(t *testing.T) {
  118. got := new(bytes.Buffer)
  119. fmt.Fprintf(got, "Greeting: %v", ArgWriter(func(bw *bufio.Writer) {
  120. bw.WriteString("Hello, ")
  121. bw.WriteString("world")
  122. bw.WriteByte('!')
  123. }))
  124. const want = "Greeting: Hello, world!"
  125. if got.String() != want {
  126. t.Errorf("got %q; want %q", got, want)
  127. }
  128. }
  129. func TestSynchronization(t *testing.T) {
  130. timeNow := testTimer(1 * time.Second)
  131. tests := []struct {
  132. name string
  133. logf Logf
  134. }{
  135. {"RateLimitedFn", RateLimitedFn(t.Logf, 1*time.Minute, 2, 50)},
  136. {"LogOnChange", LogOnChange(t.Logf, 5*time.Second, timeNow)},
  137. }
  138. for _, tt := range tests {
  139. t.Run(tt.name, func(t *testing.T) {
  140. var wg sync.WaitGroup
  141. wg.Add(2)
  142. f := func() {
  143. tt.logf("1 2 3 4 5")
  144. wg.Done()
  145. }
  146. go f()
  147. go f()
  148. wg.Wait()
  149. })
  150. }
  151. }
  152. // test that RateLimitedFn is safe for reentrancy without deadlocking
  153. func TestRateLimitedFnReentrancy(t *testing.T) {
  154. rlogf := RateLimitedFn(t.Logf, time.Nanosecond, 10, 10)
  155. rlogf("Hello.")
  156. rlogf("Hello, %v", ArgWriter(func(bw *bufio.Writer) {
  157. bw.WriteString("world")
  158. }))
  159. rlogf("Hello, %v", ArgWriter(func(bw *bufio.Writer) {
  160. bw.WriteString("bye")
  161. rlogf("boom") // this used to deadlock
  162. }))
  163. }
  164. func TestContext(t *testing.T) {
  165. c := qt.New(t)
  166. ctx := context.Background()
  167. // Test that FromContext returns log.Printf when the context has no custom log function.
  168. defer log.SetOutput(log.Writer())
  169. defer log.SetFlags(log.Flags())
  170. var buf bytes.Buffer
  171. log.SetOutput(&buf)
  172. log.SetFlags(0)
  173. logf := FromContext(ctx)
  174. logf("a")
  175. c.Assert(buf.String(), qt.Equals, "a\n")
  176. // Test that FromContext and Ctx work together.
  177. var called bool
  178. markCalled := func(string, ...any) {
  179. called = true
  180. }
  181. ctx = Ctx(ctx, markCalled)
  182. logf = FromContext(ctx)
  183. logf("a")
  184. c.Assert(called, qt.IsTrue)
  185. }
  186. func TestJSON(t *testing.T) {
  187. var buf bytes.Buffer
  188. var logf Logf = func(f string, a ...any) { fmt.Fprintf(&buf, f, a...) }
  189. logf.JSON(1, "foo", &tailcfg.Hostinfo{})
  190. want := "[v\x00JSON]1" + `{"foo":{}}`
  191. if got := buf.String(); got != want {
  192. t.Errorf("mismatch\n got: %q\nwant: %q\n", got, want)
  193. }
  194. }
  195. func TestAsJSON(t *testing.T) {
  196. got := fmt.Sprintf("got %v", AsJSON(struct {
  197. Foo string
  198. Bar int
  199. }{"hi", 123}))
  200. const want = `got {"Foo":"hi","Bar":123}`
  201. if got != want {
  202. t.Errorf("got %#q; want %#q", got, want)
  203. }
  204. got = fmt.Sprintf("got %v", AsJSON(func() {}))
  205. const wantErr = `got %!JSON-ERROR:json: unsupported type: func()`
  206. if got != wantErr {
  207. t.Errorf("for marshal error, got %#q; want %#q", got, wantErr)
  208. }
  209. if version.IsRace() {
  210. // skip the rest of the test in race mode;
  211. // race mode causes more allocs which we don't care about.
  212. return
  213. }
  214. var buf bytes.Buffer
  215. n := int(testing.AllocsPerRun(1000, func() {
  216. buf.Reset()
  217. fmt.Fprintf(&buf, "got %v", AsJSON("hi"))
  218. }))
  219. if n > 2 {
  220. // the JSON AsMarshal itself + boxing
  221. // the asJSONResult into an interface (which needs
  222. // to happen at some point to get to fmt, so might
  223. // as well return an interface from AsJSON))
  224. t.Errorf("allocs = %v; want max 2", n)
  225. }
  226. }
  227. func TestHTTPServerLogFilter(t *testing.T) {
  228. var buf bytes.Buffer
  229. logf := func(format string, args ...any) {
  230. t.Logf("[logf] "+format, args...)
  231. fmt.Fprintf(&buf, format, args...)
  232. }
  233. lf := HTTPServerLogFilter{logf}
  234. quietLogger := log.New(lf, "", 0)
  235. quietLogger.Printf("foo bar")
  236. quietLogger.Printf("http: TLS handshake error from %s:%d: EOF", "1.2.3.4", 9999)
  237. quietLogger.Printf("baz")
  238. const want = "foo bar\nbaz\n"
  239. if s := buf.String(); s != want {
  240. t.Errorf("got buf=%q, want %q", s, want)
  241. }
  242. }