New upstream version 1.9.1
aviau
5 years ago
0 | # Compiled Object files, Static and Dynamic libs (Shared Objects) | |
1 | *.o | |
2 | *.a | |
3 | *.so | |
4 | ||
5 | # Folders | |
6 | _obj | |
7 | _test | |
8 | vendor | |
9 | ||
10 | # Architecture specific extensions/prefixes | |
11 | *.[568vq] | |
12 | [568vq].out | |
13 | ||
14 | *.cgo1.go | |
15 | *.cgo2.c | |
16 | _cgo_defun.c | |
17 | _cgo_gotypes.go | |
18 | _cgo_export.* | |
19 | ||
20 | _testmain.go | |
21 | ||
22 | *.exe | |
23 | *.test | |
24 | *.prof | |
25 | *.pprof | |
26 | *.out | |
27 | *.log |
10 | 10 | ## Quick Start |
11 | 11 | |
12 | 12 | In contexts where performance is nice, but not critical, use the |
13 | `SugaredLogger`. It's 4-10x faster than than other structured logging | |
13 | `SugaredLogger`. It's 4-10x faster than other structured logging | |
14 | 14 | packages and includes both structured and `printf`-style APIs. |
15 | 15 | |
16 | 16 | ```go |
0 | 0 | language: go |
1 | 1 | sudo: false |
2 | 2 | go: |
3 | - 1.8 | |
4 | - 1.9 | |
3 | - 1.9.x | |
4 | - 1.10.x | |
5 | 5 | go_import_path: go.uber.org/zap |
6 | 6 | env: |
7 | 7 | global: |
0 | 0 | # Changelog |
1 | ||
2 | ## v1.9.1 (06 Aug 2018) | |
3 | ||
4 | Bugfixes: | |
5 | ||
6 | * [#614][]: MapObjectEncoder should not ignore empty slices. | |
7 | ||
8 | ## v1.9.0 (19 Jul 2018) | |
9 | ||
10 | Enhancements: | |
11 | * [#602][]: Reduce number of allocations when logging with reflection. | |
12 | * [#572][], [#606][]: Expose a registry for third-party logging sinks. | |
13 | ||
14 | Thanks to @nfarah86, @AlekSi, @JeanMertz, @philippgille, @etsangsplk, and | |
15 | @dimroc for their contributions to this release. | |
16 | ||
17 | ## v1.8.0 (13 Apr 2018) | |
18 | ||
19 | Enhancements: | |
20 | * [#508][]: Make log level configurable when redirecting the standard | |
21 | library's logger. | |
22 | * [#518][]: Add a logger that writes to a `*testing.TB`. | |
23 | * [#577][]: Add a top-level alias for `zapcore.Field` to clean up GoDoc. | |
24 | ||
25 | Bugfixes: | |
26 | * [#574][]: Add a missing import comment to `go.uber.org/zap/buffer`. | |
27 | ||
28 | Thanks to @DiSiqueira and @djui for their contributions to this release. | |
1 | 29 | |
2 | 30 | ## v1.7.1 (25 Sep 2017) |
3 | 31 | |
265 | 293 | [#487]: https://github.com/uber-go/zap/pull/487 |
266 | 294 | [#490]: https://github.com/uber-go/zap/pull/490 |
267 | 295 | [#491]: https://github.com/uber-go/zap/pull/491 |
268 | [#491]: https://github.com/uber-go/zap/pull/439 | |
269 | 296 | [#504]: https://github.com/uber-go/zap/pull/504 |
297 | [#508]: https://github.com/uber-go/zap/pull/508 | |
298 | [#518]: https://github.com/uber-go/zap/pull/518 | |
299 | [#577]: https://github.com/uber-go/zap/pull/577 | |
300 | [#574]: https://github.com/uber-go/zap/pull/574 | |
301 | [#602]: https://github.com/uber-go/zap/pull/602 | |
302 | [#572]: https://github.com/uber-go/zap/pull/572 | |
303 | [#606]: https://github.com/uber-go/zap/pull/606 | |
304 | [#614]: https://github.com/uber-go/zap/pull/614 |
134 | 134 | logger := zap.New(core) |
135 | 135 | ``` |
136 | 136 | |
137 | ## Extensions | |
138 | ||
139 | We'd love to support every logging need within zap itself, but we're only | |
140 | familiar with a handful of log ingestion systems, flag-parsing packages, and | |
141 | the like. Rather than merging code that we can't effectively debug and | |
142 | support, we'd rather grow an ecosystem of zap extensions. | |
143 | ||
144 | We're aware of the following extensions, but haven't used them ourselves: | |
145 | ||
146 | | Package | Integration | | |
147 | | --- | --- | | |
148 | | `github.com/tchap/zapext` | Sentry, syslog | | |
149 | | `github.com/fgrosse/zaptest` | Ginkgo | | |
150 | | `github.com/blendle/zapdriver` | Stackdriver | | |
151 | ||
137 | 152 | [go-proverbs]: https://go-proverbs.github.io/ |
138 | 153 | [import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths |
139 | 154 | [lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2 |
8 | 8 | # stable release. |
9 | 9 | GO_VERSION := $(shell go version | cut -d " " -f 3) |
10 | 10 | GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION))) |
11 | LINTABLE_MINOR_VERSIONS := 9 | |
11 | LINTABLE_MINOR_VERSIONS := 10 | |
12 | 12 | ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),) |
13 | 13 | SHOULD_LINT := true |
14 | 14 | endif |
10 | 10 | ## Quick Start |
11 | 11 | |
12 | 12 | In contexts where performance is nice, but not critical, use the |
13 | `SugaredLogger`. It's 4-10x faster than than other structured logging | |
13 | `SugaredLogger`. It's 4-10x faster than other structured logging | |
14 | 14 | packages and includes both structured and `printf`-style APIs. |
15 | 15 | |
16 | 16 | ```go |
28 | 28 | // Array constructs a field with the given key and ArrayMarshaler. It provides |
29 | 29 | // a flexible, but still type-safe and efficient, way to add array-like types |
30 | 30 | // to the logging context. The struct's MarshalLogArray method is called lazily. |
31 | func Array(key string, val zapcore.ArrayMarshaler) zapcore.Field { | |
32 | return zapcore.Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} | |
31 | func Array(key string, val zapcore.ArrayMarshaler) Field { | |
32 | return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} | |
33 | 33 | } |
34 | 34 | |
35 | 35 | // Bools constructs a field that carries a slice of bools. |
36 | func Bools(key string, bs []bool) zapcore.Field { | |
36 | func Bools(key string, bs []bool) Field { | |
37 | 37 | return Array(key, bools(bs)) |
38 | 38 | } |
39 | 39 | |
40 | 40 | // ByteStrings constructs a field that carries a slice of []byte, each of which |
41 | 41 | // must be UTF-8 encoded text. |
42 | func ByteStrings(key string, bss [][]byte) zapcore.Field { | |
42 | func ByteStrings(key string, bss [][]byte) Field { | |
43 | 43 | return Array(key, byteStringsArray(bss)) |
44 | 44 | } |
45 | 45 | |
46 | 46 | // Complex128s constructs a field that carries a slice of complex numbers. |
47 | func Complex128s(key string, nums []complex128) zapcore.Field { | |
47 | func Complex128s(key string, nums []complex128) Field { | |
48 | 48 | return Array(key, complex128s(nums)) |
49 | 49 | } |
50 | 50 | |
51 | 51 | // Complex64s constructs a field that carries a slice of complex numbers. |
52 | func Complex64s(key string, nums []complex64) zapcore.Field { | |
52 | func Complex64s(key string, nums []complex64) Field { | |
53 | 53 | return Array(key, complex64s(nums)) |
54 | 54 | } |
55 | 55 | |
56 | 56 | // Durations constructs a field that carries a slice of time.Durations. |
57 | func Durations(key string, ds []time.Duration) zapcore.Field { | |
57 | func Durations(key string, ds []time.Duration) Field { | |
58 | 58 | return Array(key, durations(ds)) |
59 | 59 | } |
60 | 60 | |
61 | 61 | // Float64s constructs a field that carries a slice of floats. |
62 | func Float64s(key string, nums []float64) zapcore.Field { | |
62 | func Float64s(key string, nums []float64) Field { | |
63 | 63 | return Array(key, float64s(nums)) |
64 | 64 | } |
65 | 65 | |
66 | 66 | // Float32s constructs a field that carries a slice of floats. |
67 | func Float32s(key string, nums []float32) zapcore.Field { | |
67 | func Float32s(key string, nums []float32) Field { | |
68 | 68 | return Array(key, float32s(nums)) |
69 | 69 | } |
70 | 70 | |
71 | 71 | // Ints constructs a field that carries a slice of integers. |
72 | func Ints(key string, nums []int) zapcore.Field { | |
72 | func Ints(key string, nums []int) Field { | |
73 | 73 | return Array(key, ints(nums)) |
74 | 74 | } |
75 | 75 | |
76 | 76 | // Int64s constructs a field that carries a slice of integers. |
77 | func Int64s(key string, nums []int64) zapcore.Field { | |
77 | func Int64s(key string, nums []int64) Field { | |
78 | 78 | return Array(key, int64s(nums)) |
79 | 79 | } |
80 | 80 | |
81 | 81 | // Int32s constructs a field that carries a slice of integers. |
82 | func Int32s(key string, nums []int32) zapcore.Field { | |
82 | func Int32s(key string, nums []int32) Field { | |
83 | 83 | return Array(key, int32s(nums)) |
84 | 84 | } |
85 | 85 | |
86 | 86 | // Int16s constructs a field that carries a slice of integers. |
87 | func Int16s(key string, nums []int16) zapcore.Field { | |
87 | func Int16s(key string, nums []int16) Field { | |
88 | 88 | return Array(key, int16s(nums)) |
89 | 89 | } |
90 | 90 | |
91 | 91 | // Int8s constructs a field that carries a slice of integers. |
92 | func Int8s(key string, nums []int8) zapcore.Field { | |
92 | func Int8s(key string, nums []int8) Field { | |
93 | 93 | return Array(key, int8s(nums)) |
94 | 94 | } |
95 | 95 | |
96 | 96 | // Strings constructs a field that carries a slice of strings. |
97 | func Strings(key string, ss []string) zapcore.Field { | |
97 | func Strings(key string, ss []string) Field { | |
98 | 98 | return Array(key, stringArray(ss)) |
99 | 99 | } |
100 | 100 | |
101 | 101 | // Times constructs a field that carries a slice of time.Times. |
102 | func Times(key string, ts []time.Time) zapcore.Field { | |
102 | func Times(key string, ts []time.Time) Field { | |
103 | 103 | return Array(key, times(ts)) |
104 | 104 | } |
105 | 105 | |
106 | 106 | // Uints constructs a field that carries a slice of unsigned integers. |
107 | func Uints(key string, nums []uint) zapcore.Field { | |
107 | func Uints(key string, nums []uint) Field { | |
108 | 108 | return Array(key, uints(nums)) |
109 | 109 | } |
110 | 110 | |
111 | 111 | // Uint64s constructs a field that carries a slice of unsigned integers. |
112 | func Uint64s(key string, nums []uint64) zapcore.Field { | |
112 | func Uint64s(key string, nums []uint64) Field { | |
113 | 113 | return Array(key, uint64s(nums)) |
114 | 114 | } |
115 | 115 | |
116 | 116 | // Uint32s constructs a field that carries a slice of unsigned integers. |
117 | func Uint32s(key string, nums []uint32) zapcore.Field { | |
117 | func Uint32s(key string, nums []uint32) Field { | |
118 | 118 | return Array(key, uint32s(nums)) |
119 | 119 | } |
120 | 120 | |
121 | 121 | // Uint16s constructs a field that carries a slice of unsigned integers. |
122 | func Uint16s(key string, nums []uint16) zapcore.Field { | |
122 | func Uint16s(key string, nums []uint16) Field { | |
123 | 123 | return Array(key, uint16s(nums)) |
124 | 124 | } |
125 | 125 | |
126 | 126 | // Uint8s constructs a field that carries a slice of unsigned integers. |
127 | func Uint8s(key string, nums []uint8) zapcore.Field { | |
127 | func Uint8s(key string, nums []uint8) Field { | |
128 | 128 | return Array(key, uint8s(nums)) |
129 | 129 | } |
130 | 130 | |
131 | 131 | // Uintptrs constructs a field that carries a slice of pointer addresses. |
132 | func Uintptrs(key string, us []uintptr) zapcore.Field { | |
132 | func Uintptrs(key string, us []uintptr) Field { | |
133 | 133 | return Array(key, uintptrs(us)) |
134 | 134 | } |
135 | 135 | |
136 | 136 | // Errors constructs a field that carries a slice of errors. |
137 | func Errors(key string, errs []error) zapcore.Field { | |
137 | func Errors(key string, errs []error) Field { | |
138 | 138 | return Array(key, errArray(errs)) |
139 | 139 | } |
140 | 140 |
51 | 51 | func TestArrayWrappers(t *testing.T) { |
52 | 52 | tests := []struct { |
53 | 53 | desc string |
54 | field zapcore.Field | |
54 | field Field | |
55 | 55 | expected []interface{} |
56 | 56 | }{ |
57 | {"empty bools", Bools("", []bool{}), []interface{}(nil)}, | |
58 | {"empty byte strings", ByteStrings("", [][]byte{}), []interface{}(nil)}, | |
59 | {"empty complex128s", Complex128s("", []complex128{}), []interface{}(nil)}, | |
60 | {"empty complex64s", Complex64s("", []complex64{}), []interface{}(nil)}, | |
61 | {"empty durations", Durations("", []time.Duration{}), []interface{}(nil)}, | |
62 | {"empty float64s", Float64s("", []float64{}), []interface{}(nil)}, | |
63 | {"empty float32s", Float32s("", []float32{}), []interface{}(nil)}, | |
64 | {"empty ints", Ints("", []int{}), []interface{}(nil)}, | |
65 | {"empty int64s", Int64s("", []int64{}), []interface{}(nil)}, | |
66 | {"empty int32s", Int32s("", []int32{}), []interface{}(nil)}, | |
67 | {"empty int16s", Int16s("", []int16{}), []interface{}(nil)}, | |
68 | {"empty int8s", Int8s("", []int8{}), []interface{}(nil)}, | |
69 | {"empty strings", Strings("", []string{}), []interface{}(nil)}, | |
70 | {"empty times", Times("", []time.Time{}), []interface{}(nil)}, | |
71 | {"empty uints", Uints("", []uint{}), []interface{}(nil)}, | |
72 | {"empty uint64s", Uint64s("", []uint64{}), []interface{}(nil)}, | |
73 | {"empty uint32s", Uint32s("", []uint32{}), []interface{}(nil)}, | |
74 | {"empty uint16s", Uint16s("", []uint16{}), []interface{}(nil)}, | |
75 | {"empty uint8s", Uint8s("", []uint8{}), []interface{}(nil)}, | |
76 | {"empty uintptrs", Uintptrs("", []uintptr{}), []interface{}(nil)}, | |
57 | {"empty bools", Bools("", []bool{}), []interface{}{}}, | |
58 | {"empty byte strings", ByteStrings("", [][]byte{}), []interface{}{}}, | |
59 | {"empty complex128s", Complex128s("", []complex128{}), []interface{}{}}, | |
60 | {"empty complex64s", Complex64s("", []complex64{}), []interface{}{}}, | |
61 | {"empty durations", Durations("", []time.Duration{}), []interface{}{}}, | |
62 | {"empty float64s", Float64s("", []float64{}), []interface{}{}}, | |
63 | {"empty float32s", Float32s("", []float32{}), []interface{}{}}, | |
64 | {"empty ints", Ints("", []int{}), []interface{}{}}, | |
65 | {"empty int64s", Int64s("", []int64{}), []interface{}{}}, | |
66 | {"empty int32s", Int32s("", []int32{}), []interface{}{}}, | |
67 | {"empty int16s", Int16s("", []int16{}), []interface{}{}}, | |
68 | {"empty int8s", Int8s("", []int8{}), []interface{}{}}, | |
69 | {"empty strings", Strings("", []string{}), []interface{}{}}, | |
70 | {"empty times", Times("", []time.Time{}), []interface{}{}}, | |
71 | {"empty uints", Uints("", []uint{}), []interface{}{}}, | |
72 | {"empty uint64s", Uint64s("", []uint64{}), []interface{}{}}, | |
73 | {"empty uint32s", Uint32s("", []uint32{}), []interface{}{}}, | |
74 | {"empty uint16s", Uint16s("", []uint16{}), []interface{}{}}, | |
75 | {"empty uint8s", Uint8s("", []uint8{}), []interface{}{}}, | |
76 | {"empty uintptrs", Uintptrs("", []uintptr{}), []interface{}{}}, | |
77 | 77 | {"bools", Bools("", []bool{true, false}), []interface{}{true, false}}, |
78 | 78 | {"byte strings", ByteStrings("", [][]byte{{1, 2}, {3, 4}}), []interface{}{[]byte{1, 2}, []byte{3, 4}}}, |
79 | 79 | {"complex128s", Complex128s("", []complex128{1 + 2i, 3 + 4i}), []interface{}{1 + 2i, 3 + 4i}}, |
123 | 123 | )) |
124 | 124 | } |
125 | 125 | |
126 | func fakeFields() []zapcore.Field { | |
127 | return []zapcore.Field{ | |
126 | func fakeFields() []zap.Field { | |
127 | return []zap.Field{ | |
128 | 128 | zap.Int("int", _tenInts[0]), |
129 | 129 | zap.Ints("ints", _tenInts), |
130 | 130 | zap.String("string", _tenStrings[0]), |
20 | 20 | // Package buffer provides a thin wrapper around a byte slice. Unlike the |
21 | 21 | // standard library's bytes.Buffer, it supports a portion of the strconv |
22 | 22 | // package's zero-allocation formatters. |
23 | package buffer | |
23 | package buffer // import "go.uber.org/zap/buffer" | |
24 | 24 | |
25 | 25 | import "strconv" |
26 | 26 | |
97 | 97 | return len(bs), nil |
98 | 98 | } |
99 | 99 | |
100 | // TrimNewline trims any final "\n" byte from the end of the buffer. | |
101 | func (b *Buffer) TrimNewline() { | |
102 | if i := len(b.bs) - 1; i >= 0 { | |
103 | if b.bs[i] == '\n' { | |
104 | b.bs = b.bs[:i] | |
105 | } | |
106 | } | |
107 | } | |
108 | ||
100 | 109 | // Free returns the Buffer to its Pool. |
101 | 110 | // |
102 | 111 | // Callers must not retain references to the Buffer after calling Free. |
73 | 73 | // EncoderConfig sets options for the chosen encoder. See |
74 | 74 | // zapcore.EncoderConfig for details. |
75 | 75 | EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` |
76 | // OutputPaths is a list of paths to write logging output to. See Open for | |
77 | // details. | |
76 | // OutputPaths is a list of URLs or file paths to write logging output to. | |
77 | // See Open for details. | |
78 | 78 | OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` |
79 | // ErrorOutputPaths is a list of paths to write internal logger errors to. | |
79 | // ErrorOutputPaths is a list of URLs to write internal logger errors to. | |
80 | 80 | // The default is standard error. |
81 | 81 | // |
82 | 82 | // Note that this setting only affects internal errors; for sample code that |
209 | 209 | } |
210 | 210 | |
211 | 211 | if len(cfg.InitialFields) > 0 { |
212 | fs := make([]zapcore.Field, 0, len(cfg.InitialFields)) | |
212 | fs := make([]Field, 0, len(cfg.InitialFields)) | |
213 | 213 | keys := make([]string, 0, len(cfg.InitialFields)) |
214 | 214 | for k := range cfg.InitialFields { |
215 | 215 | keys = append(keys, k) |
47 | 47 | // "attempt", 3, |
48 | 48 | // "backoff", time.Second, |
49 | 49 | // ) |
50 | // sugar.Printf("failed to fetch URL: %s", "http://example.com") | |
50 | // sugar.Infof("failed to fetch URL: %s", "http://example.com") | |
51 | 51 | // |
52 | 52 | // By default, loggers are unbuffered. However, since zap's low-level APIs |
53 | 53 | // allow buffering, calling Sync before letting your process exit is a good |
30 | 30 | }} |
31 | 31 | |
32 | 32 | // Error is shorthand for the common idiom NamedError("error", err). |
33 | func Error(err error) zapcore.Field { | |
33 | func Error(err error) Field { | |
34 | 34 | return NamedError("error", err) |
35 | 35 | } |
36 | 36 | |
41 | 41 | // |
42 | 42 | // For the common case in which the key is simply "error", the Error function |
43 | 43 | // is shorter and less repetitive. |
44 | func NamedError(key string, err error) zapcore.Field { | |
44 | func NamedError(key string, err error) Field { | |
45 | 45 | if err == nil { |
46 | 46 | return Skip() |
47 | 47 | } |
48 | return zapcore.Field{Key: key, Type: zapcore.ErrorType, Interface: err} | |
48 | return Field{Key: key, Type: zapcore.ErrorType, Interface: err} | |
49 | 49 | } |
50 | 50 | |
51 | 51 | type errArray []error |
35 | 35 | |
36 | 36 | tests := []struct { |
37 | 37 | name string |
38 | field zapcore.Field | |
39 | expect zapcore.Field | |
38 | field Field | |
39 | expect Field | |
40 | 40 | }{ |
41 | 41 | {"Error", Skip(), Error(nil)}, |
42 | {"Error", zapcore.Field{Key: "error", Type: zapcore.ErrorType, Interface: fail}, Error(fail)}, | |
42 | {"Error", Field{Key: "error", Type: zapcore.ErrorType, Interface: fail}, Error(fail)}, | |
43 | 43 | {"NamedError", Skip(), NamedError("foo", nil)}, |
44 | {"NamedError", zapcore.Field{Key: "foo", Type: zapcore.ErrorType, Interface: fail}, NamedError("foo", fail)}, | |
44 | {"NamedError", Field{Key: "foo", Type: zapcore.ErrorType, Interface: fail}, NamedError("foo", fail)}, | |
45 | 45 | {"Any:Error", Any("k", errors.New("v")), NamedError("k", errors.New("v"))}, |
46 | 46 | {"Any:Errors", Any("k", []error{errors.New("v")}), Errors("k", []error{errors.New("v")})}, |
47 | 47 | } |
57 | 57 | func TestErrorArrayConstructor(t *testing.T) { |
58 | 58 | tests := []struct { |
59 | 59 | desc string |
60 | field zapcore.Field | |
60 | field Field | |
61 | 61 | expected []interface{} |
62 | 62 | }{ |
63 | {"empty errors", Errors("", []error{}), []interface{}(nil)}, | |
63 | {"empty errors", Errors("", []error{}), []interface{}{}}, | |
64 | 64 | { |
65 | 65 | "errors", |
66 | 66 | Errors("", []error{nil, errors.New("foo"), nil, errors.New("bar")}), |
27 | 27 | "go.uber.org/zap/zapcore" |
28 | 28 | ) |
29 | 29 | |
30 | // Field is an alias for Field. Aliasing this type dramatically | |
31 | // improves the navigability of this package's API documentation. | |
32 | type Field = zapcore.Field | |
33 | ||
30 | 34 | // Skip constructs a no-op field, which is often useful when handling invalid |
31 | 35 | // inputs in other Field constructors. |
32 | func Skip() zapcore.Field { | |
33 | return zapcore.Field{Type: zapcore.SkipType} | |
36 | func Skip() Field { | |
37 | return Field{Type: zapcore.SkipType} | |
34 | 38 | } |
35 | 39 | |
36 | 40 | // Binary constructs a field that carries an opaque binary blob. |
38 | 42 | // Binary data is serialized in an encoding-appropriate format. For example, |
39 | 43 | // zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text, |
40 | 44 | // use ByteString. |
41 | func Binary(key string, val []byte) zapcore.Field { | |
42 | return zapcore.Field{Key: key, Type: zapcore.BinaryType, Interface: val} | |
45 | func Binary(key string, val []byte) Field { | |
46 | return Field{Key: key, Type: zapcore.BinaryType, Interface: val} | |
43 | 47 | } |
44 | 48 | |
45 | 49 | // Bool constructs a field that carries a bool. |
46 | func Bool(key string, val bool) zapcore.Field { | |
50 | func Bool(key string, val bool) Field { | |
47 | 51 | var ival int64 |
48 | 52 | if val { |
49 | 53 | ival = 1 |
50 | 54 | } |
51 | return zapcore.Field{Key: key, Type: zapcore.BoolType, Integer: ival} | |
55 | return Field{Key: key, Type: zapcore.BoolType, Integer: ival} | |
52 | 56 | } |
53 | 57 | |
54 | 58 | // ByteString constructs a field that carries UTF-8 encoded text as a []byte. |
55 | 59 | // To log opaque binary blobs (which aren't necessarily valid UTF-8), use |
56 | 60 | // Binary. |
57 | func ByteString(key string, val []byte) zapcore.Field { | |
58 | return zapcore.Field{Key: key, Type: zapcore.ByteStringType, Interface: val} | |
61 | func ByteString(key string, val []byte) Field { | |
62 | return Field{Key: key, Type: zapcore.ByteStringType, Interface: val} | |
59 | 63 | } |
60 | 64 | |
61 | 65 | // Complex128 constructs a field that carries a complex number. Unlike most |
62 | 66 | // numeric fields, this costs an allocation (to convert the complex128 to |
63 | 67 | // interface{}). |
64 | func Complex128(key string, val complex128) zapcore.Field { | |
65 | return zapcore.Field{Key: key, Type: zapcore.Complex128Type, Interface: val} | |
68 | func Complex128(key string, val complex128) Field { | |
69 | return Field{Key: key, Type: zapcore.Complex128Type, Interface: val} | |
66 | 70 | } |
67 | 71 | |
68 | 72 | // Complex64 constructs a field that carries a complex number. Unlike most |
69 | 73 | // numeric fields, this costs an allocation (to convert the complex64 to |
70 | 74 | // interface{}). |
71 | func Complex64(key string, val complex64) zapcore.Field { | |
72 | return zapcore.Field{Key: key, Type: zapcore.Complex64Type, Interface: val} | |
75 | func Complex64(key string, val complex64) Field { | |
76 | return Field{Key: key, Type: zapcore.Complex64Type, Interface: val} | |
73 | 77 | } |
74 | 78 | |
75 | 79 | // Float64 constructs a field that carries a float64. The way the |
76 | 80 | // floating-point value is represented is encoder-dependent, so marshaling is |
77 | 81 | // necessarily lazy. |
78 | func Float64(key string, val float64) zapcore.Field { | |
79 | return zapcore.Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} | |
82 | func Float64(key string, val float64) Field { | |
83 | return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} | |
80 | 84 | } |
81 | 85 | |
82 | 86 | // Float32 constructs a field that carries a float32. The way the |
83 | 87 | // floating-point value is represented is encoder-dependent, so marshaling is |
84 | 88 | // necessarily lazy. |
85 | func Float32(key string, val float32) zapcore.Field { | |
86 | return zapcore.Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} | |
89 | func Float32(key string, val float32) Field { | |
90 | return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} | |
87 | 91 | } |
88 | 92 | |
89 | 93 | // Int constructs a field with the given key and value. |
90 | func Int(key string, val int) zapcore.Field { | |
94 | func Int(key string, val int) Field { | |
91 | 95 | return Int64(key, int64(val)) |
92 | 96 | } |
93 | 97 | |
94 | 98 | // Int64 constructs a field with the given key and value. |
95 | func Int64(key string, val int64) zapcore.Field { | |
96 | return zapcore.Field{Key: key, Type: zapcore.Int64Type, Integer: val} | |
99 | func Int64(key string, val int64) Field { | |
100 | return Field{Key: key, Type: zapcore.Int64Type, Integer: val} | |
97 | 101 | } |
98 | 102 | |
99 | 103 | // Int32 constructs a field with the given key and value. |
100 | func Int32(key string, val int32) zapcore.Field { | |
101 | return zapcore.Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} | |
104 | func Int32(key string, val int32) Field { | |
105 | return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} | |
102 | 106 | } |
103 | 107 | |
104 | 108 | // Int16 constructs a field with the given key and value. |
105 | func Int16(key string, val int16) zapcore.Field { | |
106 | return zapcore.Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} | |
109 | func Int16(key string, val int16) Field { | |
110 | return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} | |
107 | 111 | } |
108 | 112 | |
109 | 113 | // Int8 constructs a field with the given key and value. |
110 | func Int8(key string, val int8) zapcore.Field { | |
111 | return zapcore.Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} | |
114 | func Int8(key string, val int8) Field { | |
115 | return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} | |
112 | 116 | } |
113 | 117 | |
114 | 118 | // String constructs a field with the given key and value. |
115 | func String(key string, val string) zapcore.Field { | |
116 | return zapcore.Field{Key: key, Type: zapcore.StringType, String: val} | |
119 | func String(key string, val string) Field { | |
120 | return Field{Key: key, Type: zapcore.StringType, String: val} | |
117 | 121 | } |
118 | 122 | |
119 | 123 | // Uint constructs a field with the given key and value. |
120 | func Uint(key string, val uint) zapcore.Field { | |
124 | func Uint(key string, val uint) Field { | |
121 | 125 | return Uint64(key, uint64(val)) |
122 | 126 | } |
123 | 127 | |
124 | 128 | // Uint64 constructs a field with the given key and value. |
125 | func Uint64(key string, val uint64) zapcore.Field { | |
126 | return zapcore.Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} | |
129 | func Uint64(key string, val uint64) Field { | |
130 | return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} | |
127 | 131 | } |
128 | 132 | |
129 | 133 | // Uint32 constructs a field with the given key and value. |
130 | func Uint32(key string, val uint32) zapcore.Field { | |
131 | return zapcore.Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} | |
134 | func Uint32(key string, val uint32) Field { | |
135 | return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} | |
132 | 136 | } |
133 | 137 | |
134 | 138 | // Uint16 constructs a field with the given key and value. |
135 | func Uint16(key string, val uint16) zapcore.Field { | |
136 | return zapcore.Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} | |
139 | func Uint16(key string, val uint16) Field { | |
140 | return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} | |
137 | 141 | } |
138 | 142 | |
139 | 143 | // Uint8 constructs a field with the given key and value. |
140 | func Uint8(key string, val uint8) zapcore.Field { | |
141 | return zapcore.Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} | |
144 | func Uint8(key string, val uint8) Field { | |
145 | return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} | |
142 | 146 | } |
143 | 147 | |
144 | 148 | // Uintptr constructs a field with the given key and value. |
145 | func Uintptr(key string, val uintptr) zapcore.Field { | |
146 | return zapcore.Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} | |
149 | func Uintptr(key string, val uintptr) Field { | |
150 | return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} | |
147 | 151 | } |
148 | 152 | |
149 | 153 | // Reflect constructs a field with the given key and an arbitrary object. It uses |
153 | 157 | // |
154 | 158 | // If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect |
155 | 159 | // includes the error message in the final log output. |
156 | func Reflect(key string, val interface{}) zapcore.Field { | |
157 | return zapcore.Field{Key: key, Type: zapcore.ReflectType, Interface: val} | |
160 | func Reflect(key string, val interface{}) Field { | |
161 | return Field{Key: key, Type: zapcore.ReflectType, Interface: val} | |
158 | 162 | } |
159 | 163 | |
160 | 164 | // Namespace creates a named, isolated scope within the logger's context. All |
162 | 166 | // |
163 | 167 | // This helps prevent key collisions when injecting loggers into sub-components |
164 | 168 | // or third-party libraries. |
165 | func Namespace(key string) zapcore.Field { | |
166 | return zapcore.Field{Key: key, Type: zapcore.NamespaceType} | |
169 | func Namespace(key string) Field { | |
170 | return Field{Key: key, Type: zapcore.NamespaceType} | |
167 | 171 | } |
168 | 172 | |
169 | 173 | // Stringer constructs a field with the given key and the output of the value's |
170 | 174 | // String method. The Stringer's String method is called lazily. |
171 | func Stringer(key string, val fmt.Stringer) zapcore.Field { | |
172 | return zapcore.Field{Key: key, Type: zapcore.StringerType, Interface: val} | |
173 | } | |
174 | ||
175 | // Time constructs a zapcore.Field with the given key and value. The encoder | |
175 | func Stringer(key string, val fmt.Stringer) Field { | |
176 | return Field{Key: key, Type: zapcore.StringerType, Interface: val} | |
177 | } | |
178 | ||
179 | // Time constructs a Field with the given key and value. The encoder | |
176 | 180 | // controls how the time is serialized. |
177 | func Time(key string, val time.Time) zapcore.Field { | |
178 | return zapcore.Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} | |
181 | func Time(key string, val time.Time) Field { | |
182 | return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} | |
179 | 183 | } |
180 | 184 | |
181 | 185 | // Stack constructs a field that stores a stacktrace of the current goroutine |
182 | 186 | // under provided key. Keep in mind that taking a stacktrace is eager and |
183 | 187 | // expensive (relatively speaking); this function both makes an allocation and |
184 | 188 | // takes about two microseconds. |
185 | func Stack(key string) zapcore.Field { | |
189 | func Stack(key string) Field { | |
186 | 190 | // Returning the stacktrace as a string costs an allocation, but saves us |
187 | 191 | // from expanding the zapcore.Field union struct to include a byte slice. Since |
188 | 192 | // taking a stacktrace is already so expensive (~10us), the extra allocation |
192 | 196 | |
193 | 197 | // Duration constructs a field with the given key and value. The encoder |
194 | 198 | // controls how the duration is serialized. |
195 | func Duration(key string, val time.Duration) zapcore.Field { | |
196 | return zapcore.Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} | |
199 | func Duration(key string, val time.Duration) Field { | |
200 | return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} | |
197 | 201 | } |
198 | 202 | |
199 | 203 | // Object constructs a field with the given key and ObjectMarshaler. It |
200 | 204 | // provides a flexible, but still type-safe and efficient, way to add map- or |
201 | 205 | // struct-like user-defined types to the logging context. The struct's |
202 | 206 | // MarshalLogObject method is called lazily. |
203 | func Object(key string, val zapcore.ObjectMarshaler) zapcore.Field { | |
204 | return zapcore.Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} | |
207 | func Object(key string, val zapcore.ObjectMarshaler) Field { | |
208 | return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} | |
205 | 209 | } |
206 | 210 | |
207 | 211 | // Any takes a key and an arbitrary value and chooses the best way to represent |
211 | 215 | // Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between |
212 | 216 | // them. To minimize surprises, []byte values are treated as binary blobs, byte |
213 | 217 | // values are treated as uint8, and runes are always treated as integers. |
214 | func Any(key string, value interface{}) zapcore.Field { | |
218 | func Any(key string, value interface{}) Field { | |
215 | 219 | switch val := value.(type) { |
216 | 220 | case zapcore.ObjectMarshaler: |
217 | 221 | return Object(key, val) |
36 | 36 | return nil |
37 | 37 | } |
38 | 38 | |
39 | func assertCanBeReused(t testing.TB, field zapcore.Field) { | |
39 | func assertCanBeReused(t testing.TB, field Field) { | |
40 | 40 | var wg sync.WaitGroup |
41 | 41 | |
42 | 42 | for i := 0; i < 100; i++ { |
64 | 64 | |
65 | 65 | tests := []struct { |
66 | 66 | name string |
67 | field zapcore.Field | |
68 | expect zapcore.Field | |
67 | field Field | |
68 | expect Field | |
69 | 69 | }{ |
70 | {"Skip", zapcore.Field{Type: zapcore.SkipType}, Skip()}, | |
71 | {"Binary", zapcore.Field{Key: "k", Type: zapcore.BinaryType, Interface: []byte("ab12")}, Binary("k", []byte("ab12"))}, | |
72 | {"Bool", zapcore.Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, | |
73 | {"Bool", zapcore.Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, | |
74 | {"ByteString", zapcore.Field{Key: "k", Type: zapcore.ByteStringType, Interface: []byte("ab12")}, ByteString("k", []byte("ab12"))}, | |
75 | {"Complex128", zapcore.Field{Key: "k", Type: zapcore.Complex128Type, Interface: 1 + 2i}, Complex128("k", 1+2i)}, | |
76 | {"Complex64", zapcore.Field{Key: "k", Type: zapcore.Complex64Type, Interface: complex64(1 + 2i)}, Complex64("k", 1+2i)}, | |
77 | {"Duration", zapcore.Field{Key: "k", Type: zapcore.DurationType, Integer: 1}, Duration("k", 1)}, | |
78 | {"Int", zapcore.Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int("k", 1)}, | |
79 | {"Int64", zapcore.Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int64("k", 1)}, | |
80 | {"Int32", zapcore.Field{Key: "k", Type: zapcore.Int32Type, Integer: 1}, Int32("k", 1)}, | |
81 | {"Int16", zapcore.Field{Key: "k", Type: zapcore.Int16Type, Integer: 1}, Int16("k", 1)}, | |
82 | {"Int8", zapcore.Field{Key: "k", Type: zapcore.Int8Type, Integer: 1}, Int8("k", 1)}, | |
83 | {"String", zapcore.Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")}, | |
84 | {"Time", zapcore.Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))}, | |
85 | {"Time", zapcore.Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))}, | |
86 | {"Uint", zapcore.Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)}, | |
87 | {"Uint64", zapcore.Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)}, | |
88 | {"Uint32", zapcore.Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)}, | |
89 | {"Uint16", zapcore.Field{Key: "k", Type: zapcore.Uint16Type, Integer: 1}, Uint16("k", 1)}, | |
90 | {"Uint8", zapcore.Field{Key: "k", Type: zapcore.Uint8Type, Integer: 1}, Uint8("k", 1)}, | |
91 | {"Uintptr", zapcore.Field{Key: "k", Type: zapcore.UintptrType, Integer: 10}, Uintptr("k", 0xa)}, | |
92 | {"Reflect", zapcore.Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)}, | |
93 | {"Stringer", zapcore.Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)}, | |
94 | {"Object", zapcore.Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)}, | |
70 | {"Skip", Field{Type: zapcore.SkipType}, Skip()}, | |
71 | {"Binary", Field{Key: "k", Type: zapcore.BinaryType, Interface: []byte("ab12")}, Binary("k", []byte("ab12"))}, | |
72 | {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, | |
73 | {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, | |
74 | {"ByteString", Field{Key: "k", Type: zapcore.ByteStringType, Interface: []byte("ab12")}, ByteString("k", []byte("ab12"))}, | |
75 | {"Complex128", Field{Key: "k", Type: zapcore.Complex128Type, Interface: 1 + 2i}, Complex128("k", 1+2i)}, | |
76 | {"Complex64", Field{Key: "k", Type: zapcore.Complex64Type, Interface: complex64(1 + 2i)}, Complex64("k", 1+2i)}, | |
77 | {"Duration", Field{Key: "k", Type: zapcore.DurationType, Integer: 1}, Duration("k", 1)}, | |
78 | {"Int", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int("k", 1)}, | |
79 | {"Int64", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int64("k", 1)}, | |
80 | {"Int32", Field{Key: "k", Type: zapcore.Int32Type, Integer: 1}, Int32("k", 1)}, | |
81 | {"Int16", Field{Key: "k", Type: zapcore.Int16Type, Integer: 1}, Int16("k", 1)}, | |
82 | {"Int8", Field{Key: "k", Type: zapcore.Int8Type, Integer: 1}, Int8("k", 1)}, | |
83 | {"String", Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")}, | |
84 | {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))}, | |
85 | {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))}, | |
86 | {"Uint", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)}, | |
87 | {"Uint64", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)}, | |
88 | {"Uint32", Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)}, | |
89 | {"Uint16", Field{Key: "k", Type: zapcore.Uint16Type, Integer: 1}, Uint16("k", 1)}, | |
90 | {"Uint8", Field{Key: "k", Type: zapcore.Uint8Type, Integer: 1}, Uint8("k", 1)}, | |
91 | {"Uintptr", Field{Key: "k", Type: zapcore.UintptrType, Integer: 10}, Uintptr("k", 0xa)}, | |
92 | {"Reflect", Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)}, | |
93 | {"Stringer", Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)}, | |
94 | {"Object", Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)}, | |
95 | 95 | {"Any:ObjectMarshaler", Any("k", name), Object("k", name)}, |
96 | 96 | {"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))}, |
97 | 97 | {"Any:Stringer", Any("k", addr), Stringer("k", addr)}, |
138 | 138 | {"Any:Duration", Any("k", time.Second), Duration("k", time.Second)}, |
139 | 139 | {"Any:Durations", Any("k", []time.Duration{time.Second}), Durations("k", []time.Duration{time.Second})}, |
140 | 140 | {"Any:Fallback", Any("k", struct{}{}), Reflect("k", struct{}{})}, |
141 | {"Namespace", Namespace("k"), zapcore.Field{Key: "k", Type: zapcore.NamespaceType}}, | |
141 | {"Namespace", Namespace("k"), Field{Key: "k", Type: zapcore.NamespaceType}}, | |
142 | 142 | } |
143 | 143 | |
144 | 144 | for _, tt := range tests { |
137 | 137 | }, nil |
138 | 138 | } |
139 | 139 | |
140 | func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...zapcore.Field), error) { | |
140 | func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) { | |
141 | 141 | switch lvl { |
142 | 142 | case DebugLevel: |
143 | 143 | return logger.Debug, nil |
158 | 158 | } |
159 | 159 | |
160 | 160 | type loggerWriter struct { |
161 | logFunc func(msg string, fields ...zapcore.Field) | |
161 | logFunc func(msg string, fields ...Field) | |
162 | 162 | } |
163 | 163 | |
164 | 164 | func (l *loggerWriter) Write(p []byte) (int, error) { |
51 | 51 | S().Info("captured") |
52 | 52 | expected := observer.LoggedEntry{ |
53 | 53 | Entry: zapcore.Entry{Message: "captured"}, |
54 | Context: []zapcore.Field{}, | |
54 | Context: []Field{}, | |
55 | 55 | } |
56 | 56 | assert.Equal( |
57 | 57 | t, |
156 | 156 | |
157 | 157 | assert.Equal(t, []observer.LoggedEntry{{ |
158 | 158 | Entry: zapcore.Entry{Message: "redirected"}, |
159 | Context: []zapcore.Field{}, | |
159 | Context: []Field{}, | |
160 | 160 | }}, logs.AllUntimed(), "Unexpected global log output.") |
161 | 161 | }) |
162 | 162 | |
189 | 189 | |
190 | 190 | assert.Equal(t, []observer.LoggedEntry{{ |
191 | 191 | Entry: zapcore.Entry{Level: level, Message: "redirected"}, |
192 | Context: []zapcore.Field{}, | |
192 | Context: []Field{}, | |
193 | 193 | }}, logs.AllUntimed(), "Unexpected global log output.") |
194 | 194 | }) |
195 | 195 | } |
268 | 268 | func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) { |
269 | 269 | require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged") |
270 | 270 | entry := logs.AllUntimed()[0] |
271 | assert.Equal(t, []zapcore.Field{}, entry.Context, "Unexpected entry context.") | |
271 | assert.Equal(t, []Field{}, entry.Context, "Unexpected entry context.") | |
272 | 272 | assert.Equal(t, "redirected", entry.Entry.Message, "Unexpected entry message.") |
273 | 273 | assert.Regexp( |
274 | 274 | t, |
47 | 47 | |
48 | 48 | switch r.Method { |
49 | 49 | |
50 | case "GET": | |
50 | case http.MethodGet: | |
51 | 51 | current := lvl.Level() |
52 | 52 | enc.Encode(payload{Level: ¤t}) |
53 | 53 | |
54 | case "PUT": | |
54 | case http.MethodPut: | |
55 | 55 | var req payload |
56 | 56 | |
57 | 57 | if errmess := func() string { |
155 | 155 | |
156 | 156 | // With creates a child logger and adds structured context to it. Fields added |
157 | 157 | // to the child don't affect the parent, and vice versa. |
158 | func (log *Logger) With(fields ...zapcore.Field) *Logger { | |
158 | func (log *Logger) With(fields ...Field) *Logger { | |
159 | 159 | if len(fields) == 0 { |
160 | 160 | return log |
161 | 161 | } |
173 | 173 | |
174 | 174 | // Debug logs a message at DebugLevel. The message includes any fields passed |
175 | 175 | // at the log site, as well as any fields accumulated on the logger. |
176 | func (log *Logger) Debug(msg string, fields ...zapcore.Field) { | |
176 | func (log *Logger) Debug(msg string, fields ...Field) { | |
177 | 177 | if ce := log.check(DebugLevel, msg); ce != nil { |
178 | 178 | ce.Write(fields...) |
179 | 179 | } |
181 | 181 | |
182 | 182 | // Info logs a message at InfoLevel. The message includes any fields passed |
183 | 183 | // at the log site, as well as any fields accumulated on the logger. |
184 | func (log *Logger) Info(msg string, fields ...zapcore.Field) { | |
184 | func (log *Logger) Info(msg string, fields ...Field) { | |
185 | 185 | if ce := log.check(InfoLevel, msg); ce != nil { |
186 | 186 | ce.Write(fields...) |
187 | 187 | } |
189 | 189 | |
190 | 190 | // Warn logs a message at WarnLevel. The message includes any fields passed |
191 | 191 | // at the log site, as well as any fields accumulated on the logger. |
192 | func (log *Logger) Warn(msg string, fields ...zapcore.Field) { | |
192 | func (log *Logger) Warn(msg string, fields ...Field) { | |
193 | 193 | if ce := log.check(WarnLevel, msg); ce != nil { |
194 | 194 | ce.Write(fields...) |
195 | 195 | } |
197 | 197 | |
198 | 198 | // Error logs a message at ErrorLevel. The message includes any fields passed |
199 | 199 | // at the log site, as well as any fields accumulated on the logger. |
200 | func (log *Logger) Error(msg string, fields ...zapcore.Field) { | |
200 | func (log *Logger) Error(msg string, fields ...Field) { | |
201 | 201 | if ce := log.check(ErrorLevel, msg); ce != nil { |
202 | 202 | ce.Write(fields...) |
203 | 203 | } |
209 | 209 | // If the logger is in development mode, it then panics (DPanic means |
210 | 210 | // "development panic"). This is useful for catching errors that are |
211 | 211 | // recoverable, but shouldn't ever happen. |
212 | func (log *Logger) DPanic(msg string, fields ...zapcore.Field) { | |
212 | func (log *Logger) DPanic(msg string, fields ...Field) { | |
213 | 213 | if ce := log.check(DPanicLevel, msg); ce != nil { |
214 | 214 | ce.Write(fields...) |
215 | 215 | } |
219 | 219 | // at the log site, as well as any fields accumulated on the logger. |
220 | 220 | // |
221 | 221 | // The logger then panics, even if logging at PanicLevel is disabled. |
222 | func (log *Logger) Panic(msg string, fields ...zapcore.Field) { | |
222 | func (log *Logger) Panic(msg string, fields ...Field) { | |
223 | 223 | if ce := log.check(PanicLevel, msg); ce != nil { |
224 | 224 | ce.Write(fields...) |
225 | 225 | } |
230 | 230 | // |
231 | 231 | // The logger then calls os.Exit(1), even if logging at FatalLevel is |
232 | 232 | // disabled. |
233 | func (log *Logger) Fatal(msg string, fields ...zapcore.Field) { | |
233 | func (log *Logger) Fatal(msg string, fields ...Field) { | |
234 | 234 | if ce := log.check(FatalLevel, msg); ce != nil { |
235 | 235 | ce.Write(fields...) |
236 | 236 | } |
206 | 206 | // Don't include allocating these helper slices in the benchmark. Since |
207 | 207 | // access to them isn't synchronized, we can't run the benchmark in |
208 | 208 | // parallel. |
209 | first := make([]zapcore.Field, batchSize) | |
210 | second := make([]zapcore.Field, batchSize) | |
209 | first := make([]Field, batchSize) | |
210 | second := make([]Field, batchSize) | |
211 | 211 | b.ResetTimer() |
212 | 212 | |
213 | 213 | for i := 0; i < b.N; i++ { |
88 | 88 | logger.Info("") |
89 | 89 | assert.Equal( |
90 | 90 | t, |
91 | observer.LoggedEntry{Context: []zapcore.Field{Int("foo", 42), String("bar", "baz")}}, | |
91 | observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}}, | |
92 | 92 | logs.AllUntimed()[0], |
93 | 93 | "Unexpected output with initial fields set.", |
94 | 94 | ) |
105 | 105 | logger.Info("") |
106 | 106 | |
107 | 107 | assert.Equal(t, []observer.LoggedEntry{ |
108 | {Context: []zapcore.Field{Int("foo", 42), String("one", "two")}}, | |
109 | {Context: []zapcore.Field{Int("foo", 42), String("three", "four")}}, | |
110 | {Context: []zapcore.Field{Int("foo", 42)}}, | |
108 | {Context: []Field{Int("foo", 42), String("one", "two")}}, | |
109 | {Context: []Field{Int("foo", 42), String("three", "four")}}, | |
110 | {Context: []Field{Int("foo", 42)}}, | |
111 | 111 | }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.") |
112 | 112 | }) |
113 | 113 | } |
170 | 170 | func TestLoggerLeveledMethods(t *testing.T) { |
171 | 171 | withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { |
172 | 172 | tests := []struct { |
173 | method func(string, ...zapcore.Field) | |
173 | method func(string, ...Field) | |
174 | 174 | expectedLevel zapcore.Level |
175 | 175 | }{ |
176 | 176 | {logger.Debug, DebugLevel}, |
231 | 231 | assert.NotPanics(t, func() { logger.DPanic("") }) |
232 | 232 | assert.Equal( |
233 | 233 | t, |
234 | []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}}, | |
234 | []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, | |
235 | 235 | logs.AllUntimed(), |
236 | 236 | "Unexpected log output from DPanic in production mode.", |
237 | 237 | ) |
240 | 240 | assert.Panics(t, func() { logger.DPanic("") }) |
241 | 241 | assert.Equal( |
242 | 242 | t, |
243 | []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}}, | |
243 | []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, | |
244 | 244 | logs.AllUntimed(), |
245 | 245 | "Unexpected log output from DPanic in development mode.", |
246 | 246 | ) |
421 | 421 | t, |
422 | 422 | observer.LoggedEntry{ |
423 | 423 | Entry: zapcore.Entry{Level: InfoLevel}, |
424 | Context: []zapcore.Field{String("foo", "bar")}, | |
424 | Context: []Field{String("foo", "bar")}, | |
425 | 425 | }, |
426 | 426 | obs, |
427 | 427 | "Unexpected log output.", |
54 | 54 | } |
55 | 55 | |
56 | 56 | // Fields adds fields to the Logger. |
57 | func Fields(fs ...zapcore.Field) Option { | |
57 | func Fields(fs ...Field) Option { | |
58 | 58 | return optionFunc(func(log *Logger) { |
59 | 59 | log.core = log.core.With(fs) |
60 | 60 | }) |
0 | // Copyright (c) 2016 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zap | |
21 | ||
22 | import ( | |
23 | "errors" | |
24 | "fmt" | |
25 | "io" | |
26 | "net/url" | |
27 | "os" | |
28 | "strings" | |
29 | "sync" | |
30 | ||
31 | "go.uber.org/zap/zapcore" | |
32 | ) | |
33 | ||
34 | const schemeFile = "file" | |
35 | ||
36 | var ( | |
37 | _sinkMutex sync.RWMutex | |
38 | _sinkFactories map[string]func(*url.URL) (Sink, error) // keyed by scheme | |
39 | ) | |
40 | ||
41 | func init() { | |
42 | resetSinkRegistry() | |
43 | } | |
44 | ||
45 | func resetSinkRegistry() { | |
46 | _sinkMutex.Lock() | |
47 | defer _sinkMutex.Unlock() | |
48 | ||
49 | _sinkFactories = map[string]func(*url.URL) (Sink, error){ | |
50 | schemeFile: newFileSink, | |
51 | } | |
52 | } | |
53 | ||
54 | // Sink defines the interface to write to and close logger destinations. | |
55 | type Sink interface { | |
56 | zapcore.WriteSyncer | |
57 | io.Closer | |
58 | } | |
59 | ||
60 | type nopCloserSink struct{ zapcore.WriteSyncer } | |
61 | ||
62 | func (nopCloserSink) Close() error { return nil } | |
63 | ||
64 | type errSinkNotFound struct { | |
65 | scheme string | |
66 | } | |
67 | ||
68 | func (e *errSinkNotFound) Error() string { | |
69 | return fmt.Sprintf("no sink found for scheme %q", e.scheme) | |
70 | } | |
71 | ||
72 | // RegisterSink registers a user-supplied factory for all sinks with a | |
73 | // particular scheme. | |
74 | // | |
75 | // All schemes must be ASCII, valid under section 3.1 of RFC 3986 | |
76 | // (https://tools.ietf.org/html/rfc3986#section-3.1), and must not already | |
77 | // have a factory registered. Zap automatically registers a factory for the | |
78 | // "file" scheme. | |
79 | func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { | |
80 | _sinkMutex.Lock() | |
81 | defer _sinkMutex.Unlock() | |
82 | ||
83 | if scheme == "" { | |
84 | return errors.New("can't register a sink factory for empty string") | |
85 | } | |
86 | normalized, err := normalizeScheme(scheme) | |
87 | if err != nil { | |
88 | return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) | |
89 | } | |
90 | if _, ok := _sinkFactories[normalized]; ok { | |
91 | return fmt.Errorf("sink factory already registered for scheme %q", normalized) | |
92 | } | |
93 | _sinkFactories[normalized] = factory | |
94 | return nil | |
95 | } | |
96 | ||
97 | func newSink(rawURL string) (Sink, error) { | |
98 | u, err := url.Parse(rawURL) | |
99 | if err != nil { | |
100 | return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) | |
101 | } | |
102 | if u.Scheme == "" { | |
103 | u.Scheme = schemeFile | |
104 | } | |
105 | ||
106 | _sinkMutex.RLock() | |
107 | factory, ok := _sinkFactories[u.Scheme] | |
108 | _sinkMutex.RUnlock() | |
109 | if !ok { | |
110 | return nil, &errSinkNotFound{u.Scheme} | |
111 | } | |
112 | return factory(u) | |
113 | } | |
114 | ||
115 | func newFileSink(u *url.URL) (Sink, error) { | |
116 | if u.User != nil { | |
117 | return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) | |
118 | } | |
119 | if u.Fragment != "" { | |
120 | return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) | |
121 | } | |
122 | if u.RawQuery != "" { | |
123 | return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) | |
124 | } | |
125 | // Error messages are better if we check hostname and port separately. | |
126 | if u.Port() != "" { | |
127 | return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) | |
128 | } | |
129 | if hn := u.Hostname(); hn != "" && hn != "localhost" { | |
130 | return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) | |
131 | } | |
132 | switch u.Path { | |
133 | case "stdout": | |
134 | return nopCloserSink{os.Stdout}, nil | |
135 | case "stderr": | |
136 | return nopCloserSink{os.Stderr}, nil | |
137 | } | |
138 | return os.OpenFile(u.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) | |
139 | } | |
140 | ||
141 | func normalizeScheme(s string) (string, error) { | |
142 | // https://tools.ietf.org/html/rfc3986#section-3.1 | |
143 | s = strings.ToLower(s) | |
144 | if first := s[0]; 'a' > first || 'z' < first { | |
145 | return "", errors.New("must start with a letter") | |
146 | } | |
147 | for i := 1; i < len(s); i++ { // iterate over bytes, not runes | |
148 | c := s[i] | |
149 | switch { | |
150 | case 'a' <= c && c <= 'z': | |
151 | continue | |
152 | case '0' <= c && c <= '9': | |
153 | continue | |
154 | case c == '.' || c == '+' || c == '-': | |
155 | continue | |
156 | } | |
157 | return "", fmt.Errorf("may not contain %q", c) | |
158 | } | |
159 | return s, nil | |
160 | } |
0 | // Copyright (c) 2016 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zap | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "io/ioutil" | |
25 | "net/url" | |
26 | "strings" | |
27 | "testing" | |
28 | ||
29 | "github.com/stretchr/testify/assert" | |
30 | "github.com/stretchr/testify/require" | |
31 | ||
32 | "go.uber.org/zap/zapcore" | |
33 | ) | |
34 | ||
35 | func TestRegisterSink(t *testing.T) { | |
36 | const ( | |
37 | memScheme = "m" | |
38 | nopScheme = "no-op.1234" | |
39 | ) | |
40 | var memCalls, nopCalls int | |
41 | ||
42 | buf := bytes.NewBuffer(nil) | |
43 | memFactory := func(u *url.URL) (Sink, error) { | |
44 | assert.Equal(t, u.Scheme, memScheme, "Scheme didn't match registration.") | |
45 | memCalls++ | |
46 | return nopCloserSink{zapcore.AddSync(buf)}, nil | |
47 | } | |
48 | nopFactory := func(u *url.URL) (Sink, error) { | |
49 | assert.Equal(t, u.Scheme, nopScheme, "Scheme didn't match registration.") | |
50 | nopCalls++ | |
51 | return nopCloserSink{zapcore.AddSync(ioutil.Discard)}, nil | |
52 | } | |
53 | ||
54 | defer resetSinkRegistry() | |
55 | ||
56 | require.NoError(t, RegisterSink(strings.ToUpper(memScheme), memFactory), "Failed to register scheme %q.", memScheme) | |
57 | require.NoError(t, RegisterSink(nopScheme, nopFactory), "Failed to register scheme %q.", memScheme) | |
58 | ||
59 | sink, close, err := Open( | |
60 | memScheme+"://somewhere", | |
61 | nopScheme+"://somewhere-else", | |
62 | ) | |
63 | assert.NoError(t, err, "Unexpected error opening URLs with registered schemes.") | |
64 | ||
65 | defer close() | |
66 | ||
67 | assert.Equal(t, 1, memCalls, "Unexpected number of calls to memory factory.") | |
68 | assert.Equal(t, 1, nopCalls, "Unexpected number of calls to no-op factory.") | |
69 | ||
70 | _, err = sink.Write([]byte("foo")) | |
71 | assert.NoError(t, err, "Failed to write to combined WriteSyncer.") | |
72 | assert.Equal(t, "foo", buf.String(), "Unexpected buffer contents.") | |
73 | } | |
74 | ||
75 | func TestRegisterSinkErrors(t *testing.T) { | |
76 | nopFactory := func(_ *url.URL) (Sink, error) { | |
77 | return nopCloserSink{zapcore.AddSync(ioutil.Discard)}, nil | |
78 | } | |
79 | tests := []struct { | |
80 | scheme string | |
81 | err string | |
82 | }{ | |
83 | {"", "empty string"}, | |
84 | {"FILE", "already registered"}, | |
85 | {"42", "not a valid scheme"}, | |
86 | {"http*", "not a valid scheme"}, | |
87 | } | |
88 | ||
89 | for _, tt := range tests { | |
90 | t.Run("scheme-"+tt.scheme, func(t *testing.T) { | |
91 | defer resetSinkRegistry() | |
92 | ||
93 | err := RegisterSink(tt.scheme, nopFactory) | |
94 | if assert.Error(t, err, "expected error") { | |
95 | assert.Contains(t, err.Error(), tt.err, "unexpected error") | |
96 | } | |
97 | }) | |
98 | } | |
99 | } |
61 | 61 | } |
62 | 62 | |
63 | 63 | // With adds a variadic number of fields to the logging context. It accepts a |
64 | // mix of strongly-typed zapcore.Field objects and loosely-typed key-value | |
65 | // pairs. When processing pairs, the first element of the pair is used as the | |
66 | // field key and the second as the field value. | |
64 | // mix of strongly-typed Field objects and loosely-typed key-value pairs. When | |
65 | // processing pairs, the first element of the pair is used as the field key | |
66 | // and the second as the field value. | |
67 | 67 | // |
68 | 68 | // For example, |
69 | 69 | // sugaredLogger.With( |
234 | 234 | } |
235 | 235 | } |
236 | 236 | |
237 | func (s *SugaredLogger) sweetenFields(args []interface{}) []zapcore.Field { | |
237 | func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { | |
238 | 238 | if len(args) == 0 { |
239 | 239 | return nil |
240 | 240 | } |
241 | 241 | |
242 | 242 | // Allocate enough space for the worst case; if users pass only structured |
243 | 243 | // fields, we shouldn't penalize them with extra allocations. |
244 | fields := make([]zapcore.Field, 0, len(args)) | |
244 | fields := make([]Field, 0, len(args)) | |
245 | 245 | var invalid invalidPairs |
246 | 246 | |
247 | 247 | for i := 0; i < len(args); { |
248 | 248 | // This is a strongly-typed field. Consume it and move on. |
249 | if f, ok := args[i].(zapcore.Field); ok { | |
249 | if f, ok := args[i].(Field); ok { | |
250 | 250 | fields = append(fields, f) |
251 | 251 | i++ |
252 | 252 | continue |
36 | 36 | ignored := func(msg interface{}) observer.LoggedEntry { |
37 | 37 | return observer.LoggedEntry{ |
38 | 38 | Entry: zapcore.Entry{Level: DPanicLevel, Message: _oddNumberErrMsg}, |
39 | Context: []zapcore.Field{Any("ignored", msg)}, | |
39 | Context: []Field{Any("ignored", msg)}, | |
40 | 40 | } |
41 | 41 | } |
42 | 42 | nonString := func(pairs ...invalidPair) observer.LoggedEntry { |
43 | 43 | return observer.LoggedEntry{ |
44 | 44 | Entry: zapcore.Entry{Level: DPanicLevel, Message: _nonStringKeyErrMsg}, |
45 | Context: []zapcore.Field{Array("invalid", invalidPairs(pairs))}, | |
45 | Context: []Field{Array("invalid", invalidPairs(pairs))}, | |
46 | 46 | } |
47 | 47 | } |
48 | 48 | |
49 | 49 | tests := []struct { |
50 | 50 | desc string |
51 | 51 | args []interface{} |
52 | expected []zapcore.Field | |
52 | expected []Field | |
53 | 53 | errLogs []observer.LoggedEntry |
54 | 54 | }{ |
55 | 55 | { |
56 | 56 | desc: "nil args", |
57 | 57 | args: nil, |
58 | expected: []zapcore.Field{}, | |
58 | expected: []Field{}, | |
59 | 59 | errLogs: nil, |
60 | 60 | }, |
61 | 61 | { |
62 | 62 | desc: "empty slice of args", |
63 | 63 | args: []interface{}{}, |
64 | expected: []zapcore.Field{}, | |
64 | expected: []Field{}, | |
65 | 65 | errLogs: nil, |
66 | 66 | }, |
67 | 67 | { |
68 | 68 | desc: "just a dangling key", |
69 | 69 | args: []interface{}{"should ignore"}, |
70 | expected: []zapcore.Field{}, | |
70 | expected: []Field{}, | |
71 | 71 | errLogs: []observer.LoggedEntry{ignored("should ignore")}, |
72 | 72 | }, |
73 | 73 | { |
74 | 74 | desc: "well-formed key-value pairs", |
75 | 75 | args: []interface{}{"foo", 42, "true", "bar"}, |
76 | expected: []zapcore.Field{Int("foo", 42), String("true", "bar")}, | |
76 | expected: []Field{Int("foo", 42), String("true", "bar")}, | |
77 | 77 | errLogs: nil, |
78 | 78 | }, |
79 | 79 | { |
80 | 80 | desc: "just a structured field", |
81 | 81 | args: []interface{}{Int("foo", 42)}, |
82 | expected: []zapcore.Field{Int("foo", 42)}, | |
82 | expected: []Field{Int("foo", 42)}, | |
83 | 83 | errLogs: nil, |
84 | 84 | }, |
85 | 85 | { |
86 | 86 | desc: "structured field and a dangling key", |
87 | 87 | args: []interface{}{Int("foo", 42), "dangling"}, |
88 | expected: []zapcore.Field{Int("foo", 42)}, | |
88 | expected: []Field{Int("foo", 42)}, | |
89 | 89 | errLogs: []observer.LoggedEntry{ignored("dangling")}, |
90 | 90 | }, |
91 | 91 | { |
92 | 92 | desc: "structured field and a dangling non-string key", |
93 | 93 | args: []interface{}{Int("foo", 42), 13}, |
94 | expected: []zapcore.Field{Int("foo", 42)}, | |
94 | expected: []Field{Int("foo", 42)}, | |
95 | 95 | errLogs: []observer.LoggedEntry{ignored(13)}, |
96 | 96 | }, |
97 | 97 | { |
98 | 98 | desc: "key-value pair and a dangling key", |
99 | 99 | args: []interface{}{"foo", 42, "dangling"}, |
100 | expected: []zapcore.Field{Int("foo", 42)}, | |
100 | expected: []Field{Int("foo", 42)}, | |
101 | 101 | errLogs: []observer.LoggedEntry{ignored("dangling")}, |
102 | 102 | }, |
103 | 103 | { |
104 | 104 | desc: "pairs, a structured field, and a dangling key", |
105 | 105 | args: []interface{}{"first", "field", Int("foo", 42), "baz", "quux", "dangling"}, |
106 | expected: []zapcore.Field{String("first", "field"), Int("foo", 42), String("baz", "quux")}, | |
106 | expected: []Field{String("first", "field"), Int("foo", 42), String("baz", "quux")}, | |
107 | 107 | errLogs: []observer.LoggedEntry{ignored("dangling")}, |
108 | 108 | }, |
109 | 109 | { |
110 | 110 | desc: "one non-string key", |
111 | 111 | args: []interface{}{"foo", 42, true, "bar"}, |
112 | expected: []zapcore.Field{Int("foo", 42)}, | |
112 | expected: []Field{Int("foo", 42)}, | |
113 | 113 | errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})}, |
114 | 114 | }, |
115 | 115 | { |
116 | 116 | desc: "pairs, structured fields, non-string keys, and a dangling key", |
117 | 117 | args: []interface{}{"foo", 42, true, "bar", Int("structure", 11), 42, "reversed", "baz", "quux", "dangling"}, |
118 | expected: []zapcore.Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")}, | |
118 | expected: []Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")}, | |
119 | 119 | errLogs: []observer.LoggedEntry{ |
120 | 120 | ignored("dangling"), |
121 | 121 | nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}), |
145 | 145 | |
146 | 146 | // Double-check that the actual message was logged. |
147 | 147 | require.Equal(t, 2, len(output), "Unexpected number of entries logged.") |
148 | require.Equal(t, observer.LoggedEntry{Context: []zapcore.Field{}}, output[1], "Unexpected non-error log entry.") | |
148 | require.Equal(t, observer.LoggedEntry{Context: []Field{}}, output[1], "Unexpected non-error log entry.") | |
149 | 149 | |
150 | 150 | // Assert that the error message's structured fields serialize properly. |
151 | 151 | require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.") |
174 | 174 | // Common to all test cases. |
175 | 175 | context := []interface{}{"foo", "bar"} |
176 | 176 | extra := []interface{}{"baz", false} |
177 | expectedFields := []zapcore.Field{String("foo", "bar"), Bool("baz", false)} | |
177 | expectedFields := []Field{String("foo", "bar"), Bool("baz", false)} | |
178 | 178 | |
179 | 179 | for _, tt := range tests { |
180 | 180 | withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { |
206 | 206 | |
207 | 207 | // Common to all test cases. |
208 | 208 | context := []interface{}{"foo", "bar"} |
209 | expectedFields := []zapcore.Field{String("foo", "bar")} | |
209 | expectedFields := []Field{String("foo", "bar")} | |
210 | 210 | |
211 | 211 | for _, tt := range tests { |
212 | 212 | withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { |
242 | 242 | |
243 | 243 | // Common to all test cases. |
244 | 244 | context := []interface{}{"foo", "bar"} |
245 | expectedFields := []zapcore.Field{String("foo", "bar")} | |
245 | expectedFields := []Field{String("foo", "bar")} | |
246 | 246 | |
247 | 247 | for _, tt := range tests { |
248 | 248 | withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { |
286 | 286 | assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.") |
287 | 287 | if tt.expectedMsg != "" { |
288 | 288 | assert.Equal(t, []observer.LoggedEntry{{ |
289 | Context: []zapcore.Field{}, | |
289 | Context: []Field{}, | |
290 | 290 | Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel}, |
291 | 291 | }}, logs.AllUntimed(), "Unexpected log output.") |
292 | 292 | } else { |
319 | 319 | assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.") |
320 | 320 | if tt.expectedMsg != "" { |
321 | 321 | assert.Equal(t, []observer.LoggedEntry{{ |
322 | Context: []zapcore.Field{}, | |
322 | Context: []Field{}, | |
323 | 323 | Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel}, |
324 | 324 | }}, logs.AllUntimed(), "Unexpected log output.") |
325 | 325 | } else { |
20 | 20 | package zap |
21 | 21 | |
22 | 22 | import ( |
23 | "fmt" | |
24 | "io" | |
23 | 25 | "io/ioutil" |
24 | "os" | |
25 | 26 | |
26 | 27 | "go.uber.org/zap/zapcore" |
27 | 28 | |
28 | 29 | "go.uber.org/multierr" |
29 | 30 | ) |
30 | 31 | |
31 | // Open is a high-level wrapper that takes a variadic number of paths, opens or | |
32 | // creates each of the specified files, and combines them into a locked | |
32 | // Open is a high-level wrapper that takes a variadic number of URLs, opens or | |
33 | // creates each of the specified resources, and combines them into a locked | |
33 | 34 | // WriteSyncer. It also returns any error encountered and a function to close |
34 | 35 | // any opened files. |
35 | 36 | // |
36 | // Passing no paths returns a no-op WriteSyncer. The special paths "stdout" and | |
37 | // "stderr" are interpreted as os.Stdout and os.Stderr, respectively. | |
37 | // Passing no URLs returns a no-op WriteSyncer. Zap handles URLs without a | |
38 | // scheme and URLs with the "file" scheme. Third-party code may register | |
39 | // factories for other schemes using RegisterSink. | |
40 | // | |
41 | // URLs with the "file" scheme must use absolute paths on the local | |
42 | // filesystem. No user, password, port, fragments, or query parameters are | |
43 | // allowed, and the hostname must be empty or "localhost". | |
44 | // | |
45 | // Since it's common to write logs to the local filesystem, URLs without a | |
46 | // scheme (e.g., "/var/log/foo.log") are treated as local file paths. Without | |
47 | // a scheme, the special paths "stdout" and "stderr" are interpreted as | |
48 | // os.Stdout and os.Stderr. When specified without a scheme, relative file | |
49 | // paths also work. | |
38 | 50 | func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { |
39 | 51 | writers, close, err := open(paths) |
40 | 52 | if err != nil { |
46 | 58 | } |
47 | 59 | |
48 | 60 | func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { |
49 | var openErr error | |
50 | 61 | writers := make([]zapcore.WriteSyncer, 0, len(paths)) |
51 | files := make([]*os.File, 0, len(paths)) | |
62 | closers := make([]io.Closer, 0, len(paths)) | |
52 | 63 | close := func() { |
53 | for _, f := range files { | |
54 | f.Close() | |
55 | } | |
56 | } | |
57 | for _, path := range paths { | |
58 | switch path { | |
59 | case "stdout": | |
60 | writers = append(writers, os.Stdout) | |
61 | // Don't close standard out. | |
62 | continue | |
63 | case "stderr": | |
64 | writers = append(writers, os.Stderr) | |
65 | // Don't close standard error. | |
66 | continue | |
67 | } | |
68 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) | |
69 | openErr = multierr.Append(openErr, err) | |
70 | if err == nil { | |
71 | writers = append(writers, f) | |
72 | files = append(files, f) | |
64 | for _, c := range closers { | |
65 | c.Close() | |
73 | 66 | } |
74 | 67 | } |
75 | 68 | |
69 | var openErr error | |
70 | for _, path := range paths { | |
71 | sink, err := newSink(path) | |
72 | if err != nil { | |
73 | openErr = multierr.Append(openErr, fmt.Errorf("couldn't open sink %q: %v", path, err)) | |
74 | continue | |
75 | } | |
76 | writers = append(writers, sink) | |
77 | closers = append(closers, sink) | |
78 | } | |
76 | 79 | if openErr != nil { |
77 | 80 | close() |
78 | 81 | return writers, nil, openErr |
20 | 20 | package zap |
21 | 21 | |
22 | 22 | import ( |
23 | "encoding/hex" | |
24 | "errors" | |
23 | 25 | "io/ioutil" |
26 | "math/rand" | |
27 | "net/url" | |
24 | 28 | "os" |
29 | "path/filepath" | |
30 | "strings" | |
25 | 31 | "testing" |
26 | 32 | |
27 | 33 | "github.com/stretchr/testify/assert" |
43 | 49 | } |
44 | 50 | |
45 | 51 | func TestOpen(t *testing.T) { |
46 | temp, err := ioutil.TempFile("", "zap-open-test") | |
47 | require.NoError(t, err, "Couldn't create a temporary file for test.") | |
48 | defer os.Remove(temp.Name()) | |
52 | tempName := tempFileName("", "zap-open-test") | |
53 | assert.False(t, fileExists(tempName)) | |
54 | require.True(t, strings.HasPrefix(tempName, "/"), "Expected absolute temp file path.") | |
49 | 55 | |
50 | 56 | tests := []struct { |
51 | paths []string | |
52 | filenames []string | |
53 | error string | |
57 | paths []string | |
58 | errs []string | |
54 | 59 | }{ |
55 | {[]string{"stdout"}, []string{os.Stdout.Name()}, ""}, | |
56 | {[]string{"stderr"}, []string{os.Stderr.Name()}, ""}, | |
57 | {[]string{temp.Name()}, []string{temp.Name()}, ""}, | |
58 | {[]string{"/foo/bar/baz"}, []string{}, "open /foo/bar/baz: no such file or directory"}, | |
60 | {[]string{"stdout"}, nil}, | |
61 | {[]string{"stderr"}, nil}, | |
62 | {[]string{tempName}, nil}, | |
63 | {[]string{"file://" + tempName}, nil}, | |
64 | {[]string{"file://localhost" + tempName}, nil}, | |
65 | {[]string{"/foo/bar/baz"}, []string{"open /foo/bar/baz: no such file or directory"}}, | |
66 | {[]string{"file://localhost/foo/bar/baz"}, []string{"open /foo/bar/baz: no such file or directory"}}, | |
59 | 67 | { |
60 | paths: []string{"stdout", "/foo/bar/baz", temp.Name(), "/baz/quux"}, | |
61 | filenames: []string{os.Stdout.Name(), temp.Name()}, | |
62 | error: "open /foo/bar/baz: no such file or directory; open /baz/quux: no such file or directory", | |
68 | paths: []string{"stdout", "/foo/bar/baz", tempName, "file:///baz/quux"}, | |
69 | errs: []string{ | |
70 | "open /foo/bar/baz: no such file or directory", | |
71 | "open /baz/quux: no such file or directory", | |
72 | }, | |
63 | 73 | }, |
74 | {[]string{"file:///stderr"}, []string{"open /stderr: permission denied"}}, | |
75 | {[]string{"file:///stdout"}, []string{"open /stdout: permission denied"}}, | |
76 | {[]string{"file://host01.test.com" + tempName}, []string{"empty or use localhost"}}, | |
77 | {[]string{"file://rms@localhost" + tempName}, []string{"user and password not allowed"}}, | |
78 | {[]string{"file://localhost" + tempName + "#foo"}, []string{"fragments not allowed"}}, | |
79 | {[]string{"file://localhost" + tempName + "?foo=bar"}, []string{"query parameters not allowed"}}, | |
80 | {[]string{"file://localhost:8080" + tempName}, []string{"ports not allowed"}}, | |
64 | 81 | } |
65 | 82 | |
66 | 83 | for _, tt := range tests { |
67 | wss, cleanup, err := open(tt.paths) | |
84 | _, cleanup, err := Open(tt.paths...) | |
68 | 85 | if err == nil { |
69 | 86 | defer cleanup() |
70 | 87 | } |
71 | 88 | |
72 | if tt.error == "" { | |
89 | if len(tt.errs) == 0 { | |
73 | 90 | assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths) |
74 | 91 | } else { |
75 | assert.Equal(t, tt.error, err.Error(), "Unexpected error opening paths %v.", tt.paths) | |
92 | msg := err.Error() | |
93 | for _, expect := range tt.errs { | |
94 | assert.Contains(t, msg, expect, "Unexpected error opening paths %v.", tt.paths) | |
95 | } | |
76 | 96 | } |
77 | names := make([]string, len(wss)) | |
78 | for i, ws := range wss { | |
79 | f, ok := ws.(*os.File) | |
80 | require.True(t, ok, "Expected all WriteSyncers returned from open() to be files.") | |
81 | names[i] = f.Name() | |
97 | } | |
98 | ||
99 | assert.True(t, fileExists(tempName)) | |
100 | os.Remove(tempName) | |
101 | } | |
102 | ||
103 | func TestOpenRelativePath(t *testing.T) { | |
104 | const name = "test-relative-path.txt" | |
105 | ||
106 | require.False(t, fileExists(name), "Test file already exists.") | |
107 | s, cleanup, err := Open(name) | |
108 | require.NoError(t, err, "Open failed.") | |
109 | defer func() { | |
110 | err := os.Remove(name) | |
111 | if !t.Failed() { | |
112 | // If the test has already failed, we probably didn't create this file. | |
113 | require.NoError(t, err, "Deleting test file failed.") | |
82 | 114 | } |
83 | assert.Equal(t, tt.filenames, names, "Opened unexpected files given paths %v.", tt.paths) | |
84 | } | |
115 | }() | |
116 | defer cleanup() | |
117 | ||
118 | _, err = s.Write([]byte("test")) | |
119 | assert.NoError(t, err, "Write failed.") | |
120 | assert.True(t, fileExists(name), "Didn't create file for relative path.") | |
85 | 121 | } |
86 | 122 | |
87 | 123 | func TestOpenFails(t *testing.T) { |
88 | 124 | tests := []struct { |
89 | 125 | paths []string |
90 | 126 | }{ |
91 | { | |
92 | paths: []string{"./non-existent-dir/file"}, | |
93 | }, | |
94 | { | |
95 | paths: []string{"stdout", "./non-existent-dir/file"}, | |
96 | }, | |
127 | {paths: []string{"./non-existent-dir/file"}}, // directory doesn't exist | |
128 | {paths: []string{"stdout", "./non-existent-dir/file"}}, // directory doesn't exist | |
129 | {paths: []string{"://foo.log"}}, // invalid URL, scheme can't begin with colon | |
130 | {paths: []string{"mem://somewhere"}}, // scheme not registered | |
97 | 131 | } |
98 | 132 | |
99 | 133 | for _, tt := range tests { |
100 | 134 | _, cleanup, err := Open(tt.paths...) |
101 | 135 | require.Nil(t, cleanup, "Cleanup function should never be nil") |
102 | assert.Error(t, err, "Open with non-existent directory should fail") | |
136 | assert.Error(t, err, "Open with invalid URL should fail.") | |
103 | 137 | } |
104 | 138 | } |
105 | 139 | |
117 | 151 | return nil |
118 | 152 | } |
119 | 153 | |
154 | func TestOpenWithErroringSinkFactory(t *testing.T) { | |
155 | defer resetSinkRegistry() | |
156 | ||
157 | msg := "expected factory error" | |
158 | factory := func(_ *url.URL) (Sink, error) { | |
159 | return nil, errors.New(msg) | |
160 | } | |
161 | ||
162 | assert.NoError(t, RegisterSink("test", factory), "Failed to register sink factory.") | |
163 | _, _, err := Open("test://some/path") | |
164 | assert.Contains(t, err.Error(), msg, "Unexpected error.") | |
165 | } | |
166 | ||
120 | 167 | func TestCombineWriteSyncers(t *testing.T) { |
121 | 168 | tw := &testWriter{"test", t} |
122 | 169 | w := CombineWriteSyncers(tw) |
123 | 170 | w.Write([]byte("test")) |
124 | 171 | } |
172 | ||
173 | func tempFileName(prefix, suffix string) string { | |
174 | randBytes := make([]byte, 16) | |
175 | rand.Read(randBytes) | |
176 | return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix) | |
177 | } | |
178 | ||
179 | func fileExists(name string) bool { | |
180 | if _, err := os.Stat(name); os.IsNotExist(err) { | |
181 | return false | |
182 | } | |
183 | return true | |
184 | } |
70 | 70 | t FieldType |
71 | 71 | want interface{} |
72 | 72 | }{ |
73 | {ArrayMarshalerType, []interface{}(nil)}, | |
73 | {ArrayMarshalerType, []interface{}{}}, | |
74 | 74 | {ObjectMarshalerType, map[string]interface{}{}}, |
75 | 75 | } |
76 | 76 | for _, tt := range tests { |
43 | 43 | } |
44 | 44 | |
45 | 45 | func putJSONEncoder(enc *jsonEncoder) { |
46 | if enc.reflectBuf != nil { | |
47 | enc.reflectBuf.Free() | |
48 | } | |
46 | 49 | enc.EncoderConfig = nil |
47 | 50 | enc.buf = nil |
48 | 51 | enc.spaced = false |
49 | 52 | enc.openNamespaces = 0 |
53 | enc.reflectBuf = nil | |
54 | enc.reflectEnc = nil | |
50 | 55 | _jsonPool.Put(enc) |
51 | 56 | } |
52 | 57 | |
55 | 60 | buf *buffer.Buffer |
56 | 61 | spaced bool // include spaces after colons and commas |
57 | 62 | openNamespaces int |
63 | ||
64 | // for encoding generic values by reflection | |
65 | reflectBuf *buffer.Buffer | |
66 | reflectEnc *json.Encoder | |
58 | 67 | } |
59 | 68 | |
60 | 69 | // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder |
123 | 132 | enc.AppendInt64(val) |
124 | 133 | } |
125 | 134 | |
135 | func (enc *jsonEncoder) resetReflectBuf() { | |
136 | if enc.reflectBuf == nil { | |
137 | enc.reflectBuf = bufferpool.Get() | |
138 | enc.reflectEnc = json.NewEncoder(enc.reflectBuf) | |
139 | } else { | |
140 | enc.reflectBuf.Reset() | |
141 | } | |
142 | } | |
143 | ||
126 | 144 | func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { |
127 | marshaled, err := json.Marshal(obj) | |
145 | enc.resetReflectBuf() | |
146 | err := enc.reflectEnc.Encode(obj) | |
128 | 147 | if err != nil { |
129 | 148 | return err |
130 | 149 | } |
131 | enc.addKey(key) | |
132 | _, err = enc.buf.Write(marshaled) | |
150 | enc.reflectBuf.TrimNewline() | |
151 | enc.addKey(key) | |
152 | _, err = enc.buf.Write(enc.reflectBuf.Bytes()) | |
133 | 153 | return err |
134 | 154 | } |
135 | 155 | |
212 | 232 | } |
213 | 233 | |
214 | 234 | func (enc *jsonEncoder) AppendReflected(val interface{}) error { |
215 | marshaled, err := json.Marshal(val) | |
235 | enc.resetReflectBuf() | |
236 | err := enc.reflectEnc.Encode(val) | |
216 | 237 | if err != nil { |
217 | 238 | return err |
218 | 239 | } |
219 | enc.addElementSeparator() | |
220 | _, err = enc.buf.Write(marshaled) | |
240 | enc.reflectBuf.TrimNewline() | |
241 | enc.addElementSeparator() | |
242 | _, err = enc.buf.Write(enc.reflectBuf.Bytes()) | |
221 | 243 | return err |
222 | 244 | } |
223 | 245 |
222 | 222 | } |
223 | 223 | |
224 | 224 | for _, tt := range tests { |
225 | assertOutput(t, tt.desc, tt.expected, tt.f) | |
225 | t.Run(tt.desc, func(t *testing.T) { | |
226 | assertOutput(t, tt.expected, tt.f) | |
227 | }) | |
226 | 228 | } |
227 | 229 | } |
228 | 230 | |
313 | 315 | } |
314 | 316 | |
315 | 317 | for _, tt := range tests { |
316 | f := func(enc Encoder) error { | |
317 | return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { | |
318 | tt.f(arr) | |
319 | tt.f(arr) | |
320 | return nil | |
321 | })) | |
322 | } | |
323 | assertOutput(t, tt.desc, `"array":`+tt.expected, func(enc Encoder) { | |
324 | err := f(enc) | |
325 | assert.NoError(t, err, "Unexpected error adding array to JSON encoder.") | |
318 | t.Run(tt.desc, func(t *testing.T) { | |
319 | f := func(enc Encoder) error { | |
320 | return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { | |
321 | tt.f(arr) | |
322 | tt.f(arr) | |
323 | return nil | |
324 | })) | |
325 | } | |
326 | assertOutput(t, `"array":`+tt.expected, func(enc Encoder) { | |
327 | err := f(enc) | |
328 | assert.NoError(t, err, "Unexpected error adding array to JSON encoder.") | |
329 | }) | |
326 | 330 | }) |
327 | 331 | } |
328 | 332 | } |
331 | 335 | assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.") |
332 | 336 | } |
333 | 337 | |
334 | func assertOutput(t testing.TB, desc string, expected string, f func(Encoder)) { | |
338 | func assertOutput(t testing.TB, expected string, f func(Encoder)) { | |
335 | 339 | enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &EncoderConfig{ |
336 | 340 | EncodeTime: EpochTimeEncoder, |
337 | 341 | EncodeDuration: SecondsDurationEncoder, |
338 | 342 | }} |
339 | 343 | f(enc) |
340 | assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding a %s.", desc) | |
344 | assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding.") | |
341 | 345 | |
342 | 346 | enc.truncate() |
343 | 347 | enc.AddString("foo", "bar") |
348 | 352 | // field. |
349 | 353 | expectedPrefix += "," |
350 | 354 | } |
351 | assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding a %s as a second field.", desc) | |
355 | assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding as a second field.") | |
352 | 356 | } |
353 | 357 | |
354 | 358 | // Nested Array- and ObjectMarshalers. |
0 | // Copyright (c) 2018 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zapcore_test | |
21 | ||
22 | import ( | |
23 | "testing" | |
24 | "time" | |
25 | ||
26 | "github.com/stretchr/testify/assert" | |
27 | ||
28 | "go.uber.org/zap" | |
29 | "go.uber.org/zap/zapcore" | |
30 | ) | |
31 | ||
32 | // TestJSONEncodeEntry is an more "integrated" test that makes it easier to get | |
33 | // coverage on the json encoder (e.g. putJSONEncoder, let alone EncodeEntry | |
34 | // itself) than the tests in json_encoder_impl_test.go; it needs to be in the | |
35 | // zapcore_test package, so that it can import the toplevel zap package for | |
36 | // field constructor convenience. | |
37 | func TestJSONEncodeEntry(t *testing.T) { | |
38 | type bar struct { | |
39 | Key string `json:"key"` | |
40 | Val float64 `json:"val"` | |
41 | } | |
42 | ||
43 | type foo struct { | |
44 | A string `json:"aee"` | |
45 | B int `json:"bee"` | |
46 | C float64 `json:"cee"` | |
47 | D []bar `json:"dee"` | |
48 | } | |
49 | ||
50 | tests := []struct { | |
51 | desc string | |
52 | expected string | |
53 | ent zapcore.Entry | |
54 | fields []zapcore.Field | |
55 | }{ | |
56 | { | |
57 | desc: "info entry with some fields", | |
58 | expected: `{ | |
59 | "L": "info", | |
60 | "T": "2018-06-19T16:33:42.000Z", | |
61 | "N": "bob", | |
62 | "M": "lob law", | |
63 | "so": "passes", | |
64 | "answer": 42, | |
65 | "common_pie": 3.14, | |
66 | "such": { | |
67 | "aee": "lol", | |
68 | "bee": 123, | |
69 | "cee": 0.9999, | |
70 | "dee": [ | |
71 | {"key": "pi", "val": 3.141592653589793}, | |
72 | {"key": "tau", "val": 6.283185307179586} | |
73 | ] | |
74 | } | |
75 | }`, | |
76 | ent: zapcore.Entry{ | |
77 | Level: zapcore.InfoLevel, | |
78 | Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), | |
79 | LoggerName: "bob", | |
80 | Message: "lob law", | |
81 | }, | |
82 | fields: []zapcore.Field{ | |
83 | zap.String("so", "passes"), | |
84 | zap.Int("answer", 42), | |
85 | zap.Float64("common_pie", 3.14), | |
86 | zap.Reflect("such", foo{ | |
87 | A: "lol", | |
88 | B: 123, | |
89 | C: 0.9999, | |
90 | D: []bar{ | |
91 | {"pi", 3.141592653589793}, | |
92 | {"tau", 6.283185307179586}, | |
93 | }, | |
94 | }), | |
95 | }, | |
96 | }, | |
97 | } | |
98 | ||
99 | enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ | |
100 | MessageKey: "M", | |
101 | LevelKey: "L", | |
102 | TimeKey: "T", | |
103 | NameKey: "N", | |
104 | CallerKey: "C", | |
105 | StacktraceKey: "S", | |
106 | EncodeLevel: zapcore.LowercaseLevelEncoder, | |
107 | EncodeTime: zapcore.ISO8601TimeEncoder, | |
108 | EncodeDuration: zapcore.SecondsDurationEncoder, | |
109 | EncodeCaller: zapcore.ShortCallerEncoder, | |
110 | }) | |
111 | ||
112 | for _, tt := range tests { | |
113 | t.Run(tt.desc, func(t *testing.T) { | |
114 | buf, err := enc.EncodeEntry(tt.ent, tt.fields) | |
115 | if assert.NoError(t, err, "Unexpected JSON encoding error.") { | |
116 | assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") | |
117 | } | |
118 | buf.Free() | |
119 | }) | |
120 | } | |
121 | } |
42 | 42 | |
43 | 43 | // AddArray implements ObjectEncoder. |
44 | 44 | func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { |
45 | arr := &sliceArrayEncoder{} | |
45 | arr := &sliceArrayEncoder{elems: make([]interface{}, 0)} | |
46 | 46 | err := v.MarshalLogArray(arr) |
47 | 47 | m.cur[key] = arr.elems |
48 | 48 | return err |
24 | 24 | "time" |
25 | 25 | |
26 | 26 | "github.com/stretchr/testify/assert" |
27 | "github.com/stretchr/testify/require" | |
27 | 28 | ) |
28 | 29 | |
29 | 30 | func TestMapObjectEncoderAdd(t *testing.T) { |
72 | 73 | assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") |
73 | 74 | }, |
74 | 75 | expected: []interface{}{wantTurducken, wantTurducken}, |
76 | }, | |
77 | { | |
78 | desc: "AddArray (empty)", | |
79 | f: func(e ObjectEncoder) { | |
80 | assert.NoError(t, e.AddArray("k", turduckens(0)), "Expected AddArray to succeed.") | |
81 | }, | |
82 | expected: []interface{}{}, | |
75 | 83 | }, |
76 | 84 | { |
77 | 85 | desc: "AddBinary", |
203 | 211 | } |
204 | 212 | |
205 | 213 | for _, tt := range tests { |
206 | enc := NewMapObjectEncoder() | |
207 | tt.f(enc) | |
208 | assert.Equal(t, tt.expected, enc.Fields["k"], "Unexpected encoder output.") | |
214 | t.Run(tt.desc, func(t *testing.T) { | |
215 | enc := NewMapObjectEncoder() | |
216 | tt.f(enc) | |
217 | assert.Equal(t, tt.expected, enc.Fields["k"], "Unexpected encoder output.") | |
218 | }) | |
209 | 219 | } |
210 | 220 | } |
211 | 221 | func TestSliceArrayEncoderAppend(t *testing.T) { |
254 | 264 | } |
255 | 265 | |
256 | 266 | for _, tt := range tests { |
257 | enc := NewMapObjectEncoder() | |
258 | assert.NoError(t, enc.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { | |
259 | tt.f(arr) | |
260 | tt.f(arr) | |
261 | return nil | |
262 | })), "Expected AddArray to succeed.") | |
263 | ||
264 | arr, ok := enc.Fields["k"].([]interface{}) | |
265 | if !ok { | |
266 | t.Errorf("Test case %s didn't encode an array.", tt.desc) | |
267 | continue | |
268 | } | |
269 | assert.Equal(t, []interface{}{tt.expected, tt.expected}, arr, "Unexpected encoder output.") | |
267 | t.Run(tt.desc, func(t *testing.T) { | |
268 | enc := NewMapObjectEncoder() | |
269 | assert.NoError(t, enc.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { | |
270 | tt.f(arr) | |
271 | tt.f(arr) | |
272 | return nil | |
273 | })), "Expected AddArray to succeed.") | |
274 | ||
275 | arr, ok := enc.Fields["k"].([]interface{}) | |
276 | require.True(t, ok, "Test case %s didn't encode an array.", tt.desc) | |
277 | assert.Equal(t, []interface{}{tt.expected, tt.expected}, arr, "Unexpected encoder output.") | |
278 | }) | |
270 | 279 | } |
271 | 280 | } |
272 | 281 |
0 | // Copyright (c) 2017 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zaptest | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | ||
25 | "go.uber.org/zap" | |
26 | "go.uber.org/zap/zapcore" | |
27 | ) | |
28 | ||
29 | // LoggerOption configures the test logger built by NewLogger. | |
30 | type LoggerOption interface { | |
31 | applyLoggerOption(*loggerOptions) | |
32 | } | |
33 | ||
34 | type loggerOptions struct { | |
35 | Level zapcore.LevelEnabler | |
36 | } | |
37 | ||
38 | type loggerOptionFunc func(*loggerOptions) | |
39 | ||
40 | func (f loggerOptionFunc) applyLoggerOption(opts *loggerOptions) { | |
41 | f(opts) | |
42 | } | |
43 | ||
44 | // Level controls which messages are logged by a test Logger built by | |
45 | // NewLogger. | |
46 | func Level(enab zapcore.LevelEnabler) LoggerOption { | |
47 | return loggerOptionFunc(func(opts *loggerOptions) { | |
48 | opts.Level = enab | |
49 | }) | |
50 | } | |
51 | ||
52 | // NewLogger builds a new Logger that logs all messages to the given | |
53 | // testing.TB. | |
54 | // | |
55 | // logger := zaptest.NewLogger(t) | |
56 | // | |
57 | // Use this with a *testing.T or *testing.B to get logs which get printed only | |
58 | // if a test fails or if you ran go test -v. | |
59 | // | |
60 | // The returned logger defaults to logging debug level messages and above. | |
61 | // This may be changd by passing a zaptest.Level during construction. | |
62 | // | |
63 | // logger := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)) | |
64 | func NewLogger(t TestingT, opts ...LoggerOption) *zap.Logger { | |
65 | cfg := loggerOptions{ | |
66 | Level: zapcore.DebugLevel, | |
67 | } | |
68 | for _, o := range opts { | |
69 | o.applyLoggerOption(&cfg) | |
70 | } | |
71 | ||
72 | writer := newTestingWriter(t) | |
73 | return zap.New( | |
74 | zapcore.NewCore( | |
75 | zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), | |
76 | writer, | |
77 | cfg.Level, | |
78 | ), | |
79 | ||
80 | // Send zap errors to the same writer and mark the test as failed if | |
81 | // that happens. | |
82 | zap.ErrorOutput(writer.WithMarkFailed(true)), | |
83 | ) | |
84 | } | |
85 | ||
86 | // testingWriter is a WriteSyncer that writes to the given testing.TB. | |
87 | type testingWriter struct { | |
88 | t TestingT | |
89 | ||
90 | // If true, the test will be marked as failed if this testingWriter is | |
91 | // ever used. | |
92 | markFailed bool | |
93 | } | |
94 | ||
95 | func newTestingWriter(t TestingT) testingWriter { | |
96 | return testingWriter{t: t} | |
97 | } | |
98 | ||
99 | // WithMarkFailed returns a copy of this testingWriter with markFailed set to | |
100 | // the provided value. | |
101 | func (w testingWriter) WithMarkFailed(v bool) testingWriter { | |
102 | w.markFailed = v | |
103 | return w | |
104 | } | |
105 | ||
106 | func (w testingWriter) Write(p []byte) (n int, err error) { | |
107 | n = len(p) | |
108 | ||
109 | // Strip trailing newline because t.Log always adds one. | |
110 | p = bytes.TrimRight(p, "\n") | |
111 | ||
112 | // Note: t.Log is safe for concurrent use. | |
113 | w.t.Logf("%s", p) | |
114 | if w.markFailed { | |
115 | w.t.Fail() | |
116 | } | |
117 | ||
118 | return n, nil | |
119 | } | |
120 | ||
121 | func (w testingWriter) Sync() error { | |
122 | return nil | |
123 | } |
0 | // Copyright (c) 2017 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zaptest | |
21 | ||
22 | import ( | |
23 | "errors" | |
24 | "fmt" | |
25 | "io" | |
26 | "strings" | |
27 | "testing" | |
28 | ||
29 | "go.uber.org/zap" | |
30 | "go.uber.org/zap/internal/ztest" | |
31 | "go.uber.org/zap/zapcore" | |
32 | ||
33 | "github.com/stretchr/testify/assert" | |
34 | ) | |
35 | ||
36 | func TestTestLogger(t *testing.T) { | |
37 | ts := newTestLogSpy(t) | |
38 | defer ts.AssertPassed() | |
39 | ||
40 | log := NewLogger(ts) | |
41 | ||
42 | log.Info("received work order") | |
43 | log.Debug("starting work") | |
44 | log.Warn("work may fail") | |
45 | log.Error("work failed", zap.Error(errors.New("great sadness"))) | |
46 | ||
47 | assert.Panics(t, func() { | |
48 | log.Panic("failed to do work") | |
49 | }, "log.Panic should panic") | |
50 | ||
51 | ts.AssertMessages( | |
52 | "INFO received work order", | |
53 | "DEBUG starting work", | |
54 | "WARN work may fail", | |
55 | `ERROR work failed {"error": "great sadness"}`, | |
56 | "PANIC failed to do work", | |
57 | ) | |
58 | } | |
59 | ||
60 | func TestTestLoggerSupportsLevels(t *testing.T) { | |
61 | ts := newTestLogSpy(t) | |
62 | defer ts.AssertPassed() | |
63 | ||
64 | log := NewLogger(ts, Level(zap.WarnLevel)) | |
65 | ||
66 | log.Info("received work order") | |
67 | log.Debug("starting work") | |
68 | log.Warn("work may fail") | |
69 | log.Error("work failed", zap.Error(errors.New("great sadness"))) | |
70 | ||
71 | assert.Panics(t, func() { | |
72 | log.Panic("failed to do work") | |
73 | }, "log.Panic should panic") | |
74 | ||
75 | ts.AssertMessages( | |
76 | "WARN work may fail", | |
77 | `ERROR work failed {"error": "great sadness"}`, | |
78 | "PANIC failed to do work", | |
79 | ) | |
80 | } | |
81 | ||
82 | func TestTestingWriter(t *testing.T) { | |
83 | ts := newTestLogSpy(t) | |
84 | w := newTestingWriter(ts) | |
85 | ||
86 | n, err := io.WriteString(w, "hello\n\n") | |
87 | assert.NoError(t, err, "WriteString must not fail") | |
88 | assert.Equal(t, 7, n) | |
89 | } | |
90 | ||
91 | func TestTestLoggerErrorOutput(t *testing.T) { | |
92 | // This test verifies that the test logger logs internal messages to the | |
93 | // testing.T and marks the test as failed. | |
94 | ||
95 | ts := newTestLogSpy(t) | |
96 | defer ts.AssertFailed() | |
97 | ||
98 | log := NewLogger(ts) | |
99 | ||
100 | // Replace with a core that fails. | |
101 | log = log.WithOptions(zap.WrapCore(func(zapcore.Core) zapcore.Core { | |
102 | return zapcore.NewCore( | |
103 | zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), | |
104 | zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})), | |
105 | zapcore.DebugLevel, | |
106 | ) | |
107 | })) | |
108 | ||
109 | log.Info("foo") // this fails | |
110 | ||
111 | if assert.Len(t, ts.Messages, 1, "expected a log message") { | |
112 | assert.Regexp(t, `write error: failed`, ts.Messages[0]) | |
113 | } | |
114 | } | |
115 | ||
116 | // testLogSpy is a testing.TB that captures logged messages. | |
117 | type testLogSpy struct { | |
118 | testing.TB | |
119 | ||
120 | failed bool | |
121 | Messages []string | |
122 | } | |
123 | ||
124 | func newTestLogSpy(t testing.TB) *testLogSpy { | |
125 | return &testLogSpy{TB: t} | |
126 | } | |
127 | ||
128 | func (t *testLogSpy) Fail() { | |
129 | t.failed = true | |
130 | } | |
131 | ||
132 | func (t *testLogSpy) Failed() bool { | |
133 | return t.failed | |
134 | } | |
135 | ||
136 | func (t *testLogSpy) FailNow() { | |
137 | t.Fail() | |
138 | t.TB.FailNow() | |
139 | } | |
140 | ||
141 | func (t *testLogSpy) Logf(format string, args ...interface{}) { | |
142 | // Log messages are in the format, | |
143 | // | |
144 | // 2017-10-27T13:03:01.000-0700 DEBUG your message here {data here} | |
145 | // | |
146 | // We strip the first part of these messages because we can't really test | |
147 | // for the timestamp from these tests. | |
148 | m := fmt.Sprintf(format, args...) | |
149 | m = m[strings.IndexByte(m, '\t')+1:] | |
150 | t.Messages = append(t.Messages, m) | |
151 | t.TB.Log(m) | |
152 | } | |
153 | ||
154 | func (t *testLogSpy) AssertMessages(msgs ...string) { | |
155 | assert.Equal(t.TB, msgs, t.Messages, "logged messages did not match") | |
156 | } | |
157 | ||
158 | func (t *testLogSpy) AssertPassed() { | |
159 | t.assertFailed(false, "expected test to pass") | |
160 | } | |
161 | ||
162 | func (t *testLogSpy) AssertFailed() { | |
163 | t.assertFailed(true, "expected test to fail") | |
164 | } | |
165 | ||
166 | func (t *testLogSpy) assertFailed(v bool, msg string) { | |
167 | assert.Equal(t.TB, v, t.failed, msg) | |
168 | } |
0 | // Copyright (c) 2017 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zaptest | |
21 | ||
22 | // TestingT is a subset of the API provided by all *testing.T and *testing.B | |
23 | // objects. | |
24 | type TestingT interface { | |
25 | // Logs the given message without failing the test. | |
26 | Logf(string, ...interface{}) | |
27 | ||
28 | // Logs the given message and marks the test as failed. | |
29 | Errorf(string, ...interface{}) | |
30 | ||
31 | // Marks the test as failed. | |
32 | Fail() | |
33 | ||
34 | // Returns true if the test has been marked as failed. | |
35 | Failed() bool | |
36 | ||
37 | // Returns the name of the test. | |
38 | Name() string | |
39 | ||
40 | // Marks the test as failed and stops execution of that test. | |
41 | FailNow() | |
42 | } | |
43 | ||
44 | // Note: We currently only rely on Logf. We are including Errorf and FailNow | |
45 | // in the interface in anticipation of future need since we can't extend the | |
46 | // interface without a breaking change. |
0 | // Copyright (c) 2017 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | package zaptest | |
21 | ||
22 | import "testing" | |
23 | ||
24 | // Just a compile-time test to ensure that TestingT matches the testing.TB | |
25 | // interface. We could do this in testingt.go but that would put a dependency | |
26 | // on the "testing" package from zaptest. | |
27 | ||
28 | var _ TestingT = (testing.TB)(nil) |
17 | 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
18 | 18 | // THE SOFTWARE. |
19 | 19 | |
20 | // +build !go1.9 | |
21 | ||
22 | 20 | package zaptest |
23 | 21 | |
24 | import ( | |
25 | "bytes" | |
26 | "errors" | |
27 | "io/ioutil" | |
28 | "strings" | |
22 | import "go.uber.org/zap/internal/ztest" | |
23 | ||
24 | type ( | |
25 | // A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. | |
26 | Syncer = ztest.Syncer | |
27 | ||
28 | // A Discarder sends all writes to ioutil.Discard. | |
29 | Discarder = ztest.Discarder | |
30 | ||
31 | // FailWriter is a WriteSyncer that always returns an error on writes. | |
32 | FailWriter = ztest.FailWriter | |
33 | ||
34 | // ShortWriter is a WriteSyncer whose write method never returns an error, | |
35 | // but always reports that it wrote one byte less than the input slice's | |
36 | // length (thus, a "short write"). | |
37 | ShortWriter = ztest.ShortWriter | |
38 | ||
39 | // Buffer is an implementation of zapcore.WriteSyncer that sends all writes to | |
40 | // a bytes.Buffer. It has convenience methods to split the accumulated buffer | |
41 | // on newlines. | |
42 | Buffer = ztest.Buffer | |
29 | 43 | ) |
30 | ||
31 | // A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. | |
32 | type Syncer struct { | |
33 | err error | |
34 | called bool | |
35 | } | |
36 | ||
37 | // SetError sets the error that the Sync method will return. | |
38 | func (s *Syncer) SetError(err error) { | |
39 | s.err = err | |
40 | } | |
41 | ||
42 | // Sync records that it was called, then returns the user-supplied error (if | |
43 | // any). | |
44 | func (s *Syncer) Sync() error { | |
45 | s.called = true | |
46 | return s.err | |
47 | } | |
48 | ||
49 | // Called reports whether the Sync method was called. | |
50 | func (s *Syncer) Called() bool { | |
51 | return s.called | |
52 | } | |
53 | ||
54 | // A Discarder sends all writes to ioutil.Discard. | |
55 | type Discarder struct{ Syncer } | |
56 | ||
57 | // Write implements io.Writer. | |
58 | func (d *Discarder) Write(b []byte) (int, error) { | |
59 | return ioutil.Discard.Write(b) | |
60 | } | |
61 | ||
62 | // FailWriter is a WriteSyncer that always returns an error on writes. | |
63 | type FailWriter struct{ Syncer } | |
64 | ||
65 | // Write implements io.Writer. | |
66 | func (w FailWriter) Write(b []byte) (int, error) { | |
67 | return len(b), errors.New("failed") | |
68 | } | |
69 | ||
70 | // ShortWriter is a WriteSyncer whose write method never fails, but | |
71 | // nevertheless fails to the last byte of the input. | |
72 | type ShortWriter struct{ Syncer } | |
73 | ||
74 | // Write implements io.Writer. | |
75 | func (w ShortWriter) Write(b []byte) (int, error) { | |
76 | return len(b) - 1, nil | |
77 | } | |
78 | ||
79 | // Buffer is an implementation of zapcore.WriteSyncer that sends all writes to | |
80 | // a bytes.Buffer. It has convenience methods to split the accumulated buffer | |
81 | // on newlines. | |
82 | type Buffer struct { | |
83 | bytes.Buffer | |
84 | Syncer | |
85 | } | |
86 | ||
87 | // Lines returns the current buffer contents, split on newlines. | |
88 | func (b *Buffer) Lines() []string { | |
89 | output := strings.Split(b.String(), "\n") | |
90 | return output[:len(output)-1] | |
91 | } | |
92 | ||
93 | // Stripped returns the current buffer contents with the last trailing newline | |
94 | // stripped. | |
95 | func (b *Buffer) Stripped() string { | |
96 | return strings.TrimRight(b.String(), "\n") | |
97 | } |
0 | // Copyright (c) 2016 Uber Technologies, Inc. | |
1 | // | |
2 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | // of this software and associated documentation files (the "Software"), to deal | |
4 | // in the Software without restriction, including without limitation the rights | |
5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | // copies of the Software, and to permit persons to whom the Software is | |
7 | // furnished to do so, subject to the following conditions: | |
8 | // | |
9 | // The above copyright notice and this permission notice shall be included in | |
10 | // all copies or substantial portions of the Software. | |
11 | // | |
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | // THE SOFTWARE. | |
19 | ||
20 | // Type aliases are available only in Go 1.9 and later. | |
21 | // +build go1.9 | |
22 | ||
23 | package zaptest | |
24 | ||
25 | import "go.uber.org/zap/internal/ztest" | |
26 | ||
27 | type ( | |
28 | // A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. | |
29 | Syncer = ztest.Syncer | |
30 | ||
31 | // A Discarder sends all writes to ioutil.Discard. | |
32 | Discarder = ztest.Discarder | |
33 | ||
34 | // FailWriter is a WriteSyncer that always returns an error on writes. | |
35 | FailWriter = ztest.FailWriter | |
36 | ||
37 | // ShortWriter is a WriteSyncer whose write method never fails, but | |
38 | // nevertheless fails to the last byte of the input. | |
39 | ShortWriter = ztest.ShortWriter | |
40 | ||
41 | // Buffer is an implementation of zapcore.WriteSyncer that sends all writes to | |
42 | // a bytes.Buffer. It has convenience methods to split the accumulated buffer | |
43 | // on newlines. | |
44 | Buffer = ztest.Buffer | |
45 | ) |
20 | 20 | package zaptest |
21 | 21 | |
22 | 22 | import ( |
23 | "bufio" | |
24 | 23 | "errors" |
25 | "os" | |
26 | "strings" | |
27 | 24 | "testing" |
28 | 25 | |
29 | 26 | "github.com/stretchr/testify/assert" |
30 | "github.com/stretchr/testify/require" | |
31 | 27 | ) |
32 | ||
33 | func readCode(t testing.TB, fname string) []string { | |
34 | f, err := os.Open(fname) | |
35 | require.NoError(t, err, "Failed to read %s.", fname) | |
36 | defer func() { | |
37 | require.NoError(t, f.Close(), "Error closing file %s.", fname) | |
38 | }() | |
39 | ||
40 | var lines []string | |
41 | s := bufio.NewScanner(f) | |
42 | for s.Scan() { | |
43 | l := s.Text() | |
44 | if len(l) == 0 { | |
45 | continue | |
46 | } | |
47 | if strings.HasPrefix(l, "//") { | |
48 | continue | |
49 | } | |
50 | if strings.HasPrefix(l, "package ") { | |
51 | continue | |
52 | } | |
53 | lines = append(lines, l) | |
54 | } | |
55 | return lines | |
56 | } | |
57 | ||
58 | func TestCopiedCodeInSync(t *testing.T) { | |
59 | // Until we drop Go 1.8 support, we need to keep a near-exact copy of the | |
60 | // ztest package's WriteSyncer test spies in zaptest. This test ensures that | |
61 | // the two files stay in sync. | |
62 | assert.Equal(t, | |
63 | readCode(t, "../internal/ztest/writer.go"), | |
64 | readCode(t, "writer.go"), | |
65 | "Writer spy implementations in zaptest and internal/ztest should be identical.", | |
66 | ) | |
67 | } | |
68 | 28 | |
69 | 29 | func TestSyncer(t *testing.T) { |
70 | 30 | err := errors.New("sentinel") |