| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.21 |
| |
| package qlog |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io" |
| "log/slog" |
| "reflect" |
| "testing" |
| "time" |
| ) |
| |
| // QLog tests are mostly in the quic package, where we can test event generation |
| // and serialization together. |
| |
| func TestQLogHandlerEvents(t *testing.T) { |
| for _, test := range []struct { |
| name string |
| f func(*slog.Logger) |
| want []map[string]any // events, not counting the trace header |
| }{{ |
| name: "various types", |
| f: func(log *slog.Logger) { |
| log.Info("message", |
| "bool", true, |
| "duration", time.Duration(1*time.Second), |
| "float", 0.0, |
| "int", 0, |
| "string", "value", |
| "uint", uint64(0), |
| slog.Group("group", |
| "a", 0, |
| ), |
| ) |
| }, |
| want: []map[string]any{{ |
| "name": "message", |
| "data": map[string]any{ |
| "bool": true, |
| "duration": float64(1000), |
| "float": float64(0.0), |
| "int": float64(0), |
| "string": "value", |
| "uint": float64(0), |
| "group": map[string]any{ |
| "a": float64(0), |
| }, |
| }, |
| }}, |
| }, { |
| name: "WithAttrs", |
| f: func(log *slog.Logger) { |
| log = log.With( |
| "with_a", "a", |
| "with_b", "b", |
| ) |
| log.Info("m1", "field", "1") |
| log.Info("m2", "field", "2") |
| }, |
| want: []map[string]any{{ |
| "name": "m1", |
| "data": map[string]any{ |
| "with_a": "a", |
| "with_b": "b", |
| "field": "1", |
| }, |
| }, { |
| "name": "m2", |
| "data": map[string]any{ |
| "with_a": "a", |
| "with_b": "b", |
| "field": "2", |
| }, |
| }}, |
| }, { |
| name: "WithGroup", |
| f: func(log *slog.Logger) { |
| log = log.With( |
| "with_a", "a", |
| "with_b", "b", |
| ) |
| log.Info("m1", "field", "1") |
| log.Info("m2", "field", "2") |
| }, |
| want: []map[string]any{{ |
| "name": "m1", |
| "data": map[string]any{ |
| "with_a": "a", |
| "with_b": "b", |
| "field": "1", |
| }, |
| }, { |
| "name": "m2", |
| "data": map[string]any{ |
| "with_a": "a", |
| "with_b": "b", |
| "field": "2", |
| }, |
| }}, |
| }} { |
| var out bytes.Buffer |
| opts := HandlerOptions{ |
| Level: slog.LevelDebug, |
| NewTrace: func(TraceInfo) (io.WriteCloser, error) { |
| return nopCloseWriter{&out}, nil |
| }, |
| } |
| h, err := newJSONTraceHandler(opts, []slog.Attr{ |
| slog.String("group_id", "group"), |
| slog.Group("vantage_point", |
| slog.String("type", "client"), |
| ), |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| log := slog.New(h) |
| test.f(log) |
| got := []map[string]any{} |
| for i, e := range bytes.Split(out.Bytes(), []byte{0x1e}) { |
| // i==0: empty string before the initial record separator |
| // i==1: trace header; not part of this test |
| if i < 2 { |
| continue |
| } |
| var val map[string]any |
| if err := json.Unmarshal(e, &val); err != nil { |
| panic(fmt.Errorf("log unmarshal failure: %v\n%q", err, string(e))) |
| } |
| delete(val, "time") |
| got = append(got, val) |
| } |
| if !reflect.DeepEqual(got, test.want) { |
| t.Errorf("event mismatch\ngot: %v\nwant: %v", got, test.want) |
| } |
| } |
| |
| } |
| |
| type nopCloseWriter struct { |
| io.Writer |
| } |
| |
| func (nopCloseWriter) Close() error { return nil } |