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

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

Issue 2951393002: [errors] de-specialize Transient in favor of Tags. (Closed)
Patch Set: more refactor 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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698