Chromium Code Reviews| 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 |