Codebase list golang-go.uber-zap / 8c5b0d7
New upstream version 1.9.1 aviau 5 years ago
43 changed file(s) with 1261 addition(s) and 460 deletion(s). Raw diff Collapse all Expand all
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
1010 ## Quick Start
1111
1212 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
1414 packages and includes both structured and `printf`-style APIs.
1515
1616 ```go
00 language: go
11 sudo: false
22 go:
3 - 1.8
4 - 1.9
3 - 1.9.x
4 - 1.10.x
55 go_import_path: go.uber.org/zap
66 env:
77 global:
00 # 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.
129
230 ## v1.7.1 (25 Sep 2017)
331
265293 [#487]: https://github.com/uber-go/zap/pull/487
266294 [#490]: https://github.com/uber-go/zap/pull/490
267295 [#491]: https://github.com/uber-go/zap/pull/491
268 [#491]: https://github.com/uber-go/zap/pull/439
269296 [#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
134134 logger := zap.New(core)
135135 ```
136136
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
137152 [go-proverbs]: https://go-proverbs.github.io/
138153 [import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths
139154 [lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2
88 # stable release.
99 GO_VERSION := $(shell go version | cut -d " " -f 3)
1010 GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION)))
11 LINTABLE_MINOR_VERSIONS := 9
11 LINTABLE_MINOR_VERSIONS := 10
1212 ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),)
1313 SHOULD_LINT := true
1414 endif
1010 ## Quick Start
1111
1212 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
1414 packages and includes both structured and `printf`-style APIs.
1515
1616 ```go
2828 // Array constructs a field with the given key and ArrayMarshaler. It provides
2929 // a flexible, but still type-safe and efficient, way to add array-like types
3030 // 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}
3333 }
3434
3535 // 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 {
3737 return Array(key, bools(bs))
3838 }
3939
4040 // ByteStrings constructs a field that carries a slice of []byte, each of which
4141 // must be UTF-8 encoded text.
42 func ByteStrings(key string, bss [][]byte) zapcore.Field {
42 func ByteStrings(key string, bss [][]byte) Field {
4343 return Array(key, byteStringsArray(bss))
4444 }
4545
4646 // 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 {
4848 return Array(key, complex128s(nums))
4949 }
5050
5151 // 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 {
5353 return Array(key, complex64s(nums))
5454 }
5555
5656 // 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 {
5858 return Array(key, durations(ds))
5959 }
6060
6161 // 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 {
6363 return Array(key, float64s(nums))
6464 }
6565
6666 // 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 {
6868 return Array(key, float32s(nums))
6969 }
7070
7171 // 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 {
7373 return Array(key, ints(nums))
7474 }
7575
7676 // 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 {
7878 return Array(key, int64s(nums))
7979 }
8080
8181 // 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 {
8383 return Array(key, int32s(nums))
8484 }
8585
8686 // 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 {
8888 return Array(key, int16s(nums))
8989 }
9090
9191 // 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 {
9393 return Array(key, int8s(nums))
9494 }
9595
9696 // 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 {
9898 return Array(key, stringArray(ss))
9999 }
100100
101101 // 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 {
103103 return Array(key, times(ts))
104104 }
105105
106106 // 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 {
108108 return Array(key, uints(nums))
109109 }
110110
111111 // 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 {
113113 return Array(key, uint64s(nums))
114114 }
115115
116116 // 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 {
118118 return Array(key, uint32s(nums))
119119 }
120120
121121 // 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 {
123123 return Array(key, uint16s(nums))
124124 }
125125
126126 // 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 {
128128 return Array(key, uint8s(nums))
129129 }
130130
131131 // 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 {
133133 return Array(key, uintptrs(us))
134134 }
135135
136136 // 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 {
138138 return Array(key, errArray(errs))
139139 }
140140
5151 func TestArrayWrappers(t *testing.T) {
5252 tests := []struct {
5353 desc string
54 field zapcore.Field
54 field Field
5555 expected []interface{}
5656 }{
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{}{}},
7777 {"bools", Bools("", []bool{true, false}), []interface{}{true, false}},
7878 {"byte strings", ByteStrings("", [][]byte{{1, 2}, {3, 4}}), []interface{}{[]byte{1, 2}, []byte{3, 4}}},
7979 {"complex128s", Complex128s("", []complex128{1 + 2i, 3 + 4i}), []interface{}{1 + 2i, 3 + 4i}},
123123 ))
124124 }
125125
126 func fakeFields() []zapcore.Field {
127 return []zapcore.Field{
126 func fakeFields() []zap.Field {
127 return []zap.Field{
128128 zap.Int("int", _tenInts[0]),
129129 zap.Ints("ints", _tenInts),
130130 zap.String("string", _tenStrings[0]),
2020 // Package buffer provides a thin wrapper around a byte slice. Unlike the
2121 // standard library's bytes.Buffer, it supports a portion of the strconv
2222 // package's zero-allocation formatters.
23 package buffer
23 package buffer // import "go.uber.org/zap/buffer"
2424
2525 import "strconv"
2626
9797 return len(bs), nil
9898 }
9999
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
100109 // Free returns the Buffer to its Pool.
101110 //
102111 // Callers must not retain references to the Buffer after calling Free.
7373 // EncoderConfig sets options for the chosen encoder. See
7474 // zapcore.EncoderConfig for details.
7575 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.
7878 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.
8080 // The default is standard error.
8181 //
8282 // Note that this setting only affects internal errors; for sample code that
209209 }
210210
211211 if len(cfg.InitialFields) > 0 {
212 fs := make([]zapcore.Field, 0, len(cfg.InitialFields))
212 fs := make([]Field, 0, len(cfg.InitialFields))
213213 keys := make([]string, 0, len(cfg.InitialFields))
214214 for k := range cfg.InitialFields {
215215 keys = append(keys, k)
4747 // "attempt", 3,
4848 // "backoff", time.Second,
4949 // )
50 // sugar.Printf("failed to fetch URL: %s", "http://example.com")
50 // sugar.Infof("failed to fetch URL: %s", "http://example.com")
5151 //
5252 // By default, loggers are unbuffered. However, since zap's low-level APIs
5353 // allow buffering, calling Sync before letting your process exit is a good
3030 }}
3131
3232 // Error is shorthand for the common idiom NamedError("error", err).
33 func Error(err error) zapcore.Field {
33 func Error(err error) Field {
3434 return NamedError("error", err)
3535 }
3636
4141 //
4242 // For the common case in which the key is simply "error", the Error function
4343 // is shorter and less repetitive.
44 func NamedError(key string, err error) zapcore.Field {
44 func NamedError(key string, err error) Field {
4545 if err == nil {
4646 return Skip()
4747 }
48 return zapcore.Field{Key: key, Type: zapcore.ErrorType, Interface: err}
48 return Field{Key: key, Type: zapcore.ErrorType, Interface: err}
4949 }
5050
5151 type errArray []error
3535
3636 tests := []struct {
3737 name string
38 field zapcore.Field
39 expect zapcore.Field
38 field Field
39 expect Field
4040 }{
4141 {"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)},
4343 {"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)},
4545 {"Any:Error", Any("k", errors.New("v")), NamedError("k", errors.New("v"))},
4646 {"Any:Errors", Any("k", []error{errors.New("v")}), Errors("k", []error{errors.New("v")})},
4747 }
5757 func TestErrorArrayConstructor(t *testing.T) {
5858 tests := []struct {
5959 desc string
60 field zapcore.Field
60 field Field
6161 expected []interface{}
6262 }{
63 {"empty errors", Errors("", []error{}), []interface{}(nil)},
63 {"empty errors", Errors("", []error{}), []interface{}{}},
6464 {
6565 "errors",
6666 Errors("", []error{nil, errors.New("foo"), nil, errors.New("bar")}),
2727 "go.uber.org/zap/zapcore"
2828 )
2929
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
3034 // Skip constructs a no-op field, which is often useful when handling invalid
3135 // 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}
3438 }
3539
3640 // Binary constructs a field that carries an opaque binary blob.
3842 // Binary data is serialized in an encoding-appropriate format. For example,
3943 // zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text,
4044 // 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}
4347 }
4448
4549 // 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 {
4751 var ival int64
4852 if val {
4953 ival = 1
5054 }
51 return zapcore.Field{Key: key, Type: zapcore.BoolType, Integer: ival}
55 return Field{Key: key, Type: zapcore.BoolType, Integer: ival}
5256 }
5357
5458 // ByteString constructs a field that carries UTF-8 encoded text as a []byte.
5559 // To log opaque binary blobs (which aren't necessarily valid UTF-8), use
5660 // 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}
5963 }
6064
6165 // Complex128 constructs a field that carries a complex number. Unlike most
6266 // numeric fields, this costs an allocation (to convert the complex128 to
6367 // 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}
6670 }
6771
6872 // Complex64 constructs a field that carries a complex number. Unlike most
6973 // numeric fields, this costs an allocation (to convert the complex64 to
7074 // 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}
7377 }
7478
7579 // Float64 constructs a field that carries a float64. The way the
7680 // floating-point value is represented is encoder-dependent, so marshaling is
7781 // 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))}
8084 }
8185
8286 // Float32 constructs a field that carries a float32. The way the
8387 // floating-point value is represented is encoder-dependent, so marshaling is
8488 // 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))}
8791 }
8892
8993 // 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 {
9195 return Int64(key, int64(val))
9296 }
9397
9498 // 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}
97101 }
98102
99103 // 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)}
102106 }
103107
104108 // 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)}
107111 }
108112
109113 // 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)}
112116 }
113117
114118 // 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}
117121 }
118122
119123 // 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 {
121125 return Uint64(key, uint64(val))
122126 }
123127
124128 // 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)}
127131 }
128132
129133 // 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)}
132136 }
133137
134138 // 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)}
137141 }
138142
139143 // 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)}
142146 }
143147
144148 // 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)}
147151 }
148152
149153 // Reflect constructs a field with the given key and an arbitrary object. It uses
153157 //
154158 // If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect
155159 // 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}
158162 }
159163
160164 // Namespace creates a named, isolated scope within the logger's context. All
162166 //
163167 // This helps prevent key collisions when injecting loggers into sub-components
164168 // 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}
167171 }
168172
169173 // Stringer constructs a field with the given key and the output of the value's
170174 // 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
176180 // 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()}
179183 }
180184
181185 // Stack constructs a field that stores a stacktrace of the current goroutine
182186 // under provided key. Keep in mind that taking a stacktrace is eager and
183187 // expensive (relatively speaking); this function both makes an allocation and
184188 // takes about two microseconds.
185 func Stack(key string) zapcore.Field {
189 func Stack(key string) Field {
186190 // Returning the stacktrace as a string costs an allocation, but saves us
187191 // from expanding the zapcore.Field union struct to include a byte slice. Since
188192 // taking a stacktrace is already so expensive (~10us), the extra allocation
192196
193197 // Duration constructs a field with the given key and value. The encoder
194198 // 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)}
197201 }
198202
199203 // Object constructs a field with the given key and ObjectMarshaler. It
200204 // provides a flexible, but still type-safe and efficient, way to add map- or
201205 // struct-like user-defined types to the logging context. The struct's
202206 // 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}
205209 }
206210
207211 // Any takes a key and an arbitrary value and chooses the best way to represent
211215 // Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between
212216 // them. To minimize surprises, []byte values are treated as binary blobs, byte
213217 // 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 {
215219 switch val := value.(type) {
216220 case zapcore.ObjectMarshaler:
217221 return Object(key, val)
3636 return nil
3737 }
3838
39 func assertCanBeReused(t testing.TB, field zapcore.Field) {
39 func assertCanBeReused(t testing.TB, field Field) {
4040 var wg sync.WaitGroup
4141
4242 for i := 0; i < 100; i++ {
6464
6565 tests := []struct {
6666 name string
67 field zapcore.Field
68 expect zapcore.Field
67 field Field
68 expect Field
6969 }{
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)},
9595 {"Any:ObjectMarshaler", Any("k", name), Object("k", name)},
9696 {"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))},
9797 {"Any:Stringer", Any("k", addr), Stringer("k", addr)},
138138 {"Any:Duration", Any("k", time.Second), Duration("k", time.Second)},
139139 {"Any:Durations", Any("k", []time.Duration{time.Second}), Durations("k", []time.Duration{time.Second})},
140140 {"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}},
142142 }
143143
144144 for _, tt := range tests {
137137 }, nil
138138 }
139139
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) {
141141 switch lvl {
142142 case DebugLevel:
143143 return logger.Debug, nil
158158 }
159159
160160 type loggerWriter struct {
161 logFunc func(msg string, fields ...zapcore.Field)
161 logFunc func(msg string, fields ...Field)
162162 }
163163
164164 func (l *loggerWriter) Write(p []byte) (int, error) {
5151 S().Info("captured")
5252 expected := observer.LoggedEntry{
5353 Entry: zapcore.Entry{Message: "captured"},
54 Context: []zapcore.Field{},
54 Context: []Field{},
5555 }
5656 assert.Equal(
5757 t,
156156
157157 assert.Equal(t, []observer.LoggedEntry{{
158158 Entry: zapcore.Entry{Message: "redirected"},
159 Context: []zapcore.Field{},
159 Context: []Field{},
160160 }}, logs.AllUntimed(), "Unexpected global log output.")
161161 })
162162
189189
190190 assert.Equal(t, []observer.LoggedEntry{{
191191 Entry: zapcore.Entry{Level: level, Message: "redirected"},
192 Context: []zapcore.Field{},
192 Context: []Field{},
193193 }}, logs.AllUntimed(), "Unexpected global log output.")
194194 })
195195 }
268268 func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) {
269269 require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged")
270270 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.")
272272 assert.Equal(t, "redirected", entry.Entry.Message, "Unexpected entry message.")
273273 assert.Regexp(
274274 t,
4747
4848 switch r.Method {
4949
50 case "GET":
50 case http.MethodGet:
5151 current := lvl.Level()
5252 enc.Encode(payload{Level: &current})
5353
54 case "PUT":
54 case http.MethodPut:
5555 var req payload
5656
5757 if errmess := func() string {
155155
156156 // With creates a child logger and adds structured context to it. Fields added
157157 // 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 {
159159 if len(fields) == 0 {
160160 return log
161161 }
173173
174174 // Debug logs a message at DebugLevel. The message includes any fields passed
175175 // 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) {
177177 if ce := log.check(DebugLevel, msg); ce != nil {
178178 ce.Write(fields...)
179179 }
181181
182182 // Info logs a message at InfoLevel. The message includes any fields passed
183183 // 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) {
185185 if ce := log.check(InfoLevel, msg); ce != nil {
186186 ce.Write(fields...)
187187 }
189189
190190 // Warn logs a message at WarnLevel. The message includes any fields passed
191191 // 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) {
193193 if ce := log.check(WarnLevel, msg); ce != nil {
194194 ce.Write(fields...)
195195 }
197197
198198 // Error logs a message at ErrorLevel. The message includes any fields passed
199199 // 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) {
201201 if ce := log.check(ErrorLevel, msg); ce != nil {
202202 ce.Write(fields...)
203203 }
209209 // If the logger is in development mode, it then panics (DPanic means
210210 // "development panic"). This is useful for catching errors that are
211211 // 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) {
213213 if ce := log.check(DPanicLevel, msg); ce != nil {
214214 ce.Write(fields...)
215215 }
219219 // at the log site, as well as any fields accumulated on the logger.
220220 //
221221 // 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) {
223223 if ce := log.check(PanicLevel, msg); ce != nil {
224224 ce.Write(fields...)
225225 }
230230 //
231231 // The logger then calls os.Exit(1), even if logging at FatalLevel is
232232 // disabled.
233 func (log *Logger) Fatal(msg string, fields ...zapcore.Field) {
233 func (log *Logger) Fatal(msg string, fields ...Field) {
234234 if ce := log.check(FatalLevel, msg); ce != nil {
235235 ce.Write(fields...)
236236 }
206206 // Don't include allocating these helper slices in the benchmark. Since
207207 // access to them isn't synchronized, we can't run the benchmark in
208208 // parallel.
209 first := make([]zapcore.Field, batchSize)
210 second := make([]zapcore.Field, batchSize)
209 first := make([]Field, batchSize)
210 second := make([]Field, batchSize)
211211 b.ResetTimer()
212212
213213 for i := 0; i < b.N; i++ {
8888 logger.Info("")
8989 assert.Equal(
9090 t,
91 observer.LoggedEntry{Context: []zapcore.Field{Int("foo", 42), String("bar", "baz")}},
91 observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}},
9292 logs.AllUntimed()[0],
9393 "Unexpected output with initial fields set.",
9494 )
105105 logger.Info("")
106106
107107 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)}},
111111 }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.")
112112 })
113113 }
170170 func TestLoggerLeveledMethods(t *testing.T) {
171171 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
172172 tests := []struct {
173 method func(string, ...zapcore.Field)
173 method func(string, ...Field)
174174 expectedLevel zapcore.Level
175175 }{
176176 {logger.Debug, DebugLevel},
231231 assert.NotPanics(t, func() { logger.DPanic("") })
232232 assert.Equal(
233233 t,
234 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}},
234 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
235235 logs.AllUntimed(),
236236 "Unexpected log output from DPanic in production mode.",
237237 )
240240 assert.Panics(t, func() { logger.DPanic("") })
241241 assert.Equal(
242242 t,
243 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}},
243 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
244244 logs.AllUntimed(),
245245 "Unexpected log output from DPanic in development mode.",
246246 )
421421 t,
422422 observer.LoggedEntry{
423423 Entry: zapcore.Entry{Level: InfoLevel},
424 Context: []zapcore.Field{String("foo", "bar")},
424 Context: []Field{String("foo", "bar")},
425425 },
426426 obs,
427427 "Unexpected log output.",
5454 }
5555
5656 // Fields adds fields to the Logger.
57 func Fields(fs ...zapcore.Field) Option {
57 func Fields(fs ...Field) Option {
5858 return optionFunc(func(log *Logger) {
5959 log.core = log.core.With(fs)
6060 })
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 }
6161 }
6262
6363 // 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.
6767 //
6868 // For example,
6969 // sugaredLogger.With(
234234 }
235235 }
236236
237 func (s *SugaredLogger) sweetenFields(args []interface{}) []zapcore.Field {
237 func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
238238 if len(args) == 0 {
239239 return nil
240240 }
241241
242242 // Allocate enough space for the worst case; if users pass only structured
243243 // fields, we shouldn't penalize them with extra allocations.
244 fields := make([]zapcore.Field, 0, len(args))
244 fields := make([]Field, 0, len(args))
245245 var invalid invalidPairs
246246
247247 for i := 0; i < len(args); {
248248 // 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 {
250250 fields = append(fields, f)
251251 i++
252252 continue
3636 ignored := func(msg interface{}) observer.LoggedEntry {
3737 return observer.LoggedEntry{
3838 Entry: zapcore.Entry{Level: DPanicLevel, Message: _oddNumberErrMsg},
39 Context: []zapcore.Field{Any("ignored", msg)},
39 Context: []Field{Any("ignored", msg)},
4040 }
4141 }
4242 nonString := func(pairs ...invalidPair) observer.LoggedEntry {
4343 return observer.LoggedEntry{
4444 Entry: zapcore.Entry{Level: DPanicLevel, Message: _nonStringKeyErrMsg},
45 Context: []zapcore.Field{Array("invalid", invalidPairs(pairs))},
45 Context: []Field{Array("invalid", invalidPairs(pairs))},
4646 }
4747 }
4848
4949 tests := []struct {
5050 desc string
5151 args []interface{}
52 expected []zapcore.Field
52 expected []Field
5353 errLogs []observer.LoggedEntry
5454 }{
5555 {
5656 desc: "nil args",
5757 args: nil,
58 expected: []zapcore.Field{},
58 expected: []Field{},
5959 errLogs: nil,
6060 },
6161 {
6262 desc: "empty slice of args",
6363 args: []interface{}{},
64 expected: []zapcore.Field{},
64 expected: []Field{},
6565 errLogs: nil,
6666 },
6767 {
6868 desc: "just a dangling key",
6969 args: []interface{}{"should ignore"},
70 expected: []zapcore.Field{},
70 expected: []Field{},
7171 errLogs: []observer.LoggedEntry{ignored("should ignore")},
7272 },
7373 {
7474 desc: "well-formed key-value pairs",
7575 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")},
7777 errLogs: nil,
7878 },
7979 {
8080 desc: "just a structured field",
8181 args: []interface{}{Int("foo", 42)},
82 expected: []zapcore.Field{Int("foo", 42)},
82 expected: []Field{Int("foo", 42)},
8383 errLogs: nil,
8484 },
8585 {
8686 desc: "structured field and a dangling key",
8787 args: []interface{}{Int("foo", 42), "dangling"},
88 expected: []zapcore.Field{Int("foo", 42)},
88 expected: []Field{Int("foo", 42)},
8989 errLogs: []observer.LoggedEntry{ignored("dangling")},
9090 },
9191 {
9292 desc: "structured field and a dangling non-string key",
9393 args: []interface{}{Int("foo", 42), 13},
94 expected: []zapcore.Field{Int("foo", 42)},
94 expected: []Field{Int("foo", 42)},
9595 errLogs: []observer.LoggedEntry{ignored(13)},
9696 },
9797 {
9898 desc: "key-value pair and a dangling key",
9999 args: []interface{}{"foo", 42, "dangling"},
100 expected: []zapcore.Field{Int("foo", 42)},
100 expected: []Field{Int("foo", 42)},
101101 errLogs: []observer.LoggedEntry{ignored("dangling")},
102102 },
103103 {
104104 desc: "pairs, a structured field, and a dangling key",
105105 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")},
107107 errLogs: []observer.LoggedEntry{ignored("dangling")},
108108 },
109109 {
110110 desc: "one non-string key",
111111 args: []interface{}{"foo", 42, true, "bar"},
112 expected: []zapcore.Field{Int("foo", 42)},
112 expected: []Field{Int("foo", 42)},
113113 errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})},
114114 },
115115 {
116116 desc: "pairs, structured fields, non-string keys, and a dangling key",
117117 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")},
119119 errLogs: []observer.LoggedEntry{
120120 ignored("dangling"),
121121 nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}),
145145
146146 // Double-check that the actual message was logged.
147147 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.")
149149
150150 // Assert that the error message's structured fields serialize properly.
151151 require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.")
174174 // Common to all test cases.
175175 context := []interface{}{"foo", "bar"}
176176 extra := []interface{}{"baz", false}
177 expectedFields := []zapcore.Field{String("foo", "bar"), Bool("baz", false)}
177 expectedFields := []Field{String("foo", "bar"), Bool("baz", false)}
178178
179179 for _, tt := range tests {
180180 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
206206
207207 // Common to all test cases.
208208 context := []interface{}{"foo", "bar"}
209 expectedFields := []zapcore.Field{String("foo", "bar")}
209 expectedFields := []Field{String("foo", "bar")}
210210
211211 for _, tt := range tests {
212212 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
242242
243243 // Common to all test cases.
244244 context := []interface{}{"foo", "bar"}
245 expectedFields := []zapcore.Field{String("foo", "bar")}
245 expectedFields := []Field{String("foo", "bar")}
246246
247247 for _, tt := range tests {
248248 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
286286 assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.")
287287 if tt.expectedMsg != "" {
288288 assert.Equal(t, []observer.LoggedEntry{{
289 Context: []zapcore.Field{},
289 Context: []Field{},
290290 Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel},
291291 }}, logs.AllUntimed(), "Unexpected log output.")
292292 } else {
319319 assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.")
320320 if tt.expectedMsg != "" {
321321 assert.Equal(t, []observer.LoggedEntry{{
322 Context: []zapcore.Field{},
322 Context: []Field{},
323323 Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel},
324324 }}, logs.AllUntimed(), "Unexpected log output.")
325325 } else {
2020 package zap
2121
2222 import (
23 "fmt"
24 "io"
2325 "io/ioutil"
24 "os"
2526
2627 "go.uber.org/zap/zapcore"
2728
2829 "go.uber.org/multierr"
2930 )
3031
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
3334 // WriteSyncer. It also returns any error encountered and a function to close
3435 // any opened files.
3536 //
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.
3850 func Open(paths ...string) (zapcore.WriteSyncer, func(), error) {
3951 writers, close, err := open(paths)
4052 if err != nil {
4658 }
4759
4860 func open(paths []string) ([]zapcore.WriteSyncer, func(), error) {
49 var openErr error
5061 writers := make([]zapcore.WriteSyncer, 0, len(paths))
51 files := make([]*os.File, 0, len(paths))
62 closers := make([]io.Closer, 0, len(paths))
5263 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()
7366 }
7467 }
7568
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 }
7679 if openErr != nil {
7780 close()
7881 return writers, nil, openErr
2020 package zap
2121
2222 import (
23 "encoding/hex"
24 "errors"
2325 "io/ioutil"
26 "math/rand"
27 "net/url"
2428 "os"
29 "path/filepath"
30 "strings"
2531 "testing"
2632
2733 "github.com/stretchr/testify/assert"
4349 }
4450
4551 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.")
4955
5056 tests := []struct {
51 paths []string
52 filenames []string
53 error string
57 paths []string
58 errs []string
5459 }{
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"}},
5967 {
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 },
6373 },
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"}},
6481 }
6582
6683 for _, tt := range tests {
67 wss, cleanup, err := open(tt.paths)
84 _, cleanup, err := Open(tt.paths...)
6885 if err == nil {
6986 defer cleanup()
7087 }
7188
72 if tt.error == "" {
89 if len(tt.errs) == 0 {
7390 assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths)
7491 } 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 }
7696 }
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.")
82114 }
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.")
85121 }
86122
87123 func TestOpenFails(t *testing.T) {
88124 tests := []struct {
89125 paths []string
90126 }{
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
97131 }
98132
99133 for _, tt := range tests {
100134 _, cleanup, err := Open(tt.paths...)
101135 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.")
103137 }
104138 }
105139
117151 return nil
118152 }
119153
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
120167 func TestCombineWriteSyncers(t *testing.T) {
121168 tw := &testWriter{"test", t}
122169 w := CombineWriteSyncers(tw)
123170 w.Write([]byte("test"))
124171 }
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 }
7070 t FieldType
7171 want interface{}
7272 }{
73 {ArrayMarshalerType, []interface{}(nil)},
73 {ArrayMarshalerType, []interface{}{}},
7474 {ObjectMarshalerType, map[string]interface{}{}},
7575 }
7676 for _, tt := range tests {
4343 }
4444
4545 func putJSONEncoder(enc *jsonEncoder) {
46 if enc.reflectBuf != nil {
47 enc.reflectBuf.Free()
48 }
4649 enc.EncoderConfig = nil
4750 enc.buf = nil
4851 enc.spaced = false
4952 enc.openNamespaces = 0
53 enc.reflectBuf = nil
54 enc.reflectEnc = nil
5055 _jsonPool.Put(enc)
5156 }
5257
5560 buf *buffer.Buffer
5661 spaced bool // include spaces after colons and commas
5762 openNamespaces int
63
64 // for encoding generic values by reflection
65 reflectBuf *buffer.Buffer
66 reflectEnc *json.Encoder
5867 }
5968
6069 // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder
123132 enc.AppendInt64(val)
124133 }
125134
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
126144 func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error {
127 marshaled, err := json.Marshal(obj)
145 enc.resetReflectBuf()
146 err := enc.reflectEnc.Encode(obj)
128147 if err != nil {
129148 return err
130149 }
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())
133153 return err
134154 }
135155
212232 }
213233
214234 func (enc *jsonEncoder) AppendReflected(val interface{}) error {
215 marshaled, err := json.Marshal(val)
235 enc.resetReflectBuf()
236 err := enc.reflectEnc.Encode(val)
216237 if err != nil {
217238 return err
218239 }
219 enc.addElementSeparator()
220 _, err = enc.buf.Write(marshaled)
240 enc.reflectBuf.TrimNewline()
241 enc.addElementSeparator()
242 _, err = enc.buf.Write(enc.reflectBuf.Bytes())
221243 return err
222244 }
223245
222222 }
223223
224224 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 })
226228 }
227229 }
228230
313315 }
314316
315317 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 })
326330 })
327331 }
328332 }
331335 assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.")
332336 }
333337
334 func assertOutput(t testing.TB, desc string, expected string, f func(Encoder)) {
338 func assertOutput(t testing.TB, expected string, f func(Encoder)) {
335339 enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &EncoderConfig{
336340 EncodeTime: EpochTimeEncoder,
337341 EncodeDuration: SecondsDurationEncoder,
338342 }}
339343 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.")
341345
342346 enc.truncate()
343347 enc.AddString("foo", "bar")
348352 // field.
349353 expectedPrefix += ","
350354 }
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.")
352356 }
353357
354358 // 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 }
4242
4343 // AddArray implements ObjectEncoder.
4444 func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error {
45 arr := &sliceArrayEncoder{}
45 arr := &sliceArrayEncoder{elems: make([]interface{}, 0)}
4646 err := v.MarshalLogArray(arr)
4747 m.cur[key] = arr.elems
4848 return err
2424 "time"
2525
2626 "github.com/stretchr/testify/assert"
27 "github.com/stretchr/testify/require"
2728 )
2829
2930 func TestMapObjectEncoderAdd(t *testing.T) {
7273 assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.")
7374 },
7475 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{}{},
7583 },
7684 {
7785 desc: "AddBinary",
203211 }
204212
205213 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 })
209219 }
210220 }
211221 func TestSliceArrayEncoderAppend(t *testing.T) {
254264 }
255265
256266 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 })
270279 }
271280 }
272281
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)
1717 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1818 // THE SOFTWARE.
1919
20 // +build !go1.9
21
2220 package zaptest
2321
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
2943 )
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
-46
zaptest/writer_go_19.go less more
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 )
2020 package zaptest
2121
2222 import (
23 "bufio"
2423 "errors"
25 "os"
26 "strings"
2724 "testing"
2825
2926 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
3127 )
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 }
6828
6929 func TestSyncer(t *testing.T) {
7030 err := errors.New("sentinel")