github.com/phsym/zeroslog@v0.1.1-0.20240224183259-0b7a5ea94339/zerolog_test.go (about) 1 package zeroslog 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "log/slog" 11 "net" 12 "reflect" 13 "runtime" 14 "strings" 15 "testing" 16 "time" 17 18 "maps" 19 20 "testing/slogtest" 21 22 "github.com/rs/zerolog" 23 ) 24 25 type stringer struct{} 26 27 func (stringer) String() string { 28 return "stringer" 29 } 30 31 type marshaller struct{ err error } 32 33 func (m marshaller) MarshalText() (text []byte, err error) { 34 return []byte("marshaller"), m.err 35 } 36 37 type jsoner struct { 38 foo string 39 err error 40 } 41 42 func (j jsoner) MarshalJSON() ([]byte, error) { 43 return []byte(fmt.Sprintf(`{"foo": %q}`, j.foo)), j.err 44 } 45 46 type unknown struct{ Foo string } 47 48 var ( 49 now = time.Now() 50 51 attrs = []slog.Attr{ 52 slog.String("titi", "toto"), 53 slog.String("tata", "tutu"), 54 slog.Int("foo", 12), 55 slog.Uint64("bar", 42), 56 slog.Duration("dur", 3*time.Second), 57 slog.Bool("bool", true), 58 slog.Float64("float", 23.7), 59 slog.Time("thetime", now), 60 slog.Any("err", errors.New("yo")), 61 slog.Group("empty"), 62 slog.Group("group", slog.String("bar", "baz")), 63 slog.Any("ip", net.IP{192, 168, 1, 2}), 64 slog.Any("ipnet", net.IPNet{IP: net.IP{192, 168, 1, 0}, Mask: net.IPv4Mask(255, 255, 255, 0)}), 65 slog.Any("mac", net.HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}), 66 slog.Any("stringer", stringer{}), 67 slog.Any("marshaller", &marshaller{}), 68 slog.Any("marshaller-err", &marshaller{err: errors.New("failure")}), 69 slog.Any("unknown", unknown{Foo: "bar"}), 70 slog.Any("json", &jsoner{foo: "bar"}), 71 slog.Any("json-err", &jsoner{err: errors.New("failure")}), 72 } 73 74 exp = map[string]any{ 75 "titi": "toto", 76 "tata": "tutu", 77 "foo": 12.0, 78 "bar": 42.0, 79 "dur": 3000.0, 80 "bool": true, 81 "float": 23.7, 82 "thetime": now.Format(time.RFC3339), 83 "err": "yo", 84 "group": map[string]any{"bar": "baz"}, 85 "ip": "192.168.1.2", 86 "ipnet": "192.168.1.0/24", 87 "mac": "00:00:5e:00:53:01", 88 "stringer": "stringer", 89 "marshaller": "marshaller", 90 "marshaller-err": "!ERROR:failure", 91 "unknown": map[string]any{"Foo": "bar"}, 92 "json": map[string]any{"foo": "bar"}, 93 "json-err": "!ERROR:failure", 94 } 95 96 levels = []struct { 97 zlvl zerolog.Level 98 slvl slog.Level 99 }{ 100 {zerolog.TraceLevel, slog.LevelDebug - 1}, 101 {zerolog.DebugLevel, slog.LevelDebug}, 102 {zerolog.InfoLevel, slog.LevelInfo}, 103 {zerolog.WarnLevel, slog.LevelWarn}, 104 {zerolog.WarnLevel, slog.LevelWarn + 1}, 105 {zerolog.WarnLevel, slog.LevelError - 1}, 106 {zerolog.ErrorLevel, slog.LevelError}, 107 {zerolog.ErrorLevel, slog.LevelError + 1}, 108 } 109 ) 110 111 func TestZerolog_Levels(t *testing.T) { 112 out := bytes.Buffer{} 113 for _, lvl := range levels { 114 t.Run(lvl.slvl.String(), func(t *testing.T) { 115 hdl := NewJsonHandler(&out, &HandlerOptions{Level: lvl.slvl}) 116 for _, l := range levels { 117 enabled := l.slvl >= lvl.slvl 118 if hdl.Enabled(nil, l.slvl) != enabled { 119 t.Fatalf("Level %s enablement status unexpected", l.slvl) 120 } 121 hdl.Handle(nil, slog.NewRecord(time.Now(), l.slvl, "foobar", 0)) 122 if enabled { 123 m := map[string]any{} 124 if err := json.NewDecoder(&out).Decode(&m); err != nil { 125 t.Fatalf("Failed to json decode log output: %s", err.Error()) 126 } 127 if m[zerolog.LevelFieldName] != l.zlvl.String() { 128 t.Fatalf("Unexpected value for field %s. Got %s but expected %s", zerolog.LevelFieldName, m[zerolog.LevelFieldName], l.zlvl.String()) 129 } 130 } 131 } 132 133 }) 134 } 135 } 136 137 func TestZerolog_Levels_NoOption(t *testing.T) { 138 out := bytes.Buffer{} 139 for _, lvl := range levels { 140 t.Run(lvl.slvl.String(), func(t *testing.T) { 141 hdl := NewHandler(zerolog.New(&out).Level(lvl.zlvl), nil) 142 for _, l := range levels { 143 enabled := l.zlvl >= lvl.zlvl 144 if hdl.Enabled(nil, l.slvl) != enabled { 145 t.Fatalf("Level %s enablement status unexpected", l.slvl) 146 } 147 hdl.Handle(nil, slog.NewRecord(time.Now(), l.slvl, "foobar", 0)) 148 m := map[string]any{} 149 err := json.NewDecoder(&out).Decode(&m) 150 if enabled { 151 if err != nil { 152 t.Fatalf("Failed to json decode log output: %s", err.Error()) 153 } 154 if m[zerolog.LevelFieldName] != l.zlvl.String() { 155 t.Fatalf("Unexpected value for field %s. Got %s but expected %s", zerolog.LevelFieldName, m[zerolog.LevelFieldName], l.zlvl.String()) 156 } 157 } else { 158 if !errors.Is(err, io.EOF) { 159 t.Fatalf("Expected io.EOF error but got %s", err) 160 } 161 } 162 } 163 164 }) 165 } 166 } 167 168 func TestZerolog_NoGroup(t *testing.T) { 169 out := bytes.Buffer{} 170 hdl := NewJsonHandler(&out, nil). 171 WithAttrs([]slog.Attr{slog.String("attr", "the attr")}) 172 173 if !hdl.Enabled(nil, slog.LevelError) { 174 t.Errorf("Level %s must be enabled", slog.LevelError) 175 } 176 if hdl.Enabled(nil, slog.LevelDebug) { 177 t.Errorf("Level %s must be disabled", slog.LevelDebug) 178 } 179 180 rec := slog.NewRecord(now, slog.LevelError, "foobar", 0) 181 rec.AddAttrs(attrs...) 182 hdl.Handle(nil, rec) 183 184 expected := maps.Clone(exp) 185 expected[zerolog.LevelFieldName] = zerolog.LevelErrorValue 186 expected[zerolog.MessageFieldName] = "foobar" 187 expected[zerolog.TimestampFieldName] = now.Format(time.RFC3339) 188 expected["attr"] = "the attr" 189 190 m := map[string]any{} 191 if err := json.NewDecoder(&out).Decode(&m); err != nil { 192 t.Fatalf("Failed to json decode log output: %s", err.Error()) 193 } 194 if !reflect.DeepEqual(expected, m) { 195 t.Fatalf("Unexpected fields. Got %v, expected %v", m, expected) 196 } 197 } 198 199 func TestZerolog_Group(t *testing.T) { 200 out := bytes.Buffer{} 201 hdl := NewJsonHandler(&out, nil). 202 WithAttrs([]slog.Attr{slog.String("attr", "the attr")}). 203 WithGroup("testgroup"). 204 WithAttrs([]slog.Attr{slog.String("attr", "the attr")}). 205 WithGroup("subgroup") 206 207 if !hdl.Enabled(nil, slog.LevelError) { 208 t.Errorf("Level %s must be enabled", slog.LevelError) 209 } 210 if hdl.Enabled(nil, slog.LevelDebug) { 211 t.Errorf("Level %s must be disabled", slog.LevelDebug) 212 } 213 214 rec := slog.NewRecord(now, slog.LevelWarn, "foobar", 0) 215 rec.AddAttrs(attrs...) 216 hdl.Handle(nil, rec) 217 218 expected := map[string]any{ 219 zerolog.LevelFieldName: zerolog.LevelWarnValue, 220 zerolog.MessageFieldName: "foobar", 221 zerolog.TimestampFieldName: now.Format(time.RFC3339), 222 "attr": "the attr", 223 "testgroup": map[string]any{ 224 "attr": "the attr", 225 "subgroup": maps.Clone(exp), 226 }, 227 } 228 229 m := map[string]any{} 230 if err := json.NewDecoder(&out).Decode(&m); err != nil { 231 t.Fatalf("Failed to json decode log output: %s", err.Error()) 232 } 233 if !reflect.DeepEqual(expected, m) { 234 t.Fatalf("Unexpected fields. Got %v, expected %v", m, expected) 235 } 236 } 237 238 func TestZerolog_AddSource(t *testing.T) { 239 out := bytes.Buffer{} 240 hdl := NewJsonHandler(&out, &HandlerOptions{AddSource: true}) 241 pc, file, line, _ := runtime.Caller(0) 242 hdl.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "foobar", pc)) 243 m := map[string]any{} 244 if err := json.NewDecoder(&out).Decode(&m); err != nil { 245 t.Fatalf("Failed to json decode log output: %s", err.Error()) 246 } 247 if m[zerolog.CallerFieldName].(string) != fmt.Sprintf("%s:%d", file, line) { 248 t.Fatalf("Unexpected field %s: %s", zerolog.CallerFieldName, m[zerolog.CallerFieldName].(string)) 249 } 250 } 251 252 func TestZerolog_ConsoleHandler(t *testing.T) { 253 out := bytes.Buffer{} 254 hdl := NewConsoleHandler(&out, nil) 255 hdl.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "foobar", 0)) 256 txt := out.String() 257 if !strings.Contains(txt, "foobar") || !strings.Contains(txt, "INF") { 258 t.Errorf("Unexpected console output %q", txt) 259 } 260 } 261 262 // TestHandler uses slogtest.TestHandler from stdlib to validate 263 // the zerolog handler implementation. 264 func TestHandler(t *testing.T) { 265 out := bytes.Buffer{} 266 dec := json.NewDecoder(&out) 267 hdl := NewJsonHandler(&out, &HandlerOptions{Level: slog.LevelDebug}) 268 err := slogtest.TestHandler(hdl, func() []map[string]any { 269 results := []map[string]any{} 270 m := map[string]any{} 271 for dec.Decode(&m) != io.EOF { 272 results = append(results, m) 273 m = map[string]any{} 274 } 275 return results 276 }) 277 if err != nil { 278 t.Fatal(err) 279 } 280 }