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 |
dnj
2017/06/24 14:53:54
Let's make these internal members (lower-case)?
iannucci
2017/06/24 20:16:09
Done.
| |
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 28 matching lines...) Expand all Loading... | |
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", getTagDetail(key). description, val)) |
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) { |
dnj
2017/06/24 14:53:54
nit: Let's lower-case these method names too?
iannucci
2017/06/24 20:16:09
Done.
| |
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. |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
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 uniary optional argument, and must be a simple type (i.e. has |
dnj
2017/06/24 14:53:54
nit: "unary"
iannucci
2017/06/24 20:16:09
Done.
| |
328 // a reflect.Kind which is a base data type like bool, string, or int). | |
329 func (a *Annotator) Tag(tags ...TagValue) *Annotator { | |
321 if a == nil { | 330 if a == nil { |
322 return a | 331 return a |
323 } | 332 } |
324 » if !IsTransient(a.inner) { | 333 » for _, t := range tags { |
325 » » a.ctx.Transient = true | 334 » » if t == nil { |
335 » » » continue | |
336 » » } | |
337 » » if a.ctx.Tags == nil { | |
338 » » » a.ctx.Tags = make(map[tagKey]interface{}, len(tags)) | |
339 » » } | |
340 » » a.ctx.Tags[t.getKey()] = t.getValue() | |
326 } | 341 } |
327 return a | 342 return a |
328 } | 343 } |
329 | 344 |
330 // Err returns the finalized annotated error. | 345 // Err returns the finalized annotated error. |
331 func (a *Annotator) Err() error { | 346 func (a *Annotator) Err() error { |
332 if a == nil { | 347 if a == nil { |
333 return nil | 348 return nil |
334 } | 349 } |
335 return (*annotatedError)(a) | 350 return (*annotatedError)(a) |
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
531 } | 546 } |
532 return | 547 return |
533 } | 548 } |
534 | 549 |
535 // RenderStack renders the error to a RenderedError. | 550 // RenderStack renders the error to a RenderedError. |
536 func RenderStack(err error) *RenderedError { | 551 func RenderStack(err error) *RenderedError { |
537 ret := &RenderedError{} | 552 ret := &RenderedError{} |
538 | 553 |
539 lastAnnotatedFrame := 0 | 554 lastAnnotatedFrame := 0 |
540 var wrappers = []Lines{} | 555 var wrappers = []Lines{} |
541 » getCurFrame := func(fi *StackFrameInfo) *RenderedFrame { | 556 » getCurFrame := func(fi *stackFrameInfo) *RenderedFrame { |
542 if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID != fi.forStack.id { | 557 if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID != fi.forStack.id { |
543 lastAnnotatedFrame = len(fi.forStack.frames) - 1 | 558 lastAnnotatedFrame = len(fi.forStack.frames) - 1 |
544 toAdd := &RenderedStack{ | 559 toAdd := &RenderedStack{ |
545 GoID: fi.forStack.id, | 560 GoID: fi.forStack.id, |
546 Frames: make([]*RenderedFrame, len(fi.forStack.f rames)), | 561 Frames: make([]*RenderedFrame, len(fi.forStack.f rames)), |
547 } | 562 } |
548 for i, frm := range fi.forStack.frames { | 563 for i, frm := range fi.forStack.frames { |
549 pkgPath, filename, functionName, line := frameHe aderDetails(frm) | 564 pkgPath, filename, functionName, line := frameHe aderDetails(frm) |
550 toAdd.Frames[i] = &RenderedFrame{ | 565 toAdd.Frames[i] = &RenderedFrame{ |
551 Pkg: pkgPath, File: filename, LineNum: l ine, FuncName: functionName} | 566 Pkg: pkgPath, File: filename, LineNum: l ine, FuncName: functionName} |
552 } | 567 } |
553 ret.Stacks = append(ret.Stacks, toAdd) | 568 ret.Stacks = append(ret.Stacks, toAdd) |
554 } | 569 } |
555 curStack := ret.Stacks[len(ret.Stacks)-1] | 570 curStack := ret.Stacks[len(ret.Stacks)-1] |
556 | 571 |
557 if fi.frameIdx < lastAnnotatedFrame { | 572 if fi.frameIdx < lastAnnotatedFrame { |
558 lastAnnotatedFrame = fi.frameIdx | 573 lastAnnotatedFrame = fi.frameIdx |
559 frm := curStack.Frames[lastAnnotatedFrame] | 574 frm := curStack.Frames[lastAnnotatedFrame] |
560 frm.Wrappers = wrappers | 575 frm.Wrappers = wrappers |
561 wrappers = nil | 576 wrappers = nil |
562 return frm | 577 return frm |
563 } | 578 } |
564 return curStack.Frames[lastAnnotatedFrame] | 579 return curStack.Frames[lastAnnotatedFrame] |
565 } | 580 } |
566 | 581 |
567 for err != nil { | 582 for err != nil { |
568 » » if sc, ok := err.(StackContexter); ok { | 583 » » if sc, ok := err.(stackContexter); ok { |
569 » » » ctx := sc.StackContext() | 584 » » » ctx := sc.stackContext() |
570 if stk := ctx.FrameInfo.forStack; stk != nil { | 585 if stk := ctx.FrameInfo.forStack; stk != nil { |
571 frm := getCurFrame(&ctx.FrameInfo) | 586 frm := getCurFrame(&ctx.FrameInfo) |
572 if rendered := ctx.render(); len(rendered) > 0 { | 587 if rendered := ctx.render(); len(rendered) > 0 { |
573 frm.Annotations = append(frm.Annotations , rendered) | 588 frm.Annotations = append(frm.Annotations , rendered) |
574 } | 589 } |
575 } else { | 590 } else { |
576 wrappers = append(wrappers, ctx.render()) | 591 wrappers = append(wrappers, ctx.render()) |
577 } | 592 } |
578 } else { | 593 } else { |
579 wrappers = append(wrappers, Lines{fmt.Sprintf("unknown w rapper %T", err)}) | 594 wrappers = append(wrappers, Lines{fmt.Sprintf("unknown w rapper %T", err)}) |
580 } | 595 } |
581 switch x := err.(type) { | 596 switch x := err.(type) { |
582 case MultiError: | 597 case MultiError: |
583 // TODO(riannucci): it's kinda dumb that we have to walk the MultiError | 598 // TODO(riannucci): it's kinda dumb that we have to walk the MultiError |
584 » » » // twice (once in its StackContext method, and again her e). | 599 » » » // twice (once in its stackContext method, and again her e). |
585 err = x.First() | 600 err = x.First() |
586 case Wrapped: | 601 case Wrapped: |
587 err = x.InnerError() | 602 err = x.InnerError() |
588 default: | 603 default: |
589 ret.OriginalError = err.Error() | 604 ret.OriginalError = err.Error() |
590 err = nil | 605 err = nil |
591 } | 606 } |
592 } | 607 } |
593 | 608 |
594 return ret | 609 return ret |
(...skipping 11 matching lines...) Expand all Loading... | |
606 // | 621 // |
607 // Rendering the derived error with Error() will render a summary version of all | 622 // 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 | 623 // 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 | 624 // intended that the initial underlying error and all annotated Reasons only |
610 // contain user-visible information, so that the accumulated error may be | 625 // contain user-visible information, so that the accumulated error may be |
611 // returned to the user without worrying about leakage. | 626 // returned to the user without worrying about leakage. |
612 func Annotate(err error) *Annotator { | 627 func Annotate(err error) *Annotator { |
613 if err == nil { | 628 if err == nil { |
614 return nil | 629 return nil |
615 } | 630 } |
616 » return &Annotator{err, StackContext{FrameInfo: StackFrameInfoForError(1, err)}} | 631 » return &Annotator{err, stackContext{FrameInfo: stackFrameInfoForError(1, err)}} |
617 } | 632 } |
618 | 633 |
619 // Reason builds a new Annotator starting with reason. This allows you to use | 634 // 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 | 635 // all the formatting directives you would normally use with Annotate, in case |
621 // your originating error needs formatting directives: | 636 // your originating error needs formatting directives: |
622 // | 637 // |
623 // errors.Reason("something bad: %(value)d").D("value", 100)).Err() | 638 // errors.Reason("something bad: %(value)d").D("value", 100)).Err() |
624 // | 639 // |
625 // Prefer this form to errors.New(fmt.Sprintf("...")) | 640 // Prefer this form to errors.New(fmt.Sprintf("...")) |
626 func Reason(reason string) *Annotator { | 641 func Reason(reason string) *Annotator { |
627 currentStack := captureStack(1) | 642 currentStack := captureStack(1) |
628 » frameInfo := StackFrameInfo{0, currentStack} | 643 » frameInfo := stackFrameInfo{0, currentStack} |
629 » return (&Annotator{nil, StackContext{FrameInfo: frameInfo}}).Reason(reas on) | 644 » return (&Annotator{nil, stackContext{FrameInfo: frameInfo}}).Reason(reas on) |
630 } | 645 } |
631 | 646 |
632 // New is an API-compatible version of the standard errors.New function. Unlike | 647 // 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 | 648 // the stdlib errors.New, this will capture the current stack information at the |
634 // place this error was created. | 649 // place this error was created. |
635 func New(msg string) error { | 650 func New(msg string, tags ...TagValue) error { |
636 » return &terminalStackError{errors.New(msg), | 651 » tse := &terminalStackError{ |
637 » » StackFrameInfo{forStack: captureStack(1)}} | 652 » » errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil} |
653 » if len(tags) > 0 { | |
654 » » tse.tags = make(map[tagKey]interface{}, len(tags)) | |
dnj
2017/06/24 14:53:54
Handle all of the cases that "Tag" handles (e.g.,
iannucci
2017/06/24 20:16:09
Done.
| |
655 » » for _, t := range tags { | |
656 » » » tse.tags[t.getKey()] = t.getValue() | |
657 » » } | |
658 » } | |
659 » return tse | |
638 } | 660 } |
639 | 661 |
640 func captureStack(skip int) *stack { | 662 func captureStack(skip int) *stack { |
641 fullStk := stack{goroutine.CurID(), nil} | 663 fullStk := stack{goroutine.CurID(), nil} |
642 stk := make([]uintptr, 16) | 664 stk := make([]uintptr, 16) |
643 offset := skip + 2 | 665 offset := skip + 2 |
644 for n := len(stk); n == len(stk); { | 666 for n := len(stk); n == len(stk); { |
645 n = runtime.Callers(offset, stk) | 667 n = runtime.Callers(offset, stk) |
646 offset += n | 668 offset += n |
647 fullStk.frames = append(fullStk.frames, stk[:n]...) | 669 fullStk.frames = append(fullStk.frames, stk[:n]...) |
648 } | 670 } |
649 return &fullStk | 671 return &fullStk |
650 } | 672 } |
651 | 673 |
652 func getCapturedStack(err error) (ret *stack) { | 674 func getCapturedStack(err error) (ret *stack) { |
653 Walk(err, func(err error) bool { | 675 Walk(err, func(err error) bool { |
654 » » if sc, ok := err.(StackContexter); ok { | 676 » » if sc, ok := err.(stackContexter); ok { |
655 » » » ret = sc.StackContext().FrameInfo.forStack | 677 » » » ret = sc.stackContext().FrameInfo.forStack |
656 return false | 678 return false |
657 } | 679 } |
658 return true | 680 return true |
659 }) | 681 }) |
660 return | 682 return |
661 } | 683 } |
662 | 684 |
663 // StackFrameInfoForError returns a StackFrameInfo suitable for use to implement | 685 // stackFrameInfoForError returns a stackFrameInfo suitable for use to implement |
664 // the StackContexter interface. | 686 // the stackContexter interface. |
665 // | 687 // |
666 // It skips the provided number of frames when collecting the current trace | 688 // 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 | 689 // (which should be equal to the number of functions between your error library |
668 // and the user's code). | 690 // and the user's code). |
669 // | 691 // |
670 // The returned StackFrameInfo will find the appropriate frame in the error's | 692 // 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 | 693 // existing stack information (if the error was created with errors.New), or |
672 // include the current stack if it was not. | 694 // include the current stack if it was not. |
673 func StackFrameInfoForError(skip int, err error) StackFrameInfo { | 695 func stackFrameInfoForError(skip int, err error) stackFrameInfo { |
674 currentStack := captureStack(skip + 1) | 696 currentStack := captureStack(skip + 1) |
675 currentlyCapturedStack := getCapturedStack(err) | 697 currentlyCapturedStack := getCapturedStack(err) |
676 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured Stack.id { | 698 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured Stack.id { |
677 // This is the very first annotation on this error OR | 699 // This is the very first annotation on this error OR |
678 // We switched goroutines. | 700 // We switched goroutines. |
679 » » return StackFrameInfo{forStack: currentStack} | 701 » » return stackFrameInfo{forStack: currentStack} |
680 } | 702 } |
681 » return StackFrameInfo{ | 703 » return stackFrameInfo{ |
682 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt ack), | 704 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt ack), |
683 forStack: currentlyCapturedStack, | 705 forStack: currentlyCapturedStack, |
684 } | 706 } |
685 } | 707 } |
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 |