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

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

Issue 2963503003: [errors] Greatly simplify common/errors package. (Closed)
Patch Set: fix nits Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « common/data/text/stringtemplate/template.go ('k') | common/errors/annotate_example_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package errors 5 package errors
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "errors" 9 "errors"
10 "fmt" 10 "fmt"
11 "io" 11 "io"
12 "path/filepath" 12 "path/filepath"
13 "regexp"
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « common/data/text/stringtemplate/template.go ('k') | common/errors/annotate_example_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698