1package zerolog
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"net"
8	"reflect"
9	"runtime"
10	"strconv"
11	"strings"
12	"testing"
13	"time"
14)
15
16func TestLog(t *testing.T) {
17	t.Run("empty", func(t *testing.T) {
18		out := &bytes.Buffer{}
19		log := New(out)
20		log.Log().Msg("")
21		if got, want := decodeIfBinaryToString(out.Bytes()), "{}\n"; got != want {
22			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
23		}
24	})
25
26	t.Run("one-field", func(t *testing.T) {
27		out := &bytes.Buffer{}
28		log := New(out)
29		log.Log().Str("foo", "bar").Msg("")
30		if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar"}`+"\n"; got != want {
31			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
32		}
33	})
34
35	t.Run("two-field", func(t *testing.T) {
36		out := &bytes.Buffer{}
37		log := New(out)
38		log.Log().
39			Str("foo", "bar").
40			Int("n", 123).
41			Msg("")
42		if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","n":123}`+"\n"; got != want {
43			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
44		}
45	})
46}
47
48func TestInfo(t *testing.T) {
49	t.Run("empty", func(t *testing.T) {
50		out := &bytes.Buffer{}
51		log := New(out)
52		log.Info().Msg("")
53		if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info"}`+"\n"; got != want {
54			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
55		}
56	})
57
58	t.Run("one-field", func(t *testing.T) {
59		out := &bytes.Buffer{}
60		log := New(out)
61		log.Info().Str("foo", "bar").Msg("")
62		if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","foo":"bar"}`+"\n"; got != want {
63			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
64		}
65	})
66
67	t.Run("two-field", func(t *testing.T) {
68		out := &bytes.Buffer{}
69		log := New(out)
70		log.Info().
71			Str("foo", "bar").
72			Int("n", 123).
73			Msg("")
74		if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","foo":"bar","n":123}`+"\n"; got != want {
75			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
76		}
77	})
78}
79
80func TestWith(t *testing.T) {
81	out := &bytes.Buffer{}
82	ctx := New(out).With().
83		Str("string", "foo").
84		Bytes("bytes", []byte("bar")).
85		Hex("hex", []byte{0x12, 0xef}).
86		RawJSON("json", []byte(`{"some":"json"}`)).
87		AnErr("some_err", nil).
88		Err(errors.New("some error")).
89		Bool("bool", true).
90		Int("int", 1).
91		Int8("int8", 2).
92		Int16("int16", 3).
93		Int32("int32", 4).
94		Int64("int64", 5).
95		Uint("uint", 6).
96		Uint8("uint8", 7).
97		Uint16("uint16", 8).
98		Uint32("uint32", 9).
99		Uint64("uint64", 10).
100		Float32("float32", 11.101).
101		Float64("float64", 12.30303).
102		Time("time", time.Time{})
103	_, file, line, _ := runtime.Caller(0)
104	caller := fmt.Sprintf("%s:%d", file, line+3)
105	log := ctx.Caller().Logger()
106	log.Log().Msg("")
107	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
108		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
109	}
110
111	// Validate CallerWithSkipFrameCount.
112	out.Reset()
113	_, file, line, _ = runtime.Caller(0)
114	caller = fmt.Sprintf("%s:%d", file, line+5)
115	log = ctx.CallerWithSkipFrameCount(3).Logger()
116	func() {
117		log.Log().Msg("")
118	}()
119	// The above line is a little contrived, but the line above should be the line due
120	// to the extra frame skip.
121	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
122		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
123	}
124}
125
126func TestFieldsMap(t *testing.T) {
127	out := &bytes.Buffer{}
128	log := New(out)
129	log.Log().Fields(map[string]interface{}{
130		"nil":     nil,
131		"string":  "foo",
132		"bytes":   []byte("bar"),
133		"error":   errors.New("some error"),
134		"bool":    true,
135		"int":     int(1),
136		"int8":    int8(2),
137		"int16":   int16(3),
138		"int32":   int32(4),
139		"int64":   int64(5),
140		"uint":    uint(6),
141		"uint8":   uint8(7),
142		"uint16":  uint16(8),
143		"uint32":  uint32(9),
144		"uint64":  uint64(10),
145		"float32": float32(11),
146		"float64": float64(12),
147		"ipv6":    net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34},
148		"dur":     1 * time.Second,
149		"time":    time.Time{},
150		"obj":     obj{"a", "b", 1},
151	}).Msg("")
152	if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"ipv6":"2001:db8:85a3::8a2e:370:7334","nil":null,"obj":{"Pub":"a","Tag":"b","priv":1},"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want {
153		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
154	}
155}
156
157func TestFieldsMapPnt(t *testing.T) {
158	out := &bytes.Buffer{}
159	log := New(out)
160	log.Log().Fields(map[string]interface{}{
161		"string":  new(string),
162		"bool":    new(bool),
163		"int":     new(int),
164		"int8":    new(int8),
165		"int16":   new(int16),
166		"int32":   new(int32),
167		"int64":   new(int64),
168		"uint":    new(uint),
169		"uint8":   new(uint8),
170		"uint16":  new(uint16),
171		"uint32":  new(uint32),
172		"uint64":  new(uint64),
173		"float32": new(float32),
174		"float64": new(float64),
175		"dur":     new(time.Duration),
176		"time":    new(time.Time),
177	}).Msg("")
178	if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":false,"dur":0,"float32":0,"float64":0,"int":0,"int16":0,"int32":0,"int64":0,"int8":0,"string":"","time":"0001-01-01T00:00:00Z","uint":0,"uint16":0,"uint32":0,"uint64":0,"uint8":0}`+"\n"; got != want {
179		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
180	}
181}
182
183func TestFieldsMapNilPnt(t *testing.T) {
184	var (
185		stringPnt  *string
186		boolPnt    *bool
187		intPnt     *int
188		int8Pnt    *int8
189		int16Pnt   *int16
190		int32Pnt   *int32
191		int64Pnt   *int64
192		uintPnt    *uint
193		uint8Pnt   *uint8
194		uint16Pnt  *uint16
195		uint32Pnt  *uint32
196		uint64Pnt  *uint64
197		float32Pnt *float32
198		float64Pnt *float64
199		durPnt     *time.Duration
200		timePnt    *time.Time
201	)
202	out := &bytes.Buffer{}
203	log := New(out)
204	fields := map[string]interface{}{
205		"string":  stringPnt,
206		"bool":    boolPnt,
207		"int":     intPnt,
208		"int8":    int8Pnt,
209		"int16":   int16Pnt,
210		"int32":   int32Pnt,
211		"int64":   int64Pnt,
212		"uint":    uintPnt,
213		"uint8":   uint8Pnt,
214		"uint16":  uint16Pnt,
215		"uint32":  uint32Pnt,
216		"uint64":  uint64Pnt,
217		"float32": float32Pnt,
218		"float64": float64Pnt,
219		"dur":     durPnt,
220		"time":    timePnt,
221	}
222
223	log.Log().Fields(fields).Msg("")
224	if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":null,"dur":null,"float32":null,"float64":null,"int":null,"int16":null,"int32":null,"int64":null,"int8":null,"string":null,"time":null,"uint":null,"uint16":null,"uint32":null,"uint64":null,"uint8":null}`+"\n"; got != want {
225		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
226	}
227}
228
229func TestFields(t *testing.T) {
230	out := &bytes.Buffer{}
231	log := New(out)
232	now := time.Now()
233	_, file, line, _ := runtime.Caller(0)
234	caller := fmt.Sprintf("%s:%d", file, line+3)
235	log.Log().
236		Caller().
237		Str("string", "foo").
238		Bytes("bytes", []byte("bar")).
239		Hex("hex", []byte{0x12, 0xef}).
240		RawJSON("json", []byte(`{"some":"json"}`)).
241		AnErr("some_err", nil).
242		Err(errors.New("some error")).
243		Bool("bool", true).
244		Int("int", 1).
245		Int8("int8", 2).
246		Int16("int16", 3).
247		Int32("int32", 4).
248		Int64("int64", 5).
249		Uint("uint", 6).
250		Uint8("uint8", 7).
251		Uint16("uint16", 8).
252		Uint32("uint32", 9).
253		Uint64("uint64", 10).
254		IPAddr("IPv4", net.IP{192, 168, 0, 100}).
255		IPAddr("IPv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}).
256		MACAddr("Mac", net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45}).
257		IPPrefix("Prefix", net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}).
258		Float32("float32", 11.1234).
259		Float64("float64", 12.321321321).
260		Dur("dur", 1*time.Second).
261		Time("time", time.Time{}).
262		TimeDiff("diff", now, now.Add(-10*time.Second)).
263		Msg("")
264	if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
265		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
266	}
267}
268
269func TestFieldsArrayEmpty(t *testing.T) {
270	out := &bytes.Buffer{}
271	log := New(out)
272	log.Log().
273		Strs("string", []string{}).
274		Errs("err", []error{}).
275		Bools("bool", []bool{}).
276		Ints("int", []int{}).
277		Ints8("int8", []int8{}).
278		Ints16("int16", []int16{}).
279		Ints32("int32", []int32{}).
280		Ints64("int64", []int64{}).
281		Uints("uint", []uint{}).
282		Uints8("uint8", []uint8{}).
283		Uints16("uint16", []uint16{}).
284		Uints32("uint32", []uint32{}).
285		Uints64("uint64", []uint64{}).
286		Floats32("float32", []float32{}).
287		Floats64("float64", []float64{}).
288		Durs("dur", []time.Duration{}).
289		Times("time", []time.Time{}).
290		Msg("")
291	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
292		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
293	}
294}
295
296func TestFieldsArraySingleElement(t *testing.T) {
297	out := &bytes.Buffer{}
298	log := New(out)
299	log.Log().
300		Strs("string", []string{"foo"}).
301		Errs("err", []error{errors.New("some error")}).
302		Bools("bool", []bool{true}).
303		Ints("int", []int{1}).
304		Ints8("int8", []int8{2}).
305		Ints16("int16", []int16{3}).
306		Ints32("int32", []int32{4}).
307		Ints64("int64", []int64{5}).
308		Uints("uint", []uint{6}).
309		Uints8("uint8", []uint8{7}).
310		Uints16("uint16", []uint16{8}).
311		Uints32("uint32", []uint32{9}).
312		Uints64("uint64", []uint64{10}).
313		Floats32("float32", []float32{11}).
314		Floats64("float64", []float64{12}).
315		Durs("dur", []time.Duration{1 * time.Second}).
316		Times("time", []time.Time{time.Time{}}).
317		Msg("")
318	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want {
319		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
320	}
321}
322
323func TestFieldsArrayMultipleElement(t *testing.T) {
324	out := &bytes.Buffer{}
325	log := New(out)
326	log.Log().
327		Strs("string", []string{"foo", "bar"}).
328		Errs("err", []error{errors.New("some error"), nil}).
329		Bools("bool", []bool{true, false}).
330		Ints("int", []int{1, 0}).
331		Ints8("int8", []int8{2, 0}).
332		Ints16("int16", []int16{3, 0}).
333		Ints32("int32", []int32{4, 0}).
334		Ints64("int64", []int64{5, 0}).
335		Uints("uint", []uint{6, 0}).
336		Uints8("uint8", []uint8{7, 0}).
337		Uints16("uint16", []uint16{8, 0}).
338		Uints32("uint32", []uint32{9, 0}).
339		Uints64("uint64", []uint64{10, 0}).
340		Floats32("float32", []float32{11, 0}).
341		Floats64("float64", []float64{12, 0}).
342		Durs("dur", []time.Duration{1 * time.Second, 0}).
343		Times("time", []time.Time{time.Time{}, time.Time{}}).
344		Msg("")
345	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want {
346		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
347	}
348}
349
350func TestFieldsDisabled(t *testing.T) {
351	out := &bytes.Buffer{}
352	log := New(out).Level(InfoLevel)
353	now := time.Now()
354	log.Debug().
355		Str("string", "foo").
356		Bytes("bytes", []byte("bar")).
357		Hex("hex", []byte{0x12, 0xef}).
358		AnErr("some_err", nil).
359		Err(errors.New("some error")).
360		Bool("bool", true).
361		Int("int", 1).
362		Int8("int8", 2).
363		Int16("int16", 3).
364		Int32("int32", 4).
365		Int64("int64", 5).
366		Uint("uint", 6).
367		Uint8("uint8", 7).
368		Uint16("uint16", 8).
369		Uint32("uint32", 9).
370		Uint64("uint64", 10).
371		Float32("float32", 11).
372		Float64("float64", 12).
373		Dur("dur", 1*time.Second).
374		Time("time", time.Time{}).
375		TimeDiff("diff", now, now.Add(-10*time.Second)).
376		Msg("")
377	if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
378		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
379	}
380}
381
382func TestMsgf(t *testing.T) {
383	out := &bytes.Buffer{}
384	log := New(out)
385	log.Log().Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
386	if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"one two 3.4 5 six"}`+"\n"; got != want {
387		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
388	}
389}
390
391func TestWithAndFieldsCombined(t *testing.T) {
392	out := &bytes.Buffer{}
393	log := New(out).With().Str("f1", "val").Str("f2", "val").Logger()
394	log.Log().Str("f3", "val").Msg("")
395	if got, want := decodeIfBinaryToString(out.Bytes()), `{"f1":"val","f2":"val","f3":"val"}`+"\n"; got != want {
396		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
397	}
398}
399
400func TestLevel(t *testing.T) {
401	t.Run("Disabled", func(t *testing.T) {
402		out := &bytes.Buffer{}
403		log := New(out).Level(Disabled)
404		log.Info().Msg("test")
405		if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
406			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
407		}
408	})
409
410	t.Run("NoLevel/Disabled", func(t *testing.T) {
411		out := &bytes.Buffer{}
412		log := New(out).Level(Disabled)
413		log.Log().Msg("test")
414		if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
415			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
416		}
417	})
418
419	t.Run("NoLevel/Info", func(t *testing.T) {
420		out := &bytes.Buffer{}
421		log := New(out).Level(InfoLevel)
422		log.Log().Msg("test")
423		if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
424			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
425		}
426	})
427
428	t.Run("NoLevel/Panic", func(t *testing.T) {
429		out := &bytes.Buffer{}
430		log := New(out).Level(PanicLevel)
431		log.Log().Msg("test")
432		if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
433			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
434		}
435	})
436
437	t.Run("NoLevel/WithLevel", func(t *testing.T) {
438		out := &bytes.Buffer{}
439		log := New(out).Level(InfoLevel)
440		log.WithLevel(NoLevel).Msg("test")
441		if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
442			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
443		}
444	})
445
446	t.Run("Info", func(t *testing.T) {
447		out := &bytes.Buffer{}
448		log := New(out).Level(InfoLevel)
449		log.Info().Msg("test")
450		if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","message":"test"}`+"\n"; got != want {
451			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
452		}
453	})
454}
455
456func TestSampling(t *testing.T) {
457	out := &bytes.Buffer{}
458	log := New(out).Sample(&BasicSampler{N: 2})
459	log.Log().Int("i", 1).Msg("")
460	log.Log().Int("i", 2).Msg("")
461	log.Log().Int("i", 3).Msg("")
462	log.Log().Int("i", 4).Msg("")
463	if got, want := decodeIfBinaryToString(out.Bytes()), "{\"i\":1}\n{\"i\":3}\n"; got != want {
464		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
465	}
466}
467
468func TestDiscard(t *testing.T) {
469	out := &bytes.Buffer{}
470	log := New(out)
471	log.Log().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
472	if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
473		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
474	}
475
476	// Double call
477	log.Log().Discard().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
478	if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
479		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
480	}
481}
482
483type levelWriter struct {
484	ops []struct {
485		l Level
486		p string
487	}
488}
489
490func (lw *levelWriter) Write(p []byte) (int, error) {
491	return len(p), nil
492}
493
494func (lw *levelWriter) WriteLevel(lvl Level, p []byte) (int, error) {
495	p = decodeIfBinaryToBytes(p)
496	lw.ops = append(lw.ops, struct {
497		l Level
498		p string
499	}{lvl, string(p)})
500	return len(p), nil
501}
502
503func TestLevelWriter(t *testing.T) {
504	lw := &levelWriter{
505		ops: []struct {
506			l Level
507			p string
508		}{},
509	}
510	log := New(lw)
511	log.Debug().Msg("1")
512	log.Info().Msg("2")
513	log.Warn().Msg("3")
514	log.Error().Msg("4")
515	log.Log().Msg("nolevel-1")
516	log.WithLevel(DebugLevel).Msg("5")
517	log.WithLevel(InfoLevel).Msg("6")
518	log.WithLevel(WarnLevel).Msg("7")
519	log.WithLevel(ErrorLevel).Msg("8")
520	log.WithLevel(NoLevel).Msg("nolevel-2")
521
522	want := []struct {
523		l Level
524		p string
525	}{
526		{DebugLevel, `{"level":"debug","message":"1"}` + "\n"},
527		{InfoLevel, `{"level":"info","message":"2"}` + "\n"},
528		{WarnLevel, `{"level":"warn","message":"3"}` + "\n"},
529		{ErrorLevel, `{"level":"error","message":"4"}` + "\n"},
530		{NoLevel, `{"message":"nolevel-1"}` + "\n"},
531		{DebugLevel, `{"level":"debug","message":"5"}` + "\n"},
532		{InfoLevel, `{"level":"info","message":"6"}` + "\n"},
533		{WarnLevel, `{"level":"warn","message":"7"}` + "\n"},
534		{ErrorLevel, `{"level":"error","message":"8"}` + "\n"},
535		{NoLevel, `{"message":"nolevel-2"}` + "\n"},
536	}
537	if got := lw.ops; !reflect.DeepEqual(got, want) {
538		t.Errorf("invalid ops:\ngot:\n%v\nwant:\n%v", got, want)
539	}
540}
541
542func TestContextTimestamp(t *testing.T) {
543	TimestampFunc = func() time.Time {
544		return time.Date(2001, time.February, 3, 4, 5, 6, 7, time.UTC)
545	}
546	defer func() {
547		TimestampFunc = time.Now
548	}()
549	out := &bytes.Buffer{}
550	log := New(out).With().Timestamp().Str("foo", "bar").Logger()
551	log.Log().Msg("hello world")
552
553	if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
554		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
555	}
556}
557
558func TestEventTimestamp(t *testing.T) {
559	TimestampFunc = func() time.Time {
560		return time.Date(2001, time.February, 3, 4, 5, 6, 7, time.UTC)
561	}
562	defer func() {
563		TimestampFunc = time.Now
564	}()
565	out := &bytes.Buffer{}
566	log := New(out).With().Str("foo", "bar").Logger()
567	log.Log().Timestamp().Msg("hello world")
568
569	if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
570		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
571	}
572}
573
574func TestOutputWithoutTimestamp(t *testing.T) {
575	ignoredOut := &bytes.Buffer{}
576	out := &bytes.Buffer{}
577	log := New(ignoredOut).Output(out).With().Str("foo", "bar").Logger()
578	log.Log().Msg("hello world")
579
580	if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","message":"hello world"}`+"\n"; got != want {
581		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
582	}
583}
584
585func TestOutputWithTimestamp(t *testing.T) {
586	TimestampFunc = func() time.Time {
587		return time.Date(2001, time.February, 3, 4, 5, 6, 7, time.UTC)
588	}
589	defer func() {
590		TimestampFunc = time.Now
591	}()
592	ignoredOut := &bytes.Buffer{}
593	out := &bytes.Buffer{}
594	log := New(ignoredOut).Output(out).With().Timestamp().Str("foo", "bar").Logger()
595	log.Log().Msg("hello world")
596
597	if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
598		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
599	}
600}
601
602type loggableError struct {
603	error
604}
605
606func (l loggableError) MarshalZerologObject(e *Event) {
607	e.Str("message", l.error.Error()+": loggableError")
608}
609
610func TestErrorMarshalFunc(t *testing.T) {
611	out := &bytes.Buffer{}
612	log := New(out)
613
614	// test default behaviour
615	log.Log().Err(errors.New("err")).Msg("msg")
616	if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err","message":"msg"}`+"\n"; got != want {
617		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
618	}
619	out.Reset()
620
621	log.Log().Err(loggableError{errors.New("err")}).Msg("msg")
622	if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want {
623		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
624	}
625	out.Reset()
626
627	// test overriding the ErrorMarshalFunc
628	originalErrorMarshalFunc := ErrorMarshalFunc
629	defer func() {
630		ErrorMarshalFunc = originalErrorMarshalFunc
631	}()
632
633	ErrorMarshalFunc = func(err error) interface{} {
634		return err.Error() + ": marshaled string"
635	}
636	log.Log().Err(errors.New("err")).Msg("msg")
637	if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: marshaled string","message":"msg"}`+"\n"; got != want {
638		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
639	}
640
641	out.Reset()
642	ErrorMarshalFunc = func(err error) interface{} {
643		return errors.New(err.Error() + ": new error")
644	}
645	log.Log().Err(errors.New("err")).Msg("msg")
646	if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: new error","message":"msg"}`+"\n"; got != want {
647		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
648	}
649
650	out.Reset()
651	ErrorMarshalFunc = func(err error) interface{} {
652		return loggableError{err}
653	}
654	log.Log().Err(errors.New("err")).Msg("msg")
655	if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want {
656		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
657	}
658}
659
660func TestCallerMarshalFunc(t *testing.T) {
661	out := &bytes.Buffer{}
662	log := New(out)
663
664	// test default behaviour this is really brittle due to the line numbers
665	// actually mattering for validation
666	_, file, line, _ := runtime.Caller(0)
667	caller := fmt.Sprintf("%s:%d", file, line+2)
668	log.Log().Caller().Msg("msg")
669	if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
670		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
671	}
672	out.Reset()
673
674	// test custom behavior. In this case we'll take just the last directory
675	origCallerMarshalFunc := CallerMarshalFunc
676	defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
677	CallerMarshalFunc = func(file string, line int) string {
678		parts := strings.Split(file, "/")
679		if len(parts) > 1 {
680			return strings.Join(parts[len(parts)-2:], "/") + ":" + strconv.Itoa(line)
681		} else {
682			return file + ":" + strconv.Itoa(line)
683		}
684	}
685	_, file, line, _ = runtime.Caller(0)
686	caller = CallerMarshalFunc(file, line+2)
687	log.Log().Caller().Msg("msg")
688	if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","message":"msg"}`+"\n"; got != want {
689		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
690	}
691}
692
693func TestLevelFieldMarshalFunc(t *testing.T) {
694	origLevelFieldMarshalFunc := LevelFieldMarshalFunc
695	LevelFieldMarshalFunc = func(l Level) string {
696		return strings.ToUpper(l.String())
697	}
698	defer func() {
699		LevelFieldMarshalFunc = origLevelFieldMarshalFunc
700	}()
701	out := &bytes.Buffer{}
702	log := New(out)
703
704	log.Debug().Msg("test")
705	if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"DEBUG","message":"test"}`+"\n"; got != want {
706		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
707	}
708	out.Reset()
709
710	log.Info().Msg("test")
711	if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"INFO","message":"test"}`+"\n"; got != want {
712		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
713	}
714	out.Reset()
715
716	log.Warn().Msg("test")
717	if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"WARN","message":"test"}`+"\n"; got != want {
718		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
719	}
720	out.Reset()
721
722	log.Error().Msg("test")
723	if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"ERROR","message":"test"}`+"\n"; got != want {
724		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
725	}
726	out.Reset()
727}
728
729type errWriter struct {
730	error
731}
732
733func (w errWriter) Write(p []byte) (n int, err error) {
734	return 0, w.error
735}
736
737func TestErrorHandler(t *testing.T) {
738	var got error
739	want := errors.New("write error")
740	ErrorHandler = func(err error) {
741		got = err
742	}
743	log := New(errWriter{want})
744	log.Log().Msg("test")
745	if got != want {
746		t.Errorf("ErrorHandler err = %#v, want %#v", got, want)
747	}
748}
749