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

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

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