OLD | NEW |
1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
4 | 4 |
5 package errors | 5 package errors |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "errors" | 9 "errors" |
10 "fmt" | 10 "fmt" |
11 "io" | 11 "io" |
12 "path/filepath" | 12 "path/filepath" |
13 "regexp" | 13 "regexp" |
14 "runtime" | 14 "runtime" |
15 "sort" | 15 "sort" |
16 "strings" | 16 "strings" |
17 | 17 |
18 "golang.org/x/net/context" | 18 "golang.org/x/net/context" |
19 | 19 |
20 "github.com/luci/luci-go/common/data/stringset" | 20 "github.com/luci/luci-go/common/data/stringset" |
21 "github.com/luci/luci-go/common/data/text/indented" | 21 "github.com/luci/luci-go/common/data/text/indented" |
22 "github.com/luci/luci-go/common/iotools" | 22 "github.com/luci/luci-go/common/iotools" |
23 "github.com/luci/luci-go/common/logging" | 23 "github.com/luci/luci-go/common/logging" |
24 "github.com/luci/luci-go/common/runtime/goroutine" | 24 "github.com/luci/luci-go/common/runtime/goroutine" |
25 ) | 25 ) |
26 | 26 |
27 // Datum is a single data entry value for StackContext.Data. | 27 // Datum is a single data entry value for stackContext.Data. |
| 28 // |
| 29 // It's a tuple of Value (the actual data value you care about), and |
| 30 // StackFormat, which is a fmt-style string for how this Datum should be |
| 31 // rendered when using RenderStack. If StackFormat is left empty, "%#v" will be |
| 32 // used. |
28 type Datum struct { | 33 type Datum struct { |
29 Value interface{} | 34 Value interface{} |
30 StackFormat string | 35 StackFormat string |
31 } | 36 } |
32 | 37 |
33 // Data is used to add data to a StackContext. | 38 // Data is used to add data when Annotate'ing an error. |
34 type Data map[string]Datum | 39 type Data map[string]Datum |
35 | 40 |
36 type stack struct { | 41 type stack struct { |
37 id goroutine.ID | 42 id goroutine.ID |
38 frames []uintptr | 43 frames []uintptr |
39 } | 44 } |
40 | 45 |
41 func (s *stack) findPointOfDivergence(other *stack) int { | 46 func (s *stack) findPointOfDivergence(other *stack) int { |
42 // TODO(iannucci): can we optimize this search routine to not overly pen
alize | 47 // TODO(iannucci): can we optimize this search routine to not overly pen
alize |
43 // tail-recursive functions? Searching 'up' from both stacks doesn't wor
k in | 48 // tail-recursive functions? Searching 'up' from both stacks doesn't wor
k in |
(...skipping 12 matching lines...) Expand all Loading... |
56 oIdx := len(other.frames) - 1 | 61 oIdx := len(other.frames) - 1 |
57 | 62 |
58 for s.frames[myIdx] == other.frames[oIdx] { | 63 for s.frames[myIdx] == other.frames[oIdx] { |
59 myIdx-- | 64 myIdx-- |
60 oIdx-- | 65 oIdx-- |
61 } | 66 } |
62 | 67 |
63 return myIdx | 68 return myIdx |
64 } | 69 } |
65 | 70 |
66 // StackContexter is the interface that an error may implement if it has data | 71 // stackContexter is the interface that an error may implement if it has data |
67 // associated with a specific stack frame. | 72 // associated with a specific stack frame. |
68 type StackContexter interface { | 73 type stackContexter interface { |
69 » StackContext() StackContext | 74 » stackContext() stackContext |
70 } | 75 } |
71 | 76 |
72 // StackFrameInfo holds a stack and an index into that stack for association | 77 // stackFrameInfo holds a stack and an index into that stack for association |
73 // with StackContexts. | 78 // with stackContexts. |
74 type StackFrameInfo struct { | 79 type stackFrameInfo struct { |
75 frameIdx int | 80 frameIdx int |
76 forStack *stack | 81 forStack *stack |
77 } | 82 } |
78 | 83 |
79 // StackContext represents the annotation data associated with an error, or an | 84 // stackContext represents the annotation data associated with an error, or an |
80 // annotation of an error. | 85 // annotation of an error. |
81 type StackContext struct { | 86 type stackContext struct { |
82 » FrameInfo StackFrameInfo | 87 » frameInfo stackFrameInfo |
83 » // Reason is the publicly-facing reason, and will show up in the Error() | 88 » // reason is the publicly-facing reason, and will show up in the Error() |
84 // string. | 89 // string. |
85 » Reason string | 90 » reason string |
86 | 91 |
87 // InternalReason is used for printing tracebacks, but is otherwise form
atted | 92 // InternalReason is used for printing tracebacks, but is otherwise form
atted |
88 » // like Reason. | 93 » // like reason. |
89 » InternalReason string | 94 » internalReason string |
90 » Data Data | 95 » data Data |
91 | 96 |
92 » Transient bool | 97 » tags map[TagKey]interface{} |
93 } | 98 } |
94 | 99 |
95 // We're looking for %(sometext) which is not preceded by a %. sometext may be | 100 // We're looking for %(sometext) which is not preceded by a %. sometext may be |
96 // any characters except for a close paren. | 101 // any characters except for a close paren. |
97 // | 102 // |
98 // Submatch indices: | 103 // Submatch indices: |
99 // [0:1] Full match | 104 // [0:1] Full match |
100 // [2:3] Text before the (...) pair (including the '%'). | 105 // [2:3] Text before the (...) pair (including the '%'). |
101 // [4:5] (key) | 106 // [4:5] (key) |
102 var namedFormatMatcher = regexp.MustCompile(`((?:^|[^%])%)\(([^)]+)\)`) | 107 var namedFormatMatcher = regexp.MustCompile(`((?:^|[^%])%)\(([^)]+)\)`) |
(...skipping 26 matching lines...) Expand all Loading... |
129 if v, ok := d[key]; ok { | 134 if v, ok := d[key]; ok { |
130 args = append(args, v.Value) | 135 args = append(args, v.Value) |
131 } else { | 136 } else { |
132 args = append(args, fmt.Sprintf("MISSING(key=%q)", key)) | 137 args = append(args, fmt.Sprintf("MISSING(key=%q)", key)) |
133 } | 138 } |
134 } | 139 } |
135 parts = append(parts, format[pos:]) | 140 parts = append(parts, format[pos:]) |
136 return fmt.Sprintf(strings.Join(parts, ""), args...) | 141 return fmt.Sprintf(strings.Join(parts, ""), args...) |
137 } | 142 } |
138 | 143 |
139 // RenderPublic renders the public error.Error()-style string for this frame, | 144 // renderPublic renders the public error.Error()-style string for this frame, |
140 // using the Reason and Data to produce a human readable string. | 145 // using the Reason and Data to produce a human readable string. |
141 func (s *StackContext) RenderPublic(inner error) string { | 146 func (s *stackContext) renderPublic(inner error) string { |
142 » if s.Reason == "" { | 147 » if s.reason == "" { |
143 if inner != nil { | 148 if inner != nil { |
144 return inner.Error() | 149 return inner.Error() |
145 } | 150 } |
146 return "" | 151 return "" |
147 } | 152 } |
148 | 153 |
149 » basis := s.Data.Format(s.Reason) | 154 » basis := s.data.Format(s.reason) |
150 if inner != nil { | 155 if inner != nil { |
151 return fmt.Sprintf("%s: %s", basis, inner) | 156 return fmt.Sprintf("%s: %s", basis, inner) |
152 } | 157 } |
153 return basis | 158 return basis |
154 } | 159 } |
155 | 160 |
156 // render renders the frame as a single entry in a stack trace. This looks like: | 161 // render renders the frame as a single entry in a stack trace. This looks like: |
157 // | 162 // |
158 // I am an internal reson formatted with key1: value | 163 // I am an internal reson formatted with key1: value |
159 // reason: "The literal content of the Reason field: %(key2)d" | 164 // reason: "The literal content of the reason field: %(key2)d" |
160 // "key1" = "value" | 165 // "key1" = "value" |
161 // "key2" = 10 | 166 // "key2" = 10 |
162 func (s *StackContext) render() Lines { | 167 func (s *stackContext) render() Lines { |
163 » siz := len(s.Data) | 168 » siz := len(s.data) |
164 » if s.InternalReason != "" { | 169 » if s.internalReason != "" { |
165 siz++ | 170 siz++ |
166 } | 171 } |
167 » if s.Reason != "" { | 172 » if s.reason != "" { |
168 siz++ | 173 siz++ |
169 } | 174 } |
170 if siz == 0 { | 175 if siz == 0 { |
171 return nil | 176 return nil |
172 } | 177 } |
173 | 178 |
174 ret := make(Lines, 0, siz) | 179 ret := make(Lines, 0, siz) |
175 | 180 |
176 » if s.InternalReason != "" { | 181 » if s.internalReason != "" { |
177 » » ret = append(ret, s.Data.Format(s.InternalReason)) | 182 » » ret = append(ret, s.data.Format(s.internalReason)) |
178 } | 183 } |
179 » if s.Reason != "" { | 184 » if s.reason != "" { |
180 » » ret = append(ret, fmt.Sprintf("reason: %q", s.Data.Format(s.Reas
on))) | 185 » » ret = append(ret, fmt.Sprintf("reason: %q", s.data.Format(s.reas
on))) |
181 } | 186 } |
182 » if s.Transient { | 187 » for key, val := range s.tags { |
183 » » ret = append(ret, "transient: true") | 188 » » ret = append(ret, fmt.Sprintf("tag[%q]: %#v", key.description, v
al)) |
184 } | 189 } |
185 | 190 |
186 » if len(s.Data) > 0 { | 191 » if len(s.data) > 0 { |
187 » » for k, v := range s.Data { | 192 » » for k, v := range s.data { |
188 if v.StackFormat == "" || v.StackFormat == "%#v" { | 193 if v.StackFormat == "" || v.StackFormat == "%#v" { |
189 ret = append(ret, fmt.Sprintf("%q = %#v", k, v.V
alue)) | 194 ret = append(ret, fmt.Sprintf("%q = %#v", k, v.V
alue)) |
190 } else { | 195 } else { |
191 ret = append(ret, fmt.Sprintf("%q = "+v.StackFor
mat, k, v.Value)) | 196 ret = append(ret, fmt.Sprintf("%q = "+v.StackFor
mat, k, v.Value)) |
192 } | 197 } |
193 } | 198 } |
194 » » sort.Strings(ret[len(ret)-len(s.Data):]) | 199 » » sort.Strings(ret[len(ret)-len(s.data):]) |
195 } | 200 } |
196 | 201 |
197 return ret | 202 return ret |
198 } | 203 } |
199 | 204 |
200 // AddData does a 'dict.update' addition of the data. | 205 // addData does a 'dict.update' addition of the data. |
201 func (s *StackContext) AddData(data Data) { | 206 func (s *stackContext) addData(data Data) { |
202 » if s.Data == nil { | 207 » if s.data == nil { |
203 » » s.Data = make(Data, len(data)) | 208 » » s.data = make(Data, len(data)) |
204 } | 209 } |
205 for k, v := range data { | 210 for k, v := range data { |
206 » » s.Data[k] = v | 211 » » s.data[k] = v |
207 } | 212 } |
208 } | 213 } |
209 | 214 |
210 // AddDatum adds a single data item to the Data in this frame | 215 // addDatum adds a single data item to the Data in this frame |
211 func (s *StackContext) AddDatum(key string, value interface{}, format string) { | 216 func (s *stackContext) addDatum(key string, value interface{}, format string) { |
212 » if s.Data == nil { | 217 » if s.data == nil { |
213 » » s.Data = Data{key: {value, format}} | 218 » » s.data = Data{key: {value, format}} |
214 } else { | 219 } else { |
215 » » s.Data[key] = Datum{value, format} | 220 » » s.data[key] = Datum{value, format} |
216 } | 221 } |
217 } | 222 } |
218 | 223 |
219 type terminalStackError struct { | 224 type terminalStackError struct { |
220 error | 225 error |
221 » finfo StackFrameInfo | 226 » finfo stackFrameInfo |
| 227 » tags map[TagKey]interface{} |
222 } | 228 } |
223 | 229 |
224 var _ interface { | 230 var _ interface { |
225 error | 231 error |
226 » StackContexter | 232 » stackContexter |
227 } = (*terminalStackError)(nil) | 233 } = (*terminalStackError)(nil) |
228 | 234 |
229 func (e *terminalStackError) StackContext() StackContext { return StackContext{F
rameInfo: e.finfo} } | 235 func (e *terminalStackError) stackContext() stackContext { |
| 236 » return stackContext{frameInfo: e.finfo, tags: e.tags} |
| 237 } |
230 | 238 |
231 type annotatedError struct { | 239 type annotatedError struct { |
232 inner error | 240 inner error |
233 » ctx StackContext | 241 » ctx stackContext |
234 } | 242 } |
235 | 243 |
236 var _ interface { | 244 var _ interface { |
237 error | 245 error |
238 » StackContexter | 246 » stackContexter |
239 Wrapped | 247 Wrapped |
240 } = (*annotatedError)(nil) | 248 } = (*annotatedError)(nil) |
241 | 249 |
242 func (e *annotatedError) Error() string { return e.ctx.RenderPublic
(e.inner) } | 250 func (e *annotatedError) Error() string { return e.ctx.renderPublic
(e.inner) } |
243 func (e *annotatedError) StackContext() StackContext { return e.ctx } | 251 func (e *annotatedError) stackContext() stackContext { return e.ctx } |
244 func (e *annotatedError) InnerError() error { return e.inner } | 252 func (e *annotatedError) InnerError() error { return e.inner } |
245 func (e *annotatedError) IsTransient() bool { return e.ctx.Transient } | |
246 | 253 |
247 // Annotator is a builder for annotating errors. Obtain one by calling Annotate | 254 // Annotator is a builder for annotating errors. Obtain one by calling Annotate |
248 // on an existing error or using Reason. | 255 // on an existing error or using Reason. |
249 // | 256 // |
250 // See the example test for Annotate to see how this is meant to be used. | 257 // See the example test for Annotate to see how this is meant to be used. |
251 type Annotator struct { | 258 type Annotator struct { |
252 inner error | 259 inner error |
253 » ctx StackContext | 260 » ctx stackContext |
254 } | 261 } |
255 | 262 |
256 // Reason adds a PUBLICLY READABLE reason string (for humans) to this error. | 263 // Reason adds a PUBLICLY READABLE reason string (for humans) to this error. |
257 // | 264 // |
258 // You should assume that end-users (including unauthenticated end users) may | 265 // You should assume that end-users (including unauthenticated end users) may |
259 // see the text in here. | 266 // see the text in here. |
260 // | 267 // |
261 // These reasons will be used to compose the result of the final Error() when | 268 // These reasons will be used to compose the result of the final Error() when |
262 // rendering this error, and will also be used to decorate the error | 269 // rendering this error, and will also be used to decorate the error |
263 // annotation stack when logging the error using the Log function. | 270 // annotation stack when logging the error using the Log function. |
264 // | 271 // |
265 // In a webserver context, if you don't want users to see some information about | 272 // In a webserver context, if you don't want users to see some information about |
266 // this error, don't put it in the Reason. | 273 // this error, don't put it in the Reason. |
267 // | 274 // |
268 // This explanation may have formatting instructions in the form of: | 275 // This explanation may have formatting instructions in the form of: |
269 // %(key)... | 276 // %(key)... |
270 // where key is the name of one of the entries submitted to either D or Data. | 277 // where key is the name of one of the entries submitted to either D or Data. |
271 // The `...` may be any Printf-compatible formatting directive. | 278 // The `...` may be any Printf-compatible formatting directive. |
272 func (a *Annotator) Reason(reason string) *Annotator { | 279 func (a *Annotator) Reason(reason string) *Annotator { |
273 if a == nil { | 280 if a == nil { |
274 return a | 281 return a |
275 } | 282 } |
276 » a.ctx.Reason = reason | 283 » a.ctx.reason = reason |
277 return a | 284 return a |
278 } | 285 } |
279 | 286 |
280 // InternalReason adds a stack-trace-only internal reason string (for humans) to | 287 // InternalReason adds a stack-trace-only internal reason string (for humans) to |
281 // this error. This is formatted like Reason, but will not be visible in the | 288 // this error. This is formatted like Reason, but will not be visible in the |
282 // Error() string. | 289 // Error() string. |
283 func (a *Annotator) InternalReason(reason string) *Annotator { | 290 func (a *Annotator) InternalReason(reason string) *Annotator { |
284 if a == nil { | 291 if a == nil { |
285 return a | 292 return a |
286 } | 293 } |
287 » a.ctx.InternalReason = reason | 294 » a.ctx.internalReason = reason |
288 return a | 295 return a |
289 } | 296 } |
290 | 297 |
291 // D adds a single datum to this error. Only one format may be specified. If | 298 // D adds a single datum to this error. Only one format may be specified. If |
292 // format is omitted or the empty string, the format "%#v" will be used. | 299 // format is omitted or the empty string, the format "%#v" will be used. |
293 func (a *Annotator) D(key string, value interface{}, format ...string) *Annotato
r { | 300 func (a *Annotator) D(key string, value interface{}, format ...string) *Annotato
r { |
294 if a == nil { | 301 if a == nil { |
295 return a | 302 return a |
296 } | 303 } |
297 formatVal := "" | 304 formatVal := "" |
298 switch len(format) { | 305 switch len(format) { |
299 case 0: | 306 case 0: |
300 case 1: | 307 case 1: |
301 formatVal = format[0] | 308 formatVal = format[0] |
302 default: | 309 default: |
303 panic(fmt.Errorf("len(format) > 1: %d", len(format))) | 310 panic(fmt.Errorf("len(format) > 1: %d", len(format))) |
304 } | 311 } |
305 » a.ctx.AddDatum(key, value, formatVal) | 312 » a.ctx.addDatum(key, value, formatVal) |
306 return a | 313 return a |
307 } | 314 } |
308 | 315 |
309 // Data adds data to this error. | 316 // Data adds data to this error. |
310 func (a *Annotator) Data(data Data) *Annotator { | 317 func (a *Annotator) Data(data Data) *Annotator { |
311 if a == nil { | 318 if a == nil { |
312 return a | 319 return a |
313 } | 320 } |
314 » a.ctx.AddData(data) | 321 » a.ctx.addData(data) |
315 return a | 322 return a |
316 } | 323 } |
317 | 324 |
318 // Transient marks this error as transient. If the inner error is already | 325 // Tag adds a tag with an optional value to this error. |
319 // transient, this has no effect. | 326 // |
320 func (a *Annotator) Transient() *Annotator { | 327 // `value` is a unary optional argument, and must be a simple type (i.e. has |
| 328 // a reflect.Kind which is a base data type like bool, string, or int). |
| 329 func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator { |
321 if a == nil { | 330 if a == nil { |
322 return a | 331 return a |
323 } | 332 } |
324 » if !IsTransient(a.inner) { | 333 » tagMap := make(map[TagKey]interface{}, len(tags)) |
325 » » a.ctx.Transient = true | 334 » for _, t := range tags { |
| 335 » » v := t.GenerateErrorTagValue() |
| 336 » » tagMap[v.Key] = v.Value |
| 337 » } |
| 338 » if len(tagMap) > 0 { |
| 339 » » if a.ctx.tags == nil { |
| 340 » » » a.ctx.tags = tagMap |
| 341 » » } else { |
| 342 » » » for k, v := range tagMap { |
| 343 » » » » a.ctx.tags[k] = v |
| 344 » » » } |
| 345 » » } |
326 } | 346 } |
327 return a | 347 return a |
328 } | 348 } |
329 | 349 |
330 // Err returns the finalized annotated error. | 350 // Err returns the finalized annotated error. |
331 func (a *Annotator) Err() error { | 351 func (a *Annotator) Err() error { |
332 if a == nil { | 352 if a == nil { |
333 return nil | 353 return nil |
334 } | 354 } |
335 return (*annotatedError)(a) | 355 return (*annotatedError)(a) |
336 } | 356 } |
337 | 357 |
338 // Log logs the full error. If this is an Annotated error, it will log the full | 358 // Log logs the full error. If this is an Annotated error, it will log the full |
339 // stack information as well. | 359 // stack information as well. |
340 func Log(c context.Context, err error) { | 360 func Log(c context.Context, err error) { |
341 log := logging.Get(c) | 361 log := logging.Get(c) |
342 for _, l := range RenderStack(err).ToLines() { | 362 for _, l := range RenderStack(err).ToLines() { |
343 log.Errorf("%s", l) | 363 log.Errorf("%s", l) |
344 } | 364 } |
345 } | 365 } |
346 | 366 |
347 // Lines is just a list of printable lines. | 367 // Lines is just a list of printable lines. |
| 368 // |
| 369 // It's a type because it's most frequently used as []Lines, and [][]string |
| 370 // doesn't read well. |
348 type Lines []string | 371 type Lines []string |
349 | 372 |
350 // RenderedFrame represents a single, rendered stack frame. | 373 // RenderedFrame represents a single, rendered stack frame. |
351 type RenderedFrame struct { | 374 type RenderedFrame struct { |
352 Pkg string | 375 Pkg string |
353 File string | 376 File string |
354 LineNum int | 377 LineNum int |
355 FuncName string | 378 FuncName string |
356 | 379 |
357 // Wrappers is any frame-info-less errors.Wrapped that were encountered
when | 380 // Wrappers is any frame-info-less errors.Wrapped that were encountered
when |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
531 } | 554 } |
532 return | 555 return |
533 } | 556 } |
534 | 557 |
535 // RenderStack renders the error to a RenderedError. | 558 // RenderStack renders the error to a RenderedError. |
536 func RenderStack(err error) *RenderedError { | 559 func RenderStack(err error) *RenderedError { |
537 ret := &RenderedError{} | 560 ret := &RenderedError{} |
538 | 561 |
539 lastAnnotatedFrame := 0 | 562 lastAnnotatedFrame := 0 |
540 var wrappers = []Lines{} | 563 var wrappers = []Lines{} |
541 » getCurFrame := func(fi *StackFrameInfo) *RenderedFrame { | 564 » getCurFrame := func(fi *stackFrameInfo) *RenderedFrame { |
542 if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID !=
fi.forStack.id { | 565 if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID !=
fi.forStack.id { |
543 lastAnnotatedFrame = len(fi.forStack.frames) - 1 | 566 lastAnnotatedFrame = len(fi.forStack.frames) - 1 |
544 toAdd := &RenderedStack{ | 567 toAdd := &RenderedStack{ |
545 GoID: fi.forStack.id, | 568 GoID: fi.forStack.id, |
546 Frames: make([]*RenderedFrame, len(fi.forStack.f
rames)), | 569 Frames: make([]*RenderedFrame, len(fi.forStack.f
rames)), |
547 } | 570 } |
548 for i, frm := range fi.forStack.frames { | 571 for i, frm := range fi.forStack.frames { |
549 pkgPath, filename, functionName, line := frameHe
aderDetails(frm) | 572 pkgPath, filename, functionName, line := frameHe
aderDetails(frm) |
550 toAdd.Frames[i] = &RenderedFrame{ | 573 toAdd.Frames[i] = &RenderedFrame{ |
551 Pkg: pkgPath, File: filename, LineNum: l
ine, FuncName: functionName} | 574 Pkg: pkgPath, File: filename, LineNum: l
ine, FuncName: functionName} |
552 } | 575 } |
553 ret.Stacks = append(ret.Stacks, toAdd) | 576 ret.Stacks = append(ret.Stacks, toAdd) |
554 } | 577 } |
555 curStack := ret.Stacks[len(ret.Stacks)-1] | 578 curStack := ret.Stacks[len(ret.Stacks)-1] |
556 | 579 |
557 if fi.frameIdx < lastAnnotatedFrame { | 580 if fi.frameIdx < lastAnnotatedFrame { |
558 lastAnnotatedFrame = fi.frameIdx | 581 lastAnnotatedFrame = fi.frameIdx |
559 frm := curStack.Frames[lastAnnotatedFrame] | 582 frm := curStack.Frames[lastAnnotatedFrame] |
560 frm.Wrappers = wrappers | 583 frm.Wrappers = wrappers |
561 wrappers = nil | 584 wrappers = nil |
562 return frm | 585 return frm |
563 } | 586 } |
564 return curStack.Frames[lastAnnotatedFrame] | 587 return curStack.Frames[lastAnnotatedFrame] |
565 } | 588 } |
566 | 589 |
567 for err != nil { | 590 for err != nil { |
568 » » if sc, ok := err.(StackContexter); ok { | 591 » » if sc, ok := err.(stackContexter); ok { |
569 » » » ctx := sc.StackContext() | 592 » » » ctx := sc.stackContext() |
570 » » » if stk := ctx.FrameInfo.forStack; stk != nil { | 593 » » » if stk := ctx.frameInfo.forStack; stk != nil { |
571 » » » » frm := getCurFrame(&ctx.FrameInfo) | 594 » » » » frm := getCurFrame(&ctx.frameInfo) |
572 if rendered := ctx.render(); len(rendered) > 0 { | 595 if rendered := ctx.render(); len(rendered) > 0 { |
573 frm.Annotations = append(frm.Annotations
, rendered) | 596 frm.Annotations = append(frm.Annotations
, rendered) |
574 } | 597 } |
575 } else { | 598 } else { |
576 wrappers = append(wrappers, ctx.render()) | 599 wrappers = append(wrappers, ctx.render()) |
577 } | 600 } |
578 } else { | 601 } else { |
579 wrappers = append(wrappers, Lines{fmt.Sprintf("unknown w
rapper %T", err)}) | 602 wrappers = append(wrappers, Lines{fmt.Sprintf("unknown w
rapper %T", err)}) |
580 } | 603 } |
581 switch x := err.(type) { | 604 switch x := err.(type) { |
582 case MultiError: | 605 case MultiError: |
583 // TODO(riannucci): it's kinda dumb that we have to walk
the MultiError | 606 // TODO(riannucci): it's kinda dumb that we have to walk
the MultiError |
584 » » » // twice (once in its StackContext method, and again her
e). | 607 » » » // twice (once in its stackContext method, and again her
e). |
585 err = x.First() | 608 err = x.First() |
586 case Wrapped: | 609 case Wrapped: |
587 err = x.InnerError() | 610 err = x.InnerError() |
588 default: | 611 default: |
589 ret.OriginalError = err.Error() | 612 ret.OriginalError = err.Error() |
590 err = nil | 613 err = nil |
591 } | 614 } |
592 } | 615 } |
593 | 616 |
594 return ret | 617 return ret |
(...skipping 11 matching lines...) Expand all Loading... |
606 // | 629 // |
607 // Rendering the derived error with Error() will render a summary version of all | 630 // Rendering the derived error with Error() will render a summary version of all |
608 // the Reasons as well as the initial underlying errors Error() text. It is | 631 // the Reasons as well as the initial underlying errors Error() text. It is |
609 // intended that the initial underlying error and all annotated Reasons only | 632 // intended that the initial underlying error and all annotated Reasons only |
610 // contain user-visible information, so that the accumulated error may be | 633 // contain user-visible information, so that the accumulated error may be |
611 // returned to the user without worrying about leakage. | 634 // returned to the user without worrying about leakage. |
612 func Annotate(err error) *Annotator { | 635 func Annotate(err error) *Annotator { |
613 if err == nil { | 636 if err == nil { |
614 return nil | 637 return nil |
615 } | 638 } |
616 » return &Annotator{err, StackContext{FrameInfo: StackFrameInfoForError(1,
err)}} | 639 » return &Annotator{err, stackContext{frameInfo: stackFrameInfoForError(1,
err)}} |
617 } | 640 } |
618 | 641 |
619 // Reason builds a new Annotator starting with reason. This allows you to use | 642 // Reason builds a new Annotator starting with reason. This allows you to use |
620 // all the formatting directives you would normally use with Annotate, in case | 643 // all the formatting directives you would normally use with Annotate, in case |
621 // your originating error needs formatting directives: | 644 // your originating error needs formatting directives: |
622 // | 645 // |
623 // errors.Reason("something bad: %(value)d").D("value", 100)).Err() | 646 // errors.Reason("something bad: %(value)d").D("value", 100)).Err() |
624 // | 647 // |
625 // Prefer this form to errors.New(fmt.Sprintf("...")) | 648 // Prefer this form to errors.New(fmt.Sprintf("...")) |
626 func Reason(reason string) *Annotator { | 649 func Reason(reason string) *Annotator { |
627 currentStack := captureStack(1) | 650 currentStack := captureStack(1) |
628 » frameInfo := StackFrameInfo{0, currentStack} | 651 » frameInfo := stackFrameInfo{0, currentStack} |
629 » return (&Annotator{nil, StackContext{FrameInfo: frameInfo}}).Reason(reas
on) | 652 » return (&Annotator{nil, stackContext{frameInfo: frameInfo}}).Reason(reas
on) |
630 } | 653 } |
631 | 654 |
632 // New is an API-compatible version of the standard errors.New function. Unlike | 655 // New is an API-compatible version of the standard errors.New function. Unlike |
633 // the stdlib errors.New, this will capture the current stack information at the | 656 // the stdlib errors.New, this will capture the current stack information at the |
634 // place this error was created. | 657 // place this error was created. |
635 func New(msg string) error { | 658 func New(msg string, tags ...TagValueGenerator) error { |
636 » return &terminalStackError{errors.New(msg), | 659 » tse := &terminalStackError{ |
637 » » StackFrameInfo{forStack: captureStack(1)}} | 660 » » errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil} |
| 661 » if len(tags) > 0 { |
| 662 » » tse.tags = make(map[TagKey]interface{}, len(tags)) |
| 663 » » for _, t := range tags { |
| 664 » » » v := t.GenerateErrorTagValue() |
| 665 » » » tse.tags[v.Key] = v.Value |
| 666 » » } |
| 667 » } |
| 668 » return tse |
638 } | 669 } |
639 | 670 |
640 func captureStack(skip int) *stack { | 671 func captureStack(skip int) *stack { |
641 fullStk := stack{goroutine.CurID(), nil} | 672 fullStk := stack{goroutine.CurID(), nil} |
642 stk := make([]uintptr, 16) | 673 stk := make([]uintptr, 16) |
643 offset := skip + 2 | 674 offset := skip + 2 |
644 for n := len(stk); n == len(stk); { | 675 for n := len(stk); n == len(stk); { |
645 n = runtime.Callers(offset, stk) | 676 n = runtime.Callers(offset, stk) |
646 offset += n | 677 offset += n |
647 fullStk.frames = append(fullStk.frames, stk[:n]...) | 678 fullStk.frames = append(fullStk.frames, stk[:n]...) |
648 } | 679 } |
649 return &fullStk | 680 return &fullStk |
650 } | 681 } |
651 | 682 |
652 func getCapturedStack(err error) (ret *stack) { | 683 func getCapturedStack(err error) (ret *stack) { |
653 Walk(err, func(err error) bool { | 684 Walk(err, func(err error) bool { |
654 » » if sc, ok := err.(StackContexter); ok { | 685 » » if sc, ok := err.(stackContexter); ok { |
655 » » » ret = sc.StackContext().FrameInfo.forStack | 686 » » » ret = sc.stackContext().frameInfo.forStack |
656 return false | 687 return false |
657 } | 688 } |
658 return true | 689 return true |
659 }) | 690 }) |
660 return | 691 return |
661 } | 692 } |
662 | 693 |
663 // StackFrameInfoForError returns a StackFrameInfo suitable for use to implement | 694 // stackFrameInfoForError returns a stackFrameInfo suitable for use to implement |
664 // the StackContexter interface. | 695 // the stackContexter interface. |
665 // | 696 // |
666 // It skips the provided number of frames when collecting the current trace | 697 // It skips the provided number of frames when collecting the current trace |
667 // (which should be equal to the number of functions between your error library | 698 // (which should be equal to the number of functions between your error library |
668 // and the user's code). | 699 // and the user's code). |
669 // | 700 // |
670 // The returned StackFrameInfo will find the appropriate frame in the error's | 701 // The returned stackFrameInfo will find the appropriate frame in the error's |
671 // existing stack information (if the error was created with errors.New), or | 702 // existing stack information (if the error was created with errors.New), or |
672 // include the current stack if it was not. | 703 // include the current stack if it was not. |
673 func StackFrameInfoForError(skip int, err error) StackFrameInfo { | 704 func stackFrameInfoForError(skip int, err error) stackFrameInfo { |
674 currentStack := captureStack(skip + 1) | 705 currentStack := captureStack(skip + 1) |
675 currentlyCapturedStack := getCapturedStack(err) | 706 currentlyCapturedStack := getCapturedStack(err) |
676 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured
Stack.id { | 707 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured
Stack.id { |
677 // This is the very first annotation on this error OR | 708 // This is the very first annotation on this error OR |
678 // We switched goroutines. | 709 // We switched goroutines. |
679 » » return StackFrameInfo{forStack: currentStack} | 710 » » return stackFrameInfo{forStack: currentStack} |
680 } | 711 } |
681 » return StackFrameInfo{ | 712 » return stackFrameInfo{ |
682 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt
ack), | 713 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt
ack), |
683 forStack: currentlyCapturedStack, | 714 forStack: currentlyCapturedStack, |
684 } | 715 } |
685 } | 716 } |
686 | |
687 // ExtractData walks the error and extracts the given key's data from | |
688 // Annotations (e.g. data added with D(key, <value>) will return <value>). | |
689 // | |
690 // The first matching key encountered (e.g. highest up the callstack) will be | |
691 // returned. | |
692 // | |
693 // If the error does not contain key at all, this returns nil. | |
694 func ExtractData(err error, key string) (ret interface{}) { | |
695 Walk(err, func(err error) bool { | |
696 if sc, ok := err.(StackContexter); ok { | |
697 if d, ok := sc.StackContext().Data[key]; ok { | |
698 ret = d.Value | |
699 return false | |
700 } | |
701 } | |
702 return true | |
703 }) | |
704 return | |
705 } | |
OLD | NEW |