logtail_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package logtail
  4. import (
  5. "bytes"
  6. "context"
  7. "encoding/json"
  8. "io"
  9. "net/http"
  10. "net/http/httptest"
  11. "strings"
  12. "testing"
  13. "time"
  14. "github.com/go-json-experiment/json/jsontext"
  15. "tailscale.com/tstest"
  16. "tailscale.com/tstime"
  17. "tailscale.com/util/eventbus/eventbustest"
  18. "tailscale.com/util/must"
  19. )
  20. func TestFastShutdown(t *testing.T) {
  21. ctx, cancel := context.WithCancel(context.Background())
  22. cancel()
  23. testServ := httptest.NewServer(http.HandlerFunc(
  24. func(w http.ResponseWriter, r *http.Request) {}))
  25. defer testServ.Close()
  26. logger := NewLogger(Config{
  27. BaseURL: testServ.URL,
  28. Bus: eventbustest.NewBus(t),
  29. }, t.Logf)
  30. err := logger.Shutdown(ctx)
  31. if err != nil {
  32. t.Error(err)
  33. }
  34. }
  35. // maximum number of times a test will call l.Write()
  36. const logLines = 3
  37. type LogtailTestServer struct {
  38. srv *httptest.Server // Log server
  39. uploaded chan []byte
  40. }
  41. func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
  42. ts := LogtailTestServer{}
  43. // max channel backlog = 1 "started" + #logLines x "log line" + 1 "closed"
  44. ts.uploaded = make(chan []byte, 2+logLines)
  45. ts.srv = httptest.NewServer(http.HandlerFunc(
  46. func(w http.ResponseWriter, r *http.Request) {
  47. body, err := io.ReadAll(r.Body)
  48. if err != nil {
  49. t.Error("failed to read HTTP request")
  50. }
  51. ts.uploaded <- body
  52. }))
  53. t.Cleanup(ts.srv.Close)
  54. logger := NewLogger(Config{
  55. BaseURL: ts.srv.URL,
  56. Bus: eventbustest.NewBus(t),
  57. }, t.Logf)
  58. // There is always an initial "logtail started" message
  59. body := <-ts.uploaded
  60. if !strings.Contains(string(body), "started") {
  61. t.Errorf("unknown start logging statement: %q", string(body))
  62. }
  63. return &ts, logger
  64. }
  65. func TestDrainPendingMessages(t *testing.T) {
  66. ts, logger := NewLogtailTestHarness(t)
  67. for range logLines {
  68. logger.Write([]byte("log line"))
  69. }
  70. // all of the "log line" messages usually arrive at once, but poll if needed.
  71. body := ""
  72. for i := 0; i <= logLines; i++ {
  73. body += string(<-ts.uploaded)
  74. count := strings.Count(body, "log line")
  75. if count == logLines {
  76. break
  77. }
  78. // if we never find count == logLines, the test will eventually time out.
  79. }
  80. err := logger.Shutdown(context.Background())
  81. if err != nil {
  82. t.Error(err)
  83. }
  84. }
  85. func TestEncodeAndUploadMessages(t *testing.T) {
  86. ts, logger := NewLogtailTestHarness(t)
  87. tests := []struct {
  88. name string
  89. log string
  90. want string
  91. }{
  92. {
  93. "plain text",
  94. "log line",
  95. "log line",
  96. },
  97. {
  98. "simple JSON",
  99. `{"text":"log line"}`,
  100. "log line",
  101. },
  102. }
  103. for _, tt := range tests {
  104. io.WriteString(logger, tt.log)
  105. body := <-ts.uploaded
  106. data := unmarshalOne(t, body)
  107. got := data["text"]
  108. if got != tt.want {
  109. t.Errorf("%s: got %q; want %q", tt.name, got.(string), tt.want)
  110. }
  111. ltail, ok := data["logtail"]
  112. if ok {
  113. logtailmap := ltail.(map[string]any)
  114. _, ok = logtailmap["client_time"]
  115. if !ok {
  116. t.Errorf("%s: no client_time present", tt.name)
  117. }
  118. } else {
  119. t.Errorf("%s: no logtail map present", tt.name)
  120. }
  121. }
  122. err := logger.Shutdown(context.Background())
  123. if err != nil {
  124. t.Error(err)
  125. }
  126. }
  127. func TestLoggerWriteLength(t *testing.T) {
  128. lg := &Logger{
  129. clock: tstime.StdClock{},
  130. buffer: NewMemoryBuffer(1024),
  131. }
  132. inBuf := []byte("some text to encode")
  133. n, err := lg.Write(inBuf)
  134. if err != nil {
  135. t.Error(err)
  136. }
  137. if n != len(inBuf) {
  138. t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf))
  139. }
  140. }
  141. func TestParseAndRemoveLogLevel(t *testing.T) {
  142. tests := []struct {
  143. log string
  144. wantLevel int
  145. wantLog string
  146. }{
  147. {
  148. "no level",
  149. 0,
  150. "no level",
  151. },
  152. {
  153. "[v1] level 1",
  154. 1,
  155. "level 1",
  156. },
  157. {
  158. "level 1 [v1] ",
  159. 1,
  160. "level 1 ",
  161. },
  162. {
  163. "[v2] level 2",
  164. 2,
  165. "level 2",
  166. },
  167. {
  168. "level [v2] 2",
  169. 2,
  170. "level 2",
  171. },
  172. {
  173. "[v3] no level 3",
  174. 0,
  175. "[v3] no level 3",
  176. },
  177. {
  178. "some ignored text then [v\x00JSON]5{\"foo\":1234}",
  179. 5,
  180. `{"foo":1234}`,
  181. },
  182. }
  183. for _, tt := range tests {
  184. gotLevel, gotLog := parseAndRemoveLogLevel([]byte(tt.log))
  185. if gotLevel != tt.wantLevel {
  186. t.Errorf("parseAndRemoveLogLevel(%q): got:%d; want %d",
  187. tt.log, gotLevel, tt.wantLevel)
  188. }
  189. if string(gotLog) != tt.wantLog {
  190. t.Errorf("parseAndRemoveLogLevel(%q): got:%q; want %q",
  191. tt.log, gotLog, tt.wantLog)
  192. }
  193. }
  194. }
  195. func unmarshalOne(t *testing.T, body []byte) map[string]any {
  196. t.Helper()
  197. var entries []map[string]any
  198. err := json.Unmarshal(body, &entries)
  199. if err != nil {
  200. t.Error(err)
  201. }
  202. if len(entries) != 1 {
  203. t.Fatalf("expected one entry, got %d", len(entries))
  204. }
  205. return entries[0]
  206. }
  207. type simpleMemBuf struct {
  208. Buffer
  209. buf bytes.Buffer
  210. }
  211. func (b *simpleMemBuf) Write(p []byte) (n int, err error) { return b.buf.Write(p) }
  212. func TestEncode(t *testing.T) {
  213. tests := []struct {
  214. in string
  215. want string
  216. }{
  217. {
  218. "normal",
  219. `{"logtail":{"client_time":"1970-01-01T00:02:03.000000456Z","proc_id":7,"proc_seq":1},"text":"normal"}` + "\n",
  220. },
  221. {
  222. "and a [v1] level one",
  223. `{"logtail":{"client_time":"1970-01-01T00:02:03.000000456Z","proc_id":7,"proc_seq":1},"v":1,"text":"and a level one"}` + "\n",
  224. },
  225. {
  226. "[v2] some verbose two",
  227. `{"logtail":{"client_time":"1970-01-01T00:02:03.000000456Z","proc_id":7,"proc_seq":1},"v":2,"text":"some verbose two"}` + "\n",
  228. },
  229. {
  230. "{}",
  231. `{"logtail":{"client_time":"1970-01-01T00:02:03.000000456Z","proc_id":7,"proc_seq":1}}` + "\n",
  232. },
  233. {
  234. `{"foo":"bar"}`,
  235. `{"logtail":{"client_time":"1970-01-01T00:02:03.000000456Z","proc_id":7,"proc_seq":1},"foo":"bar"}` + "\n",
  236. },
  237. {
  238. "foo: [v\x00JSON]0{\"foo\":1}",
  239. "{\"logtail\":{\"client_time\":\"1970-01-01T00:02:03.000000456Z\",\"proc_id\":7,\"proc_seq\":1},\"foo\":1}\n",
  240. },
  241. {
  242. "foo: [v\x00JSON]2{\"foo\":1}",
  243. "{\"logtail\":{\"client_time\":\"1970-01-01T00:02:03.000000456Z\",\"proc_id\":7,\"proc_seq\":1},\"v\":2,\"foo\":1}\n",
  244. },
  245. }
  246. for _, tt := range tests {
  247. buf := new(simpleMemBuf)
  248. lg := &Logger{
  249. clock: tstest.NewClock(tstest.ClockOpts{Start: time.Unix(123, 456).UTC()}),
  250. buffer: buf,
  251. procID: 7,
  252. procSequence: 1,
  253. }
  254. io.WriteString(lg, tt.in)
  255. got := buf.buf.String()
  256. if got != tt.want {
  257. t.Errorf("for %q,\n got: %#q\nwant: %#q\n", tt.in, got, tt.want)
  258. }
  259. if err := json.Compact(new(bytes.Buffer), buf.buf.Bytes()); err != nil {
  260. t.Errorf("invalid output JSON for %q: %s", tt.in, got)
  261. }
  262. }
  263. }
  264. // Test that even if Logger.Write modifies the input buffer, we still return the
  265. // length of the input buffer, not what we shrank it down to. Otherwise the
  266. // caller will think we did a short write, violating the io.Writer contract.
  267. func TestLoggerWriteResult(t *testing.T) {
  268. buf := NewMemoryBuffer(100)
  269. lg := &Logger{
  270. clock: tstest.NewClock(tstest.ClockOpts{Start: time.Unix(123, 0)}),
  271. buffer: buf,
  272. }
  273. const in = "[v1] foo"
  274. n, err := lg.Write([]byte(in))
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. if got, want := n, len(in); got != want {
  279. t.Errorf("Write = %v; want %v", got, want)
  280. }
  281. back, err := buf.TryReadLine()
  282. if err != nil {
  283. t.Fatal(err)
  284. }
  285. if got, want := string(back), `{"logtail":{"client_time":"1970-01-01T00:02:03Z"},"v":1,"text":"foo"}`+"\n"; got != want {
  286. t.Errorf("mismatch.\n got: %#q\nwant: %#q", back, want)
  287. }
  288. }
  289. func TestAppendMetadata(t *testing.T) {
  290. var lg Logger
  291. lg.clock = tstest.NewClock(tstest.ClockOpts{Start: time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC)})
  292. lg.metricsDelta = func() string { return "metrics" }
  293. for _, tt := range []struct {
  294. skipClientTime bool
  295. skipMetrics bool
  296. procID uint32
  297. procSeq uint64
  298. errDetail string
  299. errData jsontext.Value
  300. level int
  301. want string
  302. }{
  303. {want: `"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics",`},
  304. {skipClientTime: true, want: `"metrics":"metrics",`},
  305. {skipMetrics: true, want: `"logtail":{"client_time":"2000-01-01T00:00:00Z"},`},
  306. {skipClientTime: true, skipMetrics: true, want: ``},
  307. {skipClientTime: true, skipMetrics: true, procID: 1, want: `"logtail":{"proc_id":1},`},
  308. {skipClientTime: true, skipMetrics: true, procSeq: 2, want: `"logtail":{"proc_seq":2},`},
  309. {skipClientTime: true, skipMetrics: true, procID: 1, procSeq: 2, want: `"logtail":{"proc_id":1,"proc_seq":2},`},
  310. {skipMetrics: true, procID: 1, procSeq: 2, want: `"logtail":{"client_time":"2000-01-01T00:00:00Z","proc_id":1,"proc_seq":2},`},
  311. {skipClientTime: true, skipMetrics: true, errDetail: "error", want: `"logtail":{"error":{"detail":"error"}},`},
  312. {skipClientTime: true, skipMetrics: true, errData: jsontext.Value("null"), want: `"logtail":{"error":{"bad_data":null}},`},
  313. {skipClientTime: true, skipMetrics: true, level: 5, want: `"v":5,`},
  314. {procID: 1, procSeq: 2, errDetail: "error", errData: jsontext.Value(`["something","bad","happened"]`), level: 2,
  315. want: `"logtail":{"client_time":"2000-01-01T00:00:00Z","proc_id":1,"proc_seq":2,"error":{"detail":"error","bad_data":["something","bad","happened"]}},"metrics":"metrics","v":2,`},
  316. } {
  317. got := string(lg.appendMetadata(nil, tt.skipClientTime, tt.skipMetrics, tt.procID, tt.procSeq, tt.errDetail, tt.errData, tt.level))
  318. if got != tt.want {
  319. t.Errorf("appendMetadata(%v, %v, %v, %v, %v, %v, %v):\n\tgot %s\n\twant %s", tt.skipClientTime, tt.skipMetrics, tt.procID, tt.procSeq, tt.errDetail, tt.errData, tt.level, got, tt.want)
  320. }
  321. gotObj := "{" + strings.TrimSuffix(got, ",") + "}"
  322. if !jsontext.Value(gotObj).IsValid() {
  323. t.Errorf("`%s`.IsValid() = false, want true", gotObj)
  324. }
  325. }
  326. }
  327. func TestAppendText(t *testing.T) {
  328. var lg Logger
  329. lg.clock = tstest.NewClock(tstest.ClockOpts{Start: time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC)})
  330. lg.metricsDelta = func() string { return "metrics" }
  331. lg.lowMem = true
  332. for _, tt := range []struct {
  333. text string
  334. skipClientTime bool
  335. procID uint32
  336. procSeq uint64
  337. level int
  338. want string
  339. }{
  340. {want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics"}`},
  341. {skipClientTime: true, want: `{"metrics":"metrics"}`},
  342. {skipClientTime: true, procID: 1, procSeq: 2, want: `{"logtail":{"proc_id":1,"proc_seq":2},"metrics":"metrics"}`},
  343. {text: "fizz buzz", want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","text":"fizz buzz"}`},
  344. {text: "\b\f\n\r\t\"\\", want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","text":"\b\f\n\r\t\"\\"}`},
  345. {text: "x" + strings.Repeat("😐", maxSize), want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","text":"x` + strings.Repeat("😐", 1023) + `…+1044484"}`},
  346. } {
  347. got := string(lg.appendText(nil, []byte(tt.text), tt.skipClientTime, tt.procID, tt.procSeq, tt.level))
  348. if !strings.HasSuffix(got, "\n") {
  349. t.Errorf("`%s` does not end with a newline", got)
  350. }
  351. got = got[:len(got)-1]
  352. if got != tt.want {
  353. t.Errorf("appendText(%v, %v, %v, %v, %v):\n\tgot %s\n\twant %s", tt.text[:min(len(tt.text), 256)], tt.skipClientTime, tt.procID, tt.procSeq, tt.level, got, tt.want)
  354. }
  355. if !jsontext.Value(got).IsValid() {
  356. t.Errorf("`%s`.IsValid() = false, want true", got)
  357. }
  358. }
  359. }
  360. func TestAppendTextOrJSON(t *testing.T) {
  361. var lg Logger
  362. lg.clock = tstest.NewClock(tstest.ClockOpts{Start: time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC)})
  363. lg.metricsDelta = func() string { return "metrics" }
  364. lg.lowMem = true
  365. for _, tt := range []struct {
  366. in string
  367. level int
  368. want string
  369. }{
  370. {want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics"}`},
  371. {in: "[]", want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","text":"[]"}`},
  372. {level: 1, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","v":1}`},
  373. {in: `{}`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"}}`},
  374. {in: `{}{}`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"metrics":"metrics","text":"{}{}"}`},
  375. {in: "{\n\"fizz\"\n:\n\"buzz\"\n}", want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z"},"fizz":"buzz"}`},
  376. {in: `{ "logtail" : "duplicate" }`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z","error":{"detail":"duplicate logtail member","bad_data":"duplicate"}}}`},
  377. {in: `{ "fizz" : "buzz" , "logtail" : "duplicate" }`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z","error":{"detail":"duplicate logtail member","bad_data":"duplicate"}}, "fizz" : "buzz"}`},
  378. {in: `{ "logtail" : "duplicate" , "fizz" : "buzz" }`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z","error":{"detail":"duplicate logtail member","bad_data":"duplicate"}} , "fizz" : "buzz"}`},
  379. {in: `{ "fizz" : "buzz" , "logtail" : "duplicate" , "wizz" : "wuzz" }`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z","error":{"detail":"duplicate logtail member","bad_data":"duplicate"}}, "fizz" : "buzz" , "wizz" : "wuzz"}`},
  380. {in: `{"long":"` + strings.Repeat("a", maxSize) + `"}`, want: `{"logtail":{"client_time":"2000-01-01T00:00:00Z","error":{"detail":"entry too large: 262155 bytes","bad_data":"{\"long\":\"` + strings.Repeat("a", 43681) + `…+218465"}}}`},
  381. } {
  382. got := string(lg.appendTextOrJSONLocked(nil, []byte(tt.in), tt.level))
  383. if !strings.HasSuffix(got, "\n") {
  384. t.Errorf("`%s` does not end with a newline", got)
  385. }
  386. got = got[:len(got)-1]
  387. if got != tt.want {
  388. t.Errorf("appendTextOrJSON(%v, %v):\n\tgot %s\n\twant %s", tt.in[:min(len(tt.in), 256)], tt.level, got, tt.want)
  389. }
  390. if !jsontext.Value(got).IsValid() {
  391. t.Errorf("`%s`.IsValid() = false, want true", got)
  392. }
  393. }
  394. }
  395. var sink []byte
  396. func TestAppendTextAllocs(t *testing.T) {
  397. lg := &Logger{clock: tstime.StdClock{}}
  398. inBuf := []byte("some text to encode")
  399. procID := uint32(0x24d32ee9)
  400. procSequence := uint64(0x12346)
  401. must.Do(tstest.MinAllocsPerRun(t, 0, func() {
  402. sink = lg.appendText(sink[:0], inBuf, false, procID, procSequence, 0)
  403. }))
  404. }
  405. func TestAppendJSONAllocs(t *testing.T) {
  406. lg := &Logger{clock: tstime.StdClock{}}
  407. inBuf := []byte(`{"fizz":"buzz"}`)
  408. must.Do(tstest.MinAllocsPerRun(t, 1, func() {
  409. sink = lg.appendTextOrJSONLocked(sink[:0], inBuf, 0)
  410. }))
  411. }
  412. type discardBuffer struct{ Buffer }
  413. func (discardBuffer) Write(p []byte) (n int, err error) { return n, nil }
  414. var testdataTextLog = []byte(`netcheck: report: udp=true v6=false v6os=true mapvarydest=false hair=false portmap= v4a=174.xxx.xxx.xxx:18168 derp=2 derpdist=1v4:82ms,2v4:18ms,3v4:214ms,4v4:171ms,5v4:196ms,7v4:124ms,8v4:149ms,9v4:56ms,10v4:32ms,11v4:196ms,12v4:71ms,13v4:48ms,14v4:166ms,16v4:85ms,17v4:25ms,18v4:153ms,19v4:176ms,20v4:193ms,21v4:84ms,22v4:182ms,24v4:73ms`)
  415. var testdataJSONLog = []byte(`{"end":"2024-04-08T21:39:15.715291586Z","nodeId":"nQRJBE7CNTRL","physicalTraffic":[{"dst":"127.x.x.x:2","src":"100.x.x.x:0","txBytes":148,"txPkts":1},{"dst":"127.x.x.x:2","src":"100.x.x.x:0","txBytes":148,"txPkts":1},{"dst":"98.x.x.x:1025","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5},{"dst":"24.x.x.x:49973","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5},{"dst":"73.x.x.x:41641","rxBytes":732,"rxPkts":6,"src":"100.x.x.x:0","txBytes":820,"txPkts":7},{"dst":"75.x.x.x:1025","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5},{"dst":"75.x.x.x:41641","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5},{"dst":"174.x.x.x:35497","rxBytes":13008,"rxPkts":98,"src":"100.x.x.x:0","txBytes":26688,"txPkts":150},{"dst":"47.x.x.x:41641","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5},{"dst":"64.x.x.x:41641","rxBytes":640,"rxPkts":5,"src":"100.x.x.x:0","txBytes":640,"txPkts":5}],"start":"2024-04-08T21:39:11.099495616Z","virtualTraffic":[{"dst":"100.x.x.x:33008","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:32984","proto":6,"src":"100.x.x.x:22","txBytes":1340,"txPkts":10},{"dst":"100.x.x.x:32998","proto":6,"src":"100.x.x.x:22","txBytes":1020,"txPkts":10},{"dst":"100.x.x.x:32994","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:32980","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:32950","proto":6,"src":"100.x.x.x:22","txBytes":1340,"txPkts":10},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:53332","txBytes":60,"txPkts":1},{"dst":"100.x.x.x:0","proto":1,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:32966","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:57882","txBytes":60,"txPkts":1},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:53326","txBytes":60,"txPkts":1},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:57892","txBytes":60,"txPkts":1},{"dst":"100.x.x.x:32934","proto":6,"src":"100.x.x.x:22","txBytes":8712,"txPkts":55},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:32942","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:32964","proto":6,"src":"100.x.x.x:22","txBytes":1260,"txPkts":10},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:0","proto":1,"rxBytes":420,"rxPkts":5,"src":"100.x.x.x:0","txBytes":420,"txPkts":5},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:37238","txBytes":60,"txPkts":1},{"dst":"100.x.x.x:22","proto":6,"src":"100.x.x.x:37252","txBytes":60,"txPkts":1}]}`)
  416. func BenchmarkWriteText(b *testing.B) {
  417. var lg Logger
  418. lg.clock = tstime.StdClock{}
  419. lg.buffer = discardBuffer{}
  420. b.ReportAllocs()
  421. for range b.N {
  422. must.Get(lg.Write(testdataTextLog))
  423. }
  424. }
  425. func BenchmarkWriteJSON(b *testing.B) {
  426. var lg Logger
  427. lg.clock = tstime.StdClock{}
  428. lg.buffer = discardBuffer{}
  429. b.ReportAllocs()
  430. for range b.N {
  431. must.Get(lg.Write(testdataJSONLog))
  432. }
  433. }