| 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" | |
| 14 "runtime" | 13 "runtime" |
| 15 "sort" | 14 "sort" |
| 16 "strings" | 15 "strings" |
| 17 | 16 |
| 18 "golang.org/x/net/context" | 17 "golang.org/x/net/context" |
| 19 | 18 |
| 20 "github.com/luci/luci-go/common/data/stringset" | 19 "github.com/luci/luci-go/common/data/stringset" |
| 21 "github.com/luci/luci-go/common/data/text/indented" | 20 "github.com/luci/luci-go/common/data/text/indented" |
| 22 "github.com/luci/luci-go/common/iotools" | 21 "github.com/luci/luci-go/common/iotools" |
| 23 "github.com/luci/luci-go/common/logging" | 22 "github.com/luci/luci-go/common/logging" |
| 24 "github.com/luci/luci-go/common/runtime/goroutine" | 23 "github.com/luci/luci-go/common/runtime/goroutine" |
| 25 ) | 24 ) |
| 26 | 25 |
| 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. | |
| 33 type Datum struct { | |
| 34 Value interface{} | |
| 35 StackFormat string | |
| 36 } | |
| 37 | |
| 38 // Data is used to add data when Annotate'ing an error. | |
| 39 type Data map[string]Datum | |
| 40 | |
| 41 type stack struct { | 26 type stack struct { |
| 42 id goroutine.ID | 27 id goroutine.ID |
| 43 frames []uintptr | 28 frames []uintptr |
| 44 } | 29 } |
| 45 | 30 |
| 46 func (s *stack) findPointOfDivergence(other *stack) int { | 31 func (s *stack) findPointOfDivergence(other *stack) int { |
| 47 // TODO(iannucci): can we optimize this search routine to not overly pen
alize | 32 // TODO(iannucci): can we optimize this search routine to not overly pen
alize |
| 48 // tail-recursive functions? Searching 'up' from both stacks doesn't wor
k in | 33 // tail-recursive functions? Searching 'up' from both stacks doesn't wor
k in |
| 49 // the face of recursion because there will be multiple ambiguous stack | 34 // the face of recursion because there will be multiple ambiguous stack |
| 50 // frames. The algorithm below is correct, but it potentially collects a
nd | 35 // frames. The algorithm below is correct, but it potentially collects a
nd |
| (...skipping 27 matching lines...) Expand all Loading... |
| 78 // with stackContexts. | 63 // with stackContexts. |
| 79 type stackFrameInfo struct { | 64 type stackFrameInfo struct { |
| 80 frameIdx int | 65 frameIdx int |
| 81 forStack *stack | 66 forStack *stack |
| 82 } | 67 } |
| 83 | 68 |
| 84 // stackContext represents the annotation data associated with an error, or an | 69 // stackContext represents the annotation data associated with an error, or an |
| 85 // annotation of an error. | 70 // annotation of an error. |
| 86 type stackContext struct { | 71 type stackContext struct { |
| 87 frameInfo stackFrameInfo | 72 frameInfo stackFrameInfo |
| 88 » // reason is the publicly-facing reason, and will show up in the Error() | 73 » // publicly-facing reason, and will show up in the Error() string. |
| 89 » // string. | |
| 90 reason string | 74 reason string |
| 91 | 75 |
| 92 » // InternalReason is used for printing tracebacks, but is otherwise form
atted | 76 » // used for printing tracebacks, but will not show up in the Error() str
ing. |
| 93 » // like reason. | |
| 94 internalReason string | 77 internalReason string |
| 95 data Data | |
| 96 | 78 |
| 79 // tags are any data associated with this frame. |
| 97 tags map[TagKey]interface{} | 80 tags map[TagKey]interface{} |
| 98 } | 81 } |
| 99 | 82 |
| 100 // We're looking for %(sometext) which is not preceded by a %. sometext may be | 83 // renderPublic renders the public error.Error()-style string for this frame, |
| 101 // any characters except for a close paren. | 84 // combining this frame's Reason with the inner error. |
| 102 // | 85 func (s *stackContext) renderPublic(inner error) string { |
| 103 // Submatch indices: | 86 » switch { |
| 104 // [0:1] Full match | 87 » case inner == nil: |
| 105 // [2:3] Text before the (...) pair (including the '%'). | 88 » » return s.reason |
| 106 // [4:5] (key) | 89 » case s.reason == "": |
| 107 var namedFormatMatcher = regexp.MustCompile(`((?:^|[^%])%)\(([^)]+)\)`) | 90 » » return inner.Error() |
| 108 | |
| 109 // Format uses the data contained in this Data map to format the provided | |
| 110 // string. Items from the map are looked up in python dict-format style, e.g. | |
| 111 // | |
| 112 // %(key)d | |
| 113 // | |
| 114 // Will look up the item "key", and then format as a decimal number it using the | |
| 115 // value for "key" in this Data map. Like python, a given item may appear | |
| 116 // multiple times in the format string. | |
| 117 // | |
| 118 // All formatting directives are identical to the ones used by fmt.Sprintf. | |
| 119 func (d Data) Format(format string) string { | |
| 120 » smi := namedFormatMatcher.FindAllStringSubmatchIndex(format, -1) | |
| 121 | |
| 122 » var ( | |
| 123 » » parts = make([]string, 0, len(smi)+1) | |
| 124 » » args = make([]interface{}, 0, len(smi)) | |
| 125 » » pos = 0 | |
| 126 » ) | |
| 127 » for _, match := range smi { | |
| 128 » » // %(key)s => %s | |
| 129 » » parts = append(parts, format[pos:match[3]]) | |
| 130 » » pos = match[1] | |
| 131 | |
| 132 » » // Add key to args. | |
| 133 » » key := format[match[4]:match[5]] | |
| 134 » » if v, ok := d[key]; ok { | |
| 135 » » » args = append(args, v.Value) | |
| 136 » » } else { | |
| 137 » » » args = append(args, fmt.Sprintf("MISSING(key=%q)", key)) | |
| 138 » » } | |
| 139 } | 91 } |
| 140 » parts = append(parts, format[pos:]) | 92 » return fmt.Sprintf("%s: %s", s.reason, inner.Error()) |
| 141 » return fmt.Sprintf(strings.Join(parts, ""), args...) | |
| 142 } | |
| 143 | |
| 144 // renderPublic renders the public error.Error()-style string for this frame, | |
| 145 // using the Reason and Data to produce a human readable string. | |
| 146 func (s *stackContext) renderPublic(inner error) string { | |
| 147 » if s.reason == "" { | |
| 148 » » if inner != nil { | |
| 149 » » » return inner.Error() | |
| 150 » » } | |
| 151 » » return "" | |
| 152 » } | |
| 153 | |
| 154 » basis := s.data.Format(s.reason) | |
| 155 » if inner != nil { | |
| 156 » » return fmt.Sprintf("%s: %s", basis, inner) | |
| 157 » } | |
| 158 » return basis | |
| 159 } | 93 } |
| 160 | 94 |
| 161 // render renders the frame as a single entry in a stack trace. This looks like: | 95 // render renders the frame as a single entry in a stack trace. This looks like: |
| 162 // | 96 // |
| 163 // I am an internal reson formatted with key1: value | |
| 164 // reason: "The literal content of the reason field: %(key2)d" | 97 // reason: "The literal content of the reason field: %(key2)d" |
| 165 // "key1" = "value" | 98 // internal reason: I am an internal reason formatted with key1: value |
| 166 // "key2" = 10 | 99 func (s *stackContext) render() lines { |
| 167 func (s *stackContext) render() Lines { | 100 » siz := len(s.tags) |
| 168 » siz := len(s.data) | |
| 169 if s.internalReason != "" { | 101 if s.internalReason != "" { |
| 170 siz++ | 102 siz++ |
| 171 } | 103 } |
| 172 if s.reason != "" { | 104 if s.reason != "" { |
| 173 siz++ | 105 siz++ |
| 174 } | 106 } |
| 175 if siz == 0 { | 107 if siz == 0 { |
| 176 return nil | 108 return nil |
| 177 } | 109 } |
| 178 | 110 |
| 179 » ret := make(Lines, 0, siz) | 111 » ret := make(lines, 0, siz) |
| 180 | 112 » if s.reason != "" { |
| 113 » » ret = append(ret, fmt.Sprintf("reason: %s", s.reason)) |
| 114 » } |
| 181 if s.internalReason != "" { | 115 if s.internalReason != "" { |
| 182 » » ret = append(ret, s.data.Format(s.internalReason)) | 116 » » ret = append(ret, fmt.Sprintf("internal reason: %s", s.internalR
eason)) |
| 183 } | 117 } |
| 184 » if s.reason != "" { | 118 » keys := make(tagKeySlice, 0, len(s.tags)) |
| 185 » » ret = append(ret, fmt.Sprintf("reason: %q", s.data.Format(s.reas
on))) | 119 » for key := range s.tags { |
| 120 » » keys = append(keys, key) |
| 186 } | 121 } |
| 187 » for key, val := range s.tags { | 122 » sort.Sort(keys) |
| 188 » » ret = append(ret, fmt.Sprintf("tag[%q]: %#v", key.description, v
al)) | 123 » for _, key := range keys { |
| 189 » } | 124 » » ret = append(ret, fmt.Sprintf("tag[%q]: %#v", key.description, s
.tags[key])) |
| 190 | |
| 191 » if len(s.data) > 0 { | |
| 192 » » for k, v := range s.data { | |
| 193 » » » if v.StackFormat == "" || v.StackFormat == "%#v" { | |
| 194 » » » » ret = append(ret, fmt.Sprintf("%q = %#v", k, v.V
alue)) | |
| 195 » » » } else { | |
| 196 » » » » ret = append(ret, fmt.Sprintf("%q = "+v.StackFor
mat, k, v.Value)) | |
| 197 » » » } | |
| 198 » » } | |
| 199 » » sort.Strings(ret[len(ret)-len(s.data):]) | |
| 200 } | 125 } |
| 201 | 126 |
| 202 return ret | 127 return ret |
| 203 } | 128 } |
| 204 | 129 |
| 205 // addData does a 'dict.update' addition of the data. | |
| 206 func (s *stackContext) addData(data Data) { | |
| 207 if s.data == nil { | |
| 208 s.data = make(Data, len(data)) | |
| 209 } | |
| 210 for k, v := range data { | |
| 211 s.data[k] = v | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 // addDatum adds a single data item to the Data in this frame | |
| 216 func (s *stackContext) addDatum(key string, value interface{}, format string) { | |
| 217 if s.data == nil { | |
| 218 s.data = Data{key: {value, format}} | |
| 219 } else { | |
| 220 s.data[key] = Datum{value, format} | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 type terminalStackError struct { | 130 type terminalStackError struct { |
| 225 error | 131 error |
| 226 finfo stackFrameInfo | 132 finfo stackFrameInfo |
| 227 tags map[TagKey]interface{} | 133 tags map[TagKey]interface{} |
| 228 } | 134 } |
| 229 | 135 |
| 230 var _ interface { | 136 var _ interface { |
| 231 error | 137 error |
| 232 stackContexter | 138 stackContexter |
| 233 } = (*terminalStackError)(nil) | 139 } = (*terminalStackError)(nil) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 253 | 159 |
| 254 // Annotator is a builder for annotating errors. Obtain one by calling Annotate | 160 // Annotator is a builder for annotating errors. Obtain one by calling Annotate |
| 255 // on an existing error or using Reason. | 161 // on an existing error or using Reason. |
| 256 // | 162 // |
| 257 // See the example test for Annotate to see how this is meant to be used. | 163 // See the example test for Annotate to see how this is meant to be used. |
| 258 type Annotator struct { | 164 type Annotator struct { |
| 259 inner error | 165 inner error |
| 260 ctx stackContext | 166 ctx stackContext |
| 261 } | 167 } |
| 262 | 168 |
| 263 // Reason adds a PUBLICLY READABLE reason string (for humans) to this error. | 169 // InternalReason adds a stack-trace-only internal reason string (for humans) to |
| 170 // this error. |
| 264 // | 171 // |
| 265 // You should assume that end-users (including unauthenticated end users) may | 172 // The text here will only be visible when using `errors.Log` or |
| 266 // see the text in here. | 173 // `errors.RenderStack`, not when calling the .Error() method of the resulting |
| 174 // error. |
| 267 // | 175 // |
| 268 // These reasons will be used to compose the result of the final Error() when | 176 // The `reason` string is formatted with `args` and may contain Sprintf-style |
| 269 // rendering this error, and will also be used to decorate the error | 177 // formatting directives. |
| 270 // annotation stack when logging the error using the Log function. | 178 func (a *Annotator) InternalReason(reason string, args ...interface{}) *Annotato
r { |
| 271 // | |
| 272 // In a webserver context, if you don't want users to see some information about | |
| 273 // this error, don't put it in the Reason. | |
| 274 // | |
| 275 // This explanation may have formatting instructions in the form of: | |
| 276 // %(key)... | |
| 277 // where key is the name of one of the entries submitted to either D or Data. | |
| 278 // The `...` may be any Printf-compatible formatting directive. | |
| 279 func (a *Annotator) Reason(reason string) *Annotator { | |
| 280 if a == nil { | 179 if a == nil { |
| 281 return a | 180 return a |
| 282 } | 181 } |
| 283 » a.ctx.reason = reason | 182 » a.ctx.internalReason = fmt.Sprintf(reason, args...) |
| 284 return a | 183 return a |
| 285 } | 184 } |
| 286 | 185 |
| 287 // InternalReason adds a stack-trace-only internal reason string (for humans) to | |
| 288 // this error. This is formatted like Reason, but will not be visible in the | |
| 289 // Error() string. | |
| 290 func (a *Annotator) InternalReason(reason string) *Annotator { | |
| 291 if a == nil { | |
| 292 return a | |
| 293 } | |
| 294 a.ctx.internalReason = reason | |
| 295 return a | |
| 296 } | |
| 297 | |
| 298 // D adds a single datum to this error. Only one format may be specified. If | |
| 299 // format is omitted or the empty string, the format "%#v" will be used. | |
| 300 func (a *Annotator) D(key string, value interface{}, format ...string) *Annotato
r { | |
| 301 if a == nil { | |
| 302 return a | |
| 303 } | |
| 304 formatVal := "" | |
| 305 switch len(format) { | |
| 306 case 0: | |
| 307 case 1: | |
| 308 formatVal = format[0] | |
| 309 default: | |
| 310 panic(fmt.Errorf("len(format) > 1: %d", len(format))) | |
| 311 } | |
| 312 a.ctx.addDatum(key, value, formatVal) | |
| 313 return a | |
| 314 } | |
| 315 | |
| 316 // Data adds data to this error. | |
| 317 func (a *Annotator) Data(data Data) *Annotator { | |
| 318 if a == nil { | |
| 319 return a | |
| 320 } | |
| 321 a.ctx.addData(data) | |
| 322 return a | |
| 323 } | |
| 324 | |
| 325 // Tag adds a tag with an optional value to this error. | 186 // Tag adds a tag with an optional value to this error. |
| 326 // | 187 // |
| 327 // `value` is a unary optional argument, and must be a simple type (i.e. has | 188 // `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). | 189 // a reflect.Kind which is a base data type like bool, string, or int). |
| 329 func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator { | 190 func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator { |
| 330 if a == nil { | 191 if a == nil { |
| 331 return a | 192 return a |
| 332 } | 193 } |
| 333 tagMap := make(map[TagKey]interface{}, len(tags)) | 194 tagMap := make(map[TagKey]interface{}, len(tags)) |
| 334 for _, t := range tags { | 195 for _, t := range tags { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 350 // Err returns the finalized annotated error. | 211 // Err returns the finalized annotated error. |
| 351 func (a *Annotator) Err() error { | 212 func (a *Annotator) Err() error { |
| 352 if a == nil { | 213 if a == nil { |
| 353 return nil | 214 return nil |
| 354 } | 215 } |
| 355 return (*annotatedError)(a) | 216 return (*annotatedError)(a) |
| 356 } | 217 } |
| 357 | 218 |
| 358 // Log logs the full error. If this is an Annotated error, it will log the full | 219 // Log logs the full error. If this is an Annotated error, it will log the full |
| 359 // stack information as well. | 220 // stack information as well. |
| 360 func Log(c context.Context, err error) { | 221 // |
| 222 // This is a shortcut for logging the output of RenderStack(err). |
| 223 func Log(c context.Context, err error, excludePkgs ...string) { |
| 361 log := logging.Get(c) | 224 log := logging.Get(c) |
| 362 » for _, l := range RenderStack(err).ToLines() { | 225 » for _, l := range RenderStack(err, excludePkgs...) { |
| 363 log.Errorf("%s", l) | 226 log.Errorf("%s", l) |
| 364 } | 227 } |
| 365 } | 228 } |
| 366 | 229 |
| 367 // Lines is just a list of printable lines. | 230 // lines is just a list of printable lines. |
| 368 // | 231 // |
| 369 // It's a type because it's most frequently used as []Lines, and [][]string | 232 // It's a type because it's most frequently used as []lines, and [][]string |
| 370 // doesn't read well. | 233 // doesn't read well. |
| 371 type Lines []string | 234 type lines []string |
| 372 | 235 |
| 373 // RenderedFrame represents a single, rendered stack frame. | 236 // renderedFrame represents a single, rendered stack frame. |
| 374 type RenderedFrame struct { | 237 type renderedFrame struct { |
| 375 » Pkg string | 238 » pkg string |
| 376 » File string | 239 » file string |
| 377 » LineNum int | 240 » lineNum int |
| 378 » FuncName string | 241 » funcName string |
| 379 | 242 |
| 380 » // Wrappers is any frame-info-less errors.Wrapped that were encountered
when | 243 » // wrappers is any frame-info-less errors.Wrapped that were encountered
when |
| 381 // rendering that didn't have any associated frame info: this is the clo
sest | 244 // rendering that didn't have any associated frame info: this is the clo
sest |
| 382 // frame to where they were added to the error. | 245 // frame to where they were added to the error. |
| 383 » Wrappers []Lines | 246 » wrappers []lines |
| 384 | 247 |
| 385 » // Annotations is any Annotate context associated directly with this Fra
me. | 248 » // annotations is any Annotate context associated directly with this Fra
me. |
| 386 » Annotations []Lines | 249 » annotations []lines |
| 387 } | 250 } |
| 388 | 251 |
| 389 var nlSlice = []byte{'\n'} | 252 var nlSlice = []byte{'\n'} |
| 390 | 253 |
| 391 // DumpWrappersTo formats the Wrappers portion of this RenderedFrame. | 254 // dumpWrappersTo formats the wrappers portion of this renderedFrame. |
| 392 func (r *RenderedFrame) DumpWrappersTo(w io.Writer, from, to int) (n int, err er
ror) { | 255 func (r *renderedFrame) dumpWrappersTo(w io.Writer, from, to int) (n int, err er
ror) { |
| 393 return iotools.WriteTracker(w, func(rawWriter io.Writer) error { | 256 return iotools.WriteTracker(w, func(rawWriter io.Writer) error { |
| 394 w := &indented.Writer{Writer: rawWriter, UseSpaces: true} | 257 w := &indented.Writer{Writer: rawWriter, UseSpaces: true} |
| 395 fmt.Fprintf(w, "From frame %d to %d, the following wrappers were
found:\n", from, to) | 258 fmt.Fprintf(w, "From frame %d to %d, the following wrappers were
found:\n", from, to) |
| 396 » » for i, wrp := range r.Wrappers { | 259 » » for i, wrp := range r.wrappers { |
| 397 if i != 0 { | 260 if i != 0 { |
| 398 w.Write(nlSlice) | 261 w.Write(nlSlice) |
| 399 } | 262 } |
| 400 w.Level = 2 | 263 w.Level = 2 |
| 401 for i, line := range wrp { | 264 for i, line := range wrp { |
| 402 if i == 0 { | 265 if i == 0 { |
| 403 fmt.Fprintf(w, "%s\n", line) | 266 fmt.Fprintf(w, "%s\n", line) |
| 404 w.Level += 2 | 267 w.Level += 2 |
| 405 } else { | 268 } else { |
| 406 fmt.Fprintf(w, "%s\n", line) | 269 fmt.Fprintf(w, "%s\n", line) |
| 407 } | 270 } |
| 408 } | 271 } |
| 409 } | 272 } |
| 410 return nil | 273 return nil |
| 411 }) | 274 }) |
| 412 } | 275 } |
| 413 | 276 |
| 414 // DumpTo formats the Header and Annotations for this RenderedFrame. | 277 // dumpTo formats the Header and annotations for this renderedFrame. |
| 415 func (r *RenderedFrame) DumpTo(w io.Writer, idx int) (n int, err error) { | 278 func (r *renderedFrame) dumpTo(w io.Writer, idx int) (n int, err error) { |
| 416 return iotools.WriteTracker(w, func(rawWriter io.Writer) error { | 279 return iotools.WriteTracker(w, func(rawWriter io.Writer) error { |
| 417 w := &indented.Writer{Writer: rawWriter, UseSpaces: true} | 280 w := &indented.Writer{Writer: rawWriter, UseSpaces: true} |
| 418 | 281 |
| 419 » » fmt.Fprintf(w, "#%d %s/%s:%d - %s()\n", idx, r.Pkg, r.File, | 282 » » fmt.Fprintf(w, "#%d %s/%s:%d - %s()\n", idx, r.pkg, r.file, |
| 420 » » » r.LineNum, r.FuncName) | 283 » » » r.lineNum, r.funcName) |
| 421 w.Level += 2 | 284 w.Level += 2 |
| 422 » » switch len(r.Annotations) { | 285 » » switch len(r.annotations) { |
| 423 case 0: | 286 case 0: |
| 424 // pass | 287 // pass |
| 425 case 1: | 288 case 1: |
| 426 » » » for _, line := range r.Annotations[0] { | 289 » » » for _, line := range r.annotations[0] { |
| 427 fmt.Fprintf(w, "%s\n", line) | 290 fmt.Fprintf(w, "%s\n", line) |
| 428 } | 291 } |
| 429 default: | 292 default: |
| 430 » » » for i, ann := range r.Annotations { | 293 » » » for i, ann := range r.annotations { |
| 431 fmt.Fprintf(w, "annotation #%d:\n", i) | 294 fmt.Fprintf(w, "annotation #%d:\n", i) |
| 432 w.Level += 2 | 295 w.Level += 2 |
| 433 for _, line := range ann { | 296 for _, line := range ann { |
| 434 fmt.Fprintf(w, "%s\n", line) | 297 fmt.Fprintf(w, "%s\n", line) |
| 435 } | 298 } |
| 436 w.Level -= 2 | 299 w.Level -= 2 |
| 437 } | 300 } |
| 438 } | 301 } |
| 439 return nil | 302 return nil |
| 440 }) | 303 }) |
| 441 } | 304 } |
| 442 | 305 |
| 443 // RenderedStack is a single rendered stack from one goroutine. | 306 // renderedStack is a single rendered stack from one goroutine. |
| 444 type RenderedStack struct { | 307 type renderedStack struct { |
| 445 » GoID goroutine.ID | 308 » goID goroutine.ID |
| 446 » Frames []*RenderedFrame | 309 » frames []*renderedFrame |
| 447 } | 310 } |
| 448 | 311 |
| 449 // DumpTo formats the full stack. | 312 // dumpTo formats the full stack. |
| 450 func (r *RenderedStack) DumpTo(w io.Writer, excludePkgs ...string) (n int, err e
rror) { | 313 func (r *renderedStack) dumpTo(w io.Writer, excludePkgs ...string) (n int, err e
rror) { |
| 451 excludeSet := stringset.NewFromSlice(excludePkgs...) | 314 excludeSet := stringset.NewFromSlice(excludePkgs...) |
| 452 | 315 |
| 453 return iotools.WriteTracker(w, func(w io.Writer) error { | 316 return iotools.WriteTracker(w, func(w io.Writer) error { |
| 454 » » fmt.Fprintf(w, "goroutine %d:\n", r.GoID) | 317 » » fmt.Fprintf(w, "goroutine %d:\n", r.goID) |
| 455 | 318 |
| 456 lastIdx := 0 | 319 lastIdx := 0 |
| 457 needNL := false | 320 needNL := false |
| 458 skipCount := 0 | 321 skipCount := 0 |
| 459 skipPkg := "" | 322 skipPkg := "" |
| 460 flushSkips := func(extra string) { | 323 flushSkips := func(extra string) { |
| 461 if skipCount != 0 { | 324 if skipCount != 0 { |
| 462 if needNL { | 325 if needNL { |
| 463 w.Write(nlSlice) | 326 w.Write(nlSlice) |
| 464 needNL = false | 327 needNL = false |
| 465 } | 328 } |
| 466 fmt.Fprintf(w, "... skipped %d frames in pkg %q.
..\n%s", skipCount, skipPkg, extra) | 329 fmt.Fprintf(w, "... skipped %d frames in pkg %q.
..\n%s", skipCount, skipPkg, extra) |
| 467 skipCount = 0 | 330 skipCount = 0 |
| 468 skipPkg = "" | 331 skipPkg = "" |
| 469 } | 332 } |
| 470 } | 333 } |
| 471 » » for i, f := range r.Frames { | 334 » » for i, f := range r.frames { |
| 472 if needNL { | 335 if needNL { |
| 473 w.Write(nlSlice) | 336 w.Write(nlSlice) |
| 474 needNL = false | 337 needNL = false |
| 475 } | 338 } |
| 476 » » » if excludeSet.Has(f.Pkg) { | 339 » » » if excludeSet.Has(f.pkg) { |
| 477 » » » » if skipPkg == f.Pkg { | 340 » » » » if skipPkg == f.pkg { |
| 478 skipCount++ | 341 skipCount++ |
| 479 } else { | 342 } else { |
| 480 flushSkips("") | 343 flushSkips("") |
| 481 skipCount++ | 344 skipCount++ |
| 482 » » » » » skipPkg = f.Pkg | 345 » » » » » skipPkg = f.pkg |
| 483 } | 346 } |
| 484 continue | 347 continue |
| 485 } | 348 } |
| 486 flushSkips("\n") | 349 flushSkips("\n") |
| 487 » » » if len(f.Wrappers) > 0 { | 350 » » » if len(f.wrappers) > 0 { |
| 488 » » » » f.DumpWrappersTo(w, lastIdx, i) | 351 » » » » f.dumpWrappersTo(w, lastIdx, i) |
| 489 w.Write(nlSlice) | 352 w.Write(nlSlice) |
| 490 } | 353 } |
| 491 » » » if len(f.Annotations) > 0 { | 354 » » » if len(f.annotations) > 0 { |
| 492 lastIdx = i | 355 lastIdx = i |
| 493 needNL = true | 356 needNL = true |
| 494 } | 357 } |
| 495 » » » f.DumpTo(w, i) | 358 » » » f.dumpTo(w, i) |
| 496 } | 359 } |
| 497 flushSkips("") | 360 flushSkips("") |
| 498 | 361 |
| 499 return nil | 362 return nil |
| 500 }) | 363 }) |
| 501 } | 364 } |
| 502 | 365 |
| 503 // RenderedError is a series of RenderedStacks, one for each goroutine that the | 366 // renderedError is a series of RenderedStacks, one for each goroutine that the |
| 504 // error was annotated on. | 367 // error was annotated on. |
| 505 type RenderedError struct { | 368 type renderedError struct { |
| 506 » OriginalError string | 369 » originalError string |
| 507 » Stacks []*RenderedStack | 370 » stacks []*renderedStack |
| 508 } | 371 } |
| 509 | 372 |
| 510 // ToLines renders a full-information stack trace as a series of lines. | 373 // toLines renders a full-information stack trace as a series of lines. |
| 511 func (r *RenderedError) ToLines(excludePkgs ...string) Lines { | 374 func (r *renderedError) toLines(excludePkgs ...string) lines { |
| 512 buf := bytes.Buffer{} | 375 buf := bytes.Buffer{} |
| 513 » r.DumpTo(&buf, excludePkgs...) | 376 » r.dumpTo(&buf, excludePkgs...) |
| 514 return strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n") | 377 return strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n") |
| 515 } | 378 } |
| 516 | 379 |
| 517 // DumpTo writes the full-information stack trace to the writer. | 380 // dumpTo writes the full-information stack trace to the writer. |
| 518 func (r *RenderedError) DumpTo(w io.Writer, excludePkgs ...string) (n int, err e
rror) { | 381 func (r *renderedError) dumpTo(w io.Writer, excludePkgs ...string) (n int, err e
rror) { |
| 519 return iotools.WriteTracker(w, func(w io.Writer) error { | 382 return iotools.WriteTracker(w, func(w io.Writer) error { |
| 520 » » if r.OriginalError != "" { | 383 » » if r.originalError != "" { |
| 521 » » » fmt.Fprintf(w, "original error: %s\n\n", r.OriginalError
) | 384 » » » fmt.Fprintf(w, "original error: %s\n\n", r.originalError
) |
| 522 } | 385 } |
| 523 | 386 |
| 524 » » for i := len(r.Stacks) - 1; i >= 0; i-- { | 387 » » for i := len(r.stacks) - 1; i >= 0; i-- { |
| 525 » » » if i != len(r.Stacks)-1 { | 388 » » » if i != len(r.stacks)-1 { |
| 526 w.Write(nlSlice) | 389 w.Write(nlSlice) |
| 527 } | 390 } |
| 528 » » » r.Stacks[i].DumpTo(w, excludePkgs...) | 391 » » » r.stacks[i].dumpTo(w, excludePkgs...) |
| 529 } | 392 } |
| 530 return nil | 393 return nil |
| 531 }) | 394 }) |
| 532 } | 395 } |
| 533 | 396 |
| 534 func frameHeaderDetails(frm uintptr) (pkg, filename, funcname string, lineno int
) { | 397 func frameHeaderDetails(frm uintptr) (pkg, filename, funcName string, lineno int
) { |
| 535 // this `frm--` is to get the correct line/function information, since t
he | 398 // this `frm--` is to get the correct line/function information, since t
he |
| 536 // Frame is actually the `return` pc. See runtime.Callers. | 399 // Frame is actually the `return` pc. See runtime.Callers. |
| 537 frm-- | 400 frm-- |
| 538 | 401 |
| 539 fn := runtime.FuncForPC(frm) | 402 fn := runtime.FuncForPC(frm) |
| 540 file, lineno := fn.FileLine(frm) | 403 file, lineno := fn.FileLine(frm) |
| 541 | 404 |
| 542 var dirpath string | 405 var dirpath string |
| 543 dirpath, filename = filepath.Split(file) | 406 dirpath, filename = filepath.Split(file) |
| 544 pkgTopLevelName := filepath.Base(dirpath) | 407 pkgTopLevelName := filepath.Base(dirpath) |
| 545 | 408 |
| 546 fnName := fn.Name() | 409 fnName := fn.Name() |
| 547 lastSlash := strings.LastIndex(fnName, "/") | 410 lastSlash := strings.LastIndex(fnName, "/") |
| 548 if lastSlash == -1 { | 411 if lastSlash == -1 { |
| 549 » » funcname = fnName | 412 » » funcName = fnName |
| 550 pkg = pkgTopLevelName | 413 pkg = pkgTopLevelName |
| 551 } else { | 414 } else { |
| 552 » » funcname = fnName[lastSlash+1:] | 415 » » funcName = fnName[lastSlash+1:] |
| 553 pkg = fmt.Sprintf("%s/%s", fnName[:lastSlash], pkgTopLevelName) | 416 pkg = fmt.Sprintf("%s/%s", fnName[:lastSlash], pkgTopLevelName) |
| 554 } | 417 } |
| 555 return | 418 return |
| 556 } | 419 } |
| 557 | 420 |
| 558 // RenderStack renders the error to a RenderedError. | 421 // RenderStack renders the error to a list of lines. |
| 559 func RenderStack(err error) *RenderedError { | 422 func RenderStack(err error, excludePkgs ...string) []string { |
| 560 » ret := &RenderedError{} | 423 » return renderStack(err).toLines(excludePkgs...) |
| 424 } |
| 425 |
| 426 func renderStack(err error) *renderedError { |
| 427 » ret := &renderedError{} |
| 561 | 428 |
| 562 lastAnnotatedFrame := 0 | 429 lastAnnotatedFrame := 0 |
| 563 » var wrappers = []Lines{} | 430 » var wrappers = []lines{} |
| 564 » getCurFrame := func(fi *stackFrameInfo) *RenderedFrame { | 431 » getCurFrame := func(fi *stackFrameInfo) *renderedFrame { |
| 565 » » if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID !=
fi.forStack.id { | 432 » » if len(ret.stacks) == 0 || ret.stacks[len(ret.stacks)-1].goID !=
fi.forStack.id { |
| 566 lastAnnotatedFrame = len(fi.forStack.frames) - 1 | 433 lastAnnotatedFrame = len(fi.forStack.frames) - 1 |
| 567 » » » toAdd := &RenderedStack{ | 434 » » » toAdd := &renderedStack{ |
| 568 » » » » GoID: fi.forStack.id, | 435 » » » » goID: fi.forStack.id, |
| 569 » » » » Frames: make([]*RenderedFrame, len(fi.forStack.f
rames)), | 436 » » » » frames: make([]*renderedFrame, len(fi.forStack.f
rames)), |
| 570 } | 437 } |
| 571 for i, frm := range fi.forStack.frames { | 438 for i, frm := range fi.forStack.frames { |
| 572 pkgPath, filename, functionName, line := frameHe
aderDetails(frm) | 439 pkgPath, filename, functionName, line := frameHe
aderDetails(frm) |
| 573 » » » » toAdd.Frames[i] = &RenderedFrame{ | 440 » » » » toAdd.frames[i] = &renderedFrame{ |
| 574 » » » » » Pkg: pkgPath, File: filename, LineNum: l
ine, FuncName: functionName} | 441 » » » » » pkg: pkgPath, file: filename, lineNum: l
ine, funcName: functionName} |
| 575 } | 442 } |
| 576 » » » ret.Stacks = append(ret.Stacks, toAdd) | 443 » » » ret.stacks = append(ret.stacks, toAdd) |
| 577 } | 444 } |
| 578 » » curStack := ret.Stacks[len(ret.Stacks)-1] | 445 » » curStack := ret.stacks[len(ret.stacks)-1] |
| 579 | 446 |
| 580 if fi.frameIdx < lastAnnotatedFrame { | 447 if fi.frameIdx < lastAnnotatedFrame { |
| 581 lastAnnotatedFrame = fi.frameIdx | 448 lastAnnotatedFrame = fi.frameIdx |
| 582 » » » frm := curStack.Frames[lastAnnotatedFrame] | 449 » » » frm := curStack.frames[lastAnnotatedFrame] |
| 583 » » » frm.Wrappers = wrappers | 450 » » » frm.wrappers = wrappers |
| 584 wrappers = nil | 451 wrappers = nil |
| 585 return frm | 452 return frm |
| 586 } | 453 } |
| 587 » » return curStack.Frames[lastAnnotatedFrame] | 454 » » return curStack.frames[lastAnnotatedFrame] |
| 588 } | 455 } |
| 589 | 456 |
| 590 for err != nil { | 457 for err != nil { |
| 591 if sc, ok := err.(stackContexter); ok { | 458 if sc, ok := err.(stackContexter); ok { |
| 592 ctx := sc.stackContext() | 459 ctx := sc.stackContext() |
| 593 if stk := ctx.frameInfo.forStack; stk != nil { | 460 if stk := ctx.frameInfo.forStack; stk != nil { |
| 594 frm := getCurFrame(&ctx.frameInfo) | 461 frm := getCurFrame(&ctx.frameInfo) |
| 595 if rendered := ctx.render(); len(rendered) > 0 { | 462 if rendered := ctx.render(); len(rendered) > 0 { |
| 596 » » » » » frm.Annotations = append(frm.Annotations
, rendered) | 463 » » » » » frm.annotations = append(frm.annotations
, rendered) |
| 597 } | 464 } |
| 598 } else { | 465 } else { |
| 599 wrappers = append(wrappers, ctx.render()) | 466 wrappers = append(wrappers, ctx.render()) |
| 600 } | 467 } |
| 601 } else { | 468 } else { |
| 602 » » » wrappers = append(wrappers, Lines{fmt.Sprintf("unknown w
rapper %T", err)}) | 469 » » » wrappers = append(wrappers, lines{fmt.Sprintf("unknown w
rapper %T", err)}) |
| 603 } | 470 } |
| 604 switch x := err.(type) { | 471 switch x := err.(type) { |
| 605 case MultiError: | 472 case MultiError: |
| 606 // TODO(riannucci): it's kinda dumb that we have to walk
the MultiError | 473 // TODO(riannucci): it's kinda dumb that we have to walk
the MultiError |
| 607 // twice (once in its stackContext method, and again her
e). | 474 // twice (once in its stackContext method, and again her
e). |
| 608 err = x.First() | 475 err = x.First() |
| 609 case Wrapped: | 476 case Wrapped: |
| 610 err = x.InnerError() | 477 err = x.InnerError() |
| 611 default: | 478 default: |
| 612 » » » ret.OriginalError = err.Error() | 479 » » » ret.originalError = err.Error() |
| 613 err = nil | 480 err = nil |
| 614 } | 481 } |
| 615 } | 482 } |
| 616 | 483 |
| 617 return ret | 484 return ret |
| 618 } | 485 } |
| 619 | 486 |
| 620 // Annotate captures the current stack frame and returns a new annotatable | 487 // Annotate captures the current stack frame and returns a new annotatable |
| 621 // error. You can add additional metadata to this error with its methods and | 488 // error, attaching the publically readable `reason` format string to the error. |
| 622 // then get the new derived error with the Err() function. | 489 // You can add additional metadata to this error with the 'InternalReason' and |
| 490 // 'Tag' methods, and then obtain a real `error` with the Err() function. |
| 623 // | 491 // |
| 624 // If this is passed nil, it will return a no-op Annotator whose .Err() function | 492 // If this is passed nil, it will return a no-op Annotator whose .Err() function |
| 625 // will also return nil. | 493 // will also return nil. |
| 626 // | 494 // |
| 627 // The original error may be recovered by using Wrapped.InnerError on the | 495 // The original error may be recovered by using Wrapped.InnerError on the |
| 628 // returned error. | 496 // returned error. |
| 629 // | 497 // |
| 630 // Rendering the derived error with Error() will render a summary version of all | 498 // Rendering the derived error with Error() will render a summary version of all |
| 631 // the Reasons as well as the initial underlying errors Error() text. It is | 499 // the public `reason`s as well as the initial underlying error's Error() text. |
| 632 // intended that the initial underlying error and all annotated Reasons only | 500 // It is intended that the initial underlying error and all annotated reasons |
| 633 // contain user-visible information, so that the accumulated error may be | 501 // only contain user-visible information, so that the accumulated error may be |
| 634 // returned to the user without worrying about leakage. | 502 // returned to the user without worrying about leakage. |
| 635 func Annotate(err error) *Annotator { | 503 // |
| 504 // You should assume that end-users (including unauthenticated end users) may |
| 505 // see the text in the `reason` field here. To only attach an internal reason, |
| 506 // leave the `reason` argument blank and don't pass any additional formatting |
| 507 // arguments. |
| 508 // |
| 509 // The `reason` string is formatted with `args` and may contain Sprintf-style |
| 510 // formatting directives. |
| 511 func Annotate(err error, reason string, args ...interface{}) *Annotator { |
| 636 if err == nil { | 512 if err == nil { |
| 637 return nil | 513 return nil |
| 638 } | 514 } |
| 639 » return &Annotator{err, stackContext{frameInfo: stackFrameInfoForError(1,
err)}} | 515 » return &Annotator{err, stackContext{ |
| 516 » » frameInfo: stackFrameInfoForError(1, err), |
| 517 » » reason: fmt.Sprintf(reason, args...), |
| 518 » }} |
| 640 } | 519 } |
| 641 | 520 |
| 642 // Reason builds a new Annotator starting with reason. This allows you to use | 521 // Reason builds a new Annotator starting with reason. This allows you to use |
| 643 // all the formatting directives you would normally use with Annotate, in case | 522 // all the formatting directives you would normally use with Annotate, in case |
| 644 // your originating error needs formatting directives: | 523 // your originating error needs tags or an internal reason. |
| 645 // | 524 // |
| 646 // errors.Reason("something bad: %(value)d").D("value", 100)).Err() | 525 // errors.Reason("something bad: %d", value).Tag(transient.Tag).Err() |
| 647 // | 526 // |
| 648 // Prefer this form to errors.New(fmt.Sprintf("...")) | 527 // Prefer this form to errors.New(fmt.Sprintf("...")) |
| 649 func Reason(reason string) *Annotator { | 528 func Reason(reason string, args ...interface{}) *Annotator { |
| 650 currentStack := captureStack(1) | 529 currentStack := captureStack(1) |
| 651 frameInfo := stackFrameInfo{0, currentStack} | 530 frameInfo := stackFrameInfo{0, currentStack} |
| 652 » return (&Annotator{nil, stackContext{frameInfo: frameInfo}}).Reason(reas
on) | 531 » return (&Annotator{nil, stackContext{ |
| 532 » » frameInfo: frameInfo, |
| 533 » » reason: fmt.Sprintf(reason, args...), |
| 534 » }}) |
| 653 } | 535 } |
| 654 | 536 |
| 655 // New is an API-compatible version of the standard errors.New function. Unlike | 537 // New is an API-compatible version of the standard errors.New function. Unlike |
| 656 // the stdlib errors.New, this will capture the current stack information at the | 538 // the stdlib errors.New, this will capture the current stack information at the |
| 657 // place this error was created. | 539 // place this error was created. |
| 658 func New(msg string, tags ...TagValueGenerator) error { | 540 func New(msg string, tags ...TagValueGenerator) error { |
| 659 tse := &terminalStackError{ | 541 tse := &terminalStackError{ |
| 660 errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil} | 542 errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil} |
| 661 if len(tags) > 0 { | 543 if len(tags) > 0 { |
| 662 tse.tags = make(map[TagKey]interface{}, len(tags)) | 544 tse.tags = make(map[TagKey]interface{}, len(tags)) |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 707 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured
Stack.id { | 589 if currentlyCapturedStack == nil || currentStack.id != currentlyCaptured
Stack.id { |
| 708 // This is the very first annotation on this error OR | 590 // This is the very first annotation on this error OR |
| 709 // We switched goroutines. | 591 // We switched goroutines. |
| 710 return stackFrameInfo{forStack: currentStack} | 592 return stackFrameInfo{forStack: currentStack} |
| 711 } | 593 } |
| 712 return stackFrameInfo{ | 594 return stackFrameInfo{ |
| 713 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt
ack), | 595 frameIdx: currentlyCapturedStack.findPointOfDivergence(currentSt
ack), |
| 714 forStack: currentlyCapturedStack, | 596 forStack: currentlyCapturedStack, |
| 715 } | 597 } |
| 716 } | 598 } |
| OLD | NEW |