Append ErrMissingValue to odd length keyvals rather than panic.
Chris Hines
8 years ago
16 | 16 | } |
17 | 17 | |
18 | 18 | func (l *jsonLogger) Log(keyvals ...interface{}) error { |
19 | if len(keyvals)%2 == 1 { | |
20 | panic("odd number of keyvals") | |
21 | } | |
22 | m := make(map[string]interface{}, len(keyvals)/2) | |
19 | n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd | |
20 | m := make(map[string]interface{}, n) | |
23 | 21 | for i := 0; i < len(keyvals); i += 2 { |
24 | merge(m, keyvals[i], keyvals[i+1]) | |
22 | k := keyvals[i] | |
23 | v := ErrMissingValue | |
24 | if i+1 < len(keyvals) { | |
25 | v = keyvals[i+1] | |
26 | } | |
27 | merge(m, k, v) | |
25 | 28 | } |
26 | 29 | return json.NewEncoder(l.Writer).Encode(m) |
27 | 30 | } |
9 | 9 | ) |
10 | 10 | |
11 | 11 | func TestJSONLogger(t *testing.T) { |
12 | t.Parallel() | |
12 | 13 | buf := &bytes.Buffer{} |
13 | 14 | logger := log.NewJSONLogger(buf) |
14 | 15 | if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil { |
15 | 16 | t.Fatal(err) |
16 | 17 | } |
17 | 18 | if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have { |
18 | t.Errorf("want %#v, have %#v", want, have) | |
19 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
20 | } | |
21 | } | |
22 | ||
23 | func TestJSONLoggerMissingValue(t *testing.T) { | |
24 | t.Parallel() | |
25 | buf := &bytes.Buffer{} | |
26 | logger := log.NewJSONLogger(buf) | |
27 | if err := logger.Log("k"); err != nil { | |
28 | t.Fatal(err) | |
29 | } | |
30 | if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have { | |
31 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
19 | 32 | } |
20 | 33 | } |
21 | 34 |
3 | 3 | // key/value data. |
4 | 4 | package log |
5 | 5 | |
6 | import "sync/atomic" | |
6 | import ( | |
7 | "errors" | |
8 | "sync/atomic" | |
9 | ) | |
7 | 10 | |
8 | 11 | // Logger is the fundamental interface for all log operations. Log creates a |
9 | 12 | // log event from keyvals, a variadic sequence of alternating keys and values. |
13 | 16 | type Logger interface { |
14 | 17 | Log(keyvals ...interface{}) error |
15 | 18 | } |
19 | ||
20 | // ErrMissingValue may be appended to keyval slices with an odd length to | |
21 | // populate the missing value. | |
22 | var ErrMissingValue interface{} = errors.New("(MISSING)") | |
16 | 23 | |
17 | 24 | // NewContext returns a new Context that logs to logger. |
18 | 25 | func NewContext(logger Logger) Context { |
36 | 43 | // stored context with their generated value, appends keyvals, and passes the |
37 | 44 | // result to the wrapped Logger. |
38 | 45 | func (l Context) Log(keyvals ...interface{}) error { |
39 | if len(keyvals)%2 != 0 { | |
40 | panic("bad keyvals") | |
46 | kvs := append(l.keyvals, keyvals...) | |
47 | if len(kvs)%2 != 0 { | |
48 | kvs = append(kvs, ErrMissingValue) | |
41 | 49 | } |
42 | kvs := append(l.keyvals, keyvals...) | |
43 | 50 | if l.hasValuer { |
44 | 51 | // If no keyvals were appended above then we must copy l.keyvals so |
45 | 52 | // that future log events will reevaluate the stored Valuers. |
56 | 63 | if len(keyvals) == 0 { |
57 | 64 | return l |
58 | 65 | } |
59 | if len(keyvals)%2 != 0 { | |
60 | panic("bad keyvals") | |
66 | kvs := append(l.keyvals, keyvals...) | |
67 | if len(kvs)%2 != 0 { | |
68 | kvs = append(kvs, ErrMissingValue) | |
61 | 69 | } |
62 | // Limiting the capacity of the stored keyvals ensures that a new | |
63 | // backing array is created if the slice must grow in Log or With. | |
64 | // Using the extra capacity without copying risks a data race that | |
65 | // would violate the Logger interface contract. | |
66 | n := len(l.keyvals) + len(keyvals) | |
67 | 70 | return Context{ |
68 | logger: l.logger, | |
69 | keyvals: append(l.keyvals, keyvals...)[:n:n], | |
71 | logger: l.logger, | |
72 | // Limiting the capacity of the stored keyvals ensures that a new | |
73 | // backing array is created if the slice must grow in Log or With. | |
74 | // Using the extra capacity without copying risks a data race that | |
75 | // would violate the Logger interface contract. | |
76 | keyvals: kvs[:len(kvs):len(kvs)], | |
70 | 77 | hasValuer: l.hasValuer || containsValuer(keyvals), |
71 | 78 | } |
72 | 79 | } |
77 | 84 | if len(keyvals) == 0 { |
78 | 85 | return l |
79 | 86 | } |
80 | if len(keyvals)%2 != 0 { | |
81 | panic("bad keyvals") | |
82 | } | |
83 | 87 | // Limiting the capacity of the stored keyvals ensures that a new |
84 | 88 | // backing array is created if the slice must grow in Log or With. |
85 | 89 | // Using the extra capacity without copying risks a data race that |
86 | 90 | // would violate the Logger interface contract. |
87 | 91 | n := len(l.keyvals) + len(keyvals) |
92 | if len(keyvals)%2 != 0 { | |
93 | n++ | |
94 | } | |
88 | 95 | kvs := make([]interface{}, 0, n) |
89 | 96 | kvs = append(kvs, keyvals...) |
97 | if len(kvs)%2 != 0 { | |
98 | kvs = append(kvs, ErrMissingValue) | |
99 | } | |
90 | 100 | kvs = append(kvs, l.keyvals...) |
91 | 101 | return Context{ |
92 | 102 | logger: l.logger, |
10 | 10 | var discard = log.Logger(log.LoggerFunc(func(...interface{}) error { return nil })) |
11 | 11 | |
12 | 12 | func TestContext(t *testing.T) { |
13 | t.Parallel() | |
13 | 14 | buf := &bytes.Buffer{} |
14 | 15 | logger := log.NewLogfmtLogger(buf) |
15 | 16 | |
32 | 33 | } |
33 | 34 | if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have { |
34 | 35 | t.Errorf("\nwant: %shave: %s", want, have) |
36 | } | |
37 | } | |
38 | ||
39 | func TestContextMissingValue(t *testing.T) { | |
40 | t.Parallel() | |
41 | var output []interface{} | |
42 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
43 | output = keyvals | |
44 | return nil | |
45 | })) | |
46 | ||
47 | lc := log.NewContext(logger) | |
48 | ||
49 | lc.Log("k") | |
50 | if want, have := 2, len(output); want != have { | |
51 | t.Errorf("want len(output) == %v, have %v", want, have) | |
52 | } | |
53 | if want, have := log.ErrMissingValue, output[1]; want != have { | |
54 | t.Errorf("want %#v, have %#v", want, have) | |
55 | } | |
56 | ||
57 | lc.With("k1").WithPrefix("k0").Log("k2") | |
58 | if want, have := 6, len(output); want != have { | |
59 | t.Errorf("want len(output) == %v, have %v", want, have) | |
60 | } | |
61 | for i := 1; i < 6; i += 2 { | |
62 | if want, have := log.ErrMissingValue, output[i]; want != have { | |
63 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
64 | } | |
35 | 65 | } |
36 | 66 | } |
37 | 67 |