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