github.com/Laisky/zap@v1.27.0/zapcore/error_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package zapcore_test 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "testing" 28 29 "github.com/stretchr/testify/assert" 30 31 "go.uber.org/multierr" 32 33 //revive:disable:dot-imports 34 . "github.com/Laisky/zap/zapcore" 35 ) 36 37 type errTooManyUsers int 38 39 func (e errTooManyUsers) Error() string { 40 return fmt.Sprintf("%d too many users", int(e)) 41 } 42 43 func (e errTooManyUsers) Format(s fmt.State, verb rune) { 44 // Implement fmt.Formatter, but don't add any information beyond the basic 45 // Error method. 46 if verb == 'v' && s.Flag('+') { 47 _, _ = io.WriteString(s, e.Error()) 48 } 49 } 50 51 type errTooFewUsers int 52 53 func (e errTooFewUsers) Error() string { 54 return fmt.Sprintf("%d too few users", int(e)) 55 } 56 57 func (e errTooFewUsers) Format(s fmt.State, verb rune) { 58 if verb == 'v' && s.Flag('+') { 59 _, _ = io.WriteString(s, "verbose: ") 60 _, _ = io.WriteString(s, e.Error()) 61 } 62 } 63 64 type customMultierr struct{} 65 66 func (e customMultierr) Error() string { 67 return "great sadness" 68 } 69 70 func (e customMultierr) Errors() []error { 71 return []error{ 72 errors.New("foo"), 73 nil, 74 multierr.Append( 75 errors.New("bar"), 76 errors.New("baz"), 77 ), 78 } 79 } 80 81 func TestErrorEncoding(t *testing.T) { 82 tests := []struct { 83 key string 84 iface any 85 want map[string]any 86 }{ 87 { 88 key: "k", 89 iface: errTooManyUsers(2), 90 want: map[string]any{ 91 "k": "2 too many users", 92 }, 93 }, 94 { 95 key: "k", 96 iface: errTooFewUsers(2), 97 want: map[string]any{ 98 "k": "2 too few users", 99 "kVerbose": "verbose: 2 too few users", 100 }, 101 }, 102 { 103 key: "err", 104 iface: multierr.Combine( 105 errors.New("foo"), 106 errors.New("bar"), 107 errors.New("baz"), 108 ), 109 want: map[string]any{ 110 "err": "foo; bar; baz", 111 "errCauses": []any{ 112 map[string]any{"error": "foo"}, 113 map[string]any{"error": "bar"}, 114 map[string]any{"error": "baz"}, 115 }, 116 }, 117 }, 118 { 119 key: "e", 120 iface: customMultierr{}, 121 want: map[string]any{ 122 "e": "great sadness", 123 "eCauses": []any{ 124 map[string]any{"error": "foo"}, 125 map[string]any{ 126 "error": "bar; baz", 127 "errorCauses": []any{ 128 map[string]any{"error": "bar"}, 129 map[string]any{"error": "baz"}, 130 }, 131 }, 132 }, 133 }, 134 }, 135 { 136 key: "k", 137 iface: fmt.Errorf("failed: %w", errors.New("egad")), 138 want: map[string]any{ 139 "k": "failed: egad", 140 }, 141 }, 142 { 143 key: "error", 144 iface: multierr.Combine( 145 fmt.Errorf("hello: %w", 146 multierr.Combine(errors.New("foo"), errors.New("bar")), 147 ), 148 errors.New("baz"), 149 fmt.Errorf("world: %w", errors.New("qux")), 150 ), 151 want: map[string]any{ 152 "error": "hello: foo; bar; baz; world: qux", 153 "errorCauses": []any{ 154 map[string]any{ 155 "error": "hello: foo; bar", 156 }, 157 map[string]any{"error": "baz"}, 158 map[string]any{"error": "world: qux"}, 159 }, 160 }, 161 }, 162 } 163 164 for _, tt := range tests { 165 enc := NewMapObjectEncoder() 166 f := Field{Key: tt.key, Type: ErrorType, Interface: tt.iface} 167 f.AddTo(enc) 168 assert.Equal(t, tt.want, enc.Fields, "Unexpected output from field %+v.", f) 169 } 170 } 171 172 func TestRichErrorSupport(t *testing.T) { 173 f := Field{ 174 Type: ErrorType, 175 Interface: fmt.Errorf("failed: %w", errors.New("egad")), 176 Key: "k", 177 } 178 enc := NewMapObjectEncoder() 179 f.AddTo(enc) 180 assert.Equal(t, "failed: egad", enc.Fields["k"], "Unexpected basic error message.") 181 } 182 183 func TestErrArrayBrokenEncoder(t *testing.T) { 184 t.Parallel() 185 186 f := Field{ 187 Key: "foo", 188 Type: ErrorType, 189 Interface: multierr.Combine( 190 errors.New("foo"), 191 errors.New("bar"), 192 ), 193 } 194 195 failWith := errors.New("great sadness") 196 enc := NewMapObjectEncoder() 197 f.AddTo(brokenArrayObjectEncoder{ 198 Err: failWith, 199 ObjectEncoder: enc, 200 }) 201 202 // Failure to add the field to the encoder 203 // causes the error to be added as a string field. 204 assert.Equal(t, "great sadness", enc.Fields["fooError"], 205 "Unexpected error message.") 206 } 207 208 // brokenArrayObjectEncoder is an ObjectEncoder 209 // that builds a broken ArrayEncoder. 210 type brokenArrayObjectEncoder struct { 211 ObjectEncoder 212 ArrayEncoder 213 214 Err error // error to return 215 } 216 217 func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler ArrayMarshaler) error { 218 return enc.ObjectEncoder.AddArray(key, 219 ArrayMarshalerFunc(func(ae ArrayEncoder) error { 220 enc.ArrayEncoder = ae 221 return marshaler.MarshalLogArray(enc) 222 })) 223 } 224 225 func (enc brokenArrayObjectEncoder) AppendObject(ObjectMarshaler) error { 226 return enc.Err 227 }