Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(281)

Side by Side Diff: common/errors/annotate.go

Issue 2951393002: [errors] de-specialize Transient in favor of Tags. (Closed)
Patch Set: copyright Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698