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

Unified Diff: common/errors/annotate.go

Issue 2951393002: [errors] de-specialize Transient in favor of Tags. (Closed)
Patch Set: copyright Created 3 years, 6 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 side-by-side diff with in-line comments
Download patch
Index: common/errors/annotate.go
diff --git a/common/errors/annotate.go b/common/errors/annotate.go
index 6d7707c87b2391ed109c5bd64740f3e21d212daf..604169df35eaf43c51a6971f2e863ab602648eda 100644
--- a/common/errors/annotate.go
+++ b/common/errors/annotate.go
@@ -24,13 +24,18 @@ import (
"github.com/luci/luci-go/common/runtime/goroutine"
)
-// Datum is a single data entry value for StackContext.Data.
+// Datum is a single data entry value for stackContext.Data.
+//
+// It's a tuple of Value (the actual data value you care about), and
+// StackFormat, which is a fmt-style string for how this Datum should be
+// rendered when using RenderStack. If StackFormat is left empty, "%#v" will be
+// used.
type Datum struct {
Value interface{}
StackFormat string
}
-// Data is used to add data to a StackContext.
+// Data is used to add data when Annotate'ing an error.
type Data map[string]Datum
type stack struct {
@@ -63,23 +68,23 @@ func (s *stack) findPointOfDivergence(other *stack) int {
return myIdx
}
-// StackContexter is the interface that an error may implement if it has data
+// stackContexter is the interface that an error may implement if it has data
// associated with a specific stack frame.
-type StackContexter interface {
- StackContext() StackContext
+type stackContexter interface {
+ stackContext() stackContext
}
-// StackFrameInfo holds a stack and an index into that stack for association
-// with StackContexts.
-type StackFrameInfo struct {
+// stackFrameInfo holds a stack and an index into that stack for association
+// with stackContexts.
+type stackFrameInfo struct {
frameIdx int
forStack *stack
}
-// StackContext represents the annotation data associated with an error, or an
+// stackContext represents the annotation data associated with an error, or an
// annotation of an error.
-type StackContext struct {
- FrameInfo StackFrameInfo
+type stackContext struct {
+ FrameInfo stackFrameInfo
dnj 2017/06/24 14:53:54 Let's make these internal members (lower-case)?
iannucci 2017/06/24 20:16:09 Done.
// Reason is the publicly-facing reason, and will show up in the Error()
// string.
Reason string
@@ -89,7 +94,7 @@ type StackContext struct {
InternalReason string
Data Data
- Transient bool
+ Tags map[tagKey]interface{}
}
// We're looking for %(sometext) which is not preceded by a %. sometext may be
@@ -138,7 +143,7 @@ func (d Data) Format(format string) string {
// RenderPublic renders the public error.Error()-style string for this frame,
// using the Reason and Data to produce a human readable string.
-func (s *StackContext) RenderPublic(inner error) string {
+func (s *stackContext) RenderPublic(inner error) string {
if s.Reason == "" {
if inner != nil {
return inner.Error()
@@ -159,7 +164,7 @@ func (s *StackContext) RenderPublic(inner error) string {
// reason: "The literal content of the Reason field: %(key2)d"
// "key1" = "value"
// "key2" = 10
-func (s *StackContext) render() Lines {
+func (s *stackContext) render() Lines {
siz := len(s.Data)
if s.InternalReason != "" {
siz++
@@ -179,8 +184,8 @@ func (s *StackContext) render() Lines {
if s.Reason != "" {
ret = append(ret, fmt.Sprintf("reason: %q", s.Data.Format(s.Reason)))
}
- if s.Transient {
- ret = append(ret, "transient: true")
+ for key, val := range s.Tags {
+ ret = append(ret, fmt.Sprintf("tag[%q]: %#v", getTagDetail(key).description, val))
}
if len(s.Data) > 0 {
@@ -198,7 +203,7 @@ func (s *StackContext) render() Lines {
}
// AddData does a 'dict.update' addition of the data.
-func (s *StackContext) AddData(data Data) {
+func (s *stackContext) AddData(data Data) {
dnj 2017/06/24 14:53:54 nit: Let's lower-case these method names too?
iannucci 2017/06/24 20:16:09 Done.
if s.Data == nil {
s.Data = make(Data, len(data))
}
@@ -208,7 +213,7 @@ func (s *StackContext) AddData(data Data) {
}
// AddDatum adds a single data item to the Data in this frame
-func (s *StackContext) AddDatum(key string, value interface{}, format string) {
+func (s *stackContext) AddDatum(key string, value interface{}, format string) {
if s.Data == nil {
s.Data = Data{key: {value, format}}
} else {
@@ -218,31 +223,33 @@ func (s *StackContext) AddDatum(key string, value interface{}, format string) {
type terminalStackError struct {
error
- finfo StackFrameInfo
+ finfo stackFrameInfo
+ tags map[tagKey]interface{}
}
var _ interface {
error
- StackContexter
+ stackContexter
} = (*terminalStackError)(nil)
-func (e *terminalStackError) StackContext() StackContext { return StackContext{FrameInfo: e.finfo} }
+func (e *terminalStackError) stackContext() stackContext {
+ return stackContext{FrameInfo: e.finfo, Tags: e.tags}
+}
type annotatedError struct {
inner error
- ctx StackContext
+ ctx stackContext
}
var _ interface {
error
- StackContexter
+ stackContexter
Wrapped
} = (*annotatedError)(nil)
func (e *annotatedError) Error() string { return e.ctx.RenderPublic(e.inner) }
-func (e *annotatedError) StackContext() StackContext { return e.ctx }
+func (e *annotatedError) stackContext() stackContext { return e.ctx }
func (e *annotatedError) InnerError() error { return e.inner }
-func (e *annotatedError) IsTransient() bool { return e.ctx.Transient }
// Annotator is a builder for annotating errors. Obtain one by calling Annotate
// on an existing error or using Reason.
@@ -250,7 +257,7 @@ func (e *annotatedError) IsTransient() bool { return e.ctx.Transient }
// See the example test for Annotate to see how this is meant to be used.
type Annotator struct {
inner error
- ctx StackContext
+ ctx stackContext
}
// Reason adds a PUBLICLY READABLE reason string (for humans) to this error.
@@ -315,14 +322,22 @@ func (a *Annotator) Data(data Data) *Annotator {
return a
}
-// Transient marks this error as transient. If the inner error is already
-// transient, this has no effect.
-func (a *Annotator) Transient() *Annotator {
+// Tag adds a tag with an optional value to this error.
+//
+// `value` is a uniary optional argument, and must be a simple type (i.e. has
dnj 2017/06/24 14:53:54 nit: "unary"
iannucci 2017/06/24 20:16:09 Done.
+// a reflect.Kind which is a base data type like bool, string, or int).
+func (a *Annotator) Tag(tags ...TagValue) *Annotator {
if a == nil {
return a
}
- if !IsTransient(a.inner) {
- a.ctx.Transient = true
+ for _, t := range tags {
+ if t == nil {
+ continue
+ }
+ if a.ctx.Tags == nil {
+ a.ctx.Tags = make(map[tagKey]interface{}, len(tags))
+ }
+ a.ctx.Tags[t.getKey()] = t.getValue()
}
return a
}
@@ -538,7 +553,7 @@ func RenderStack(err error) *RenderedError {
lastAnnotatedFrame := 0
var wrappers = []Lines{}
- getCurFrame := func(fi *StackFrameInfo) *RenderedFrame {
+ getCurFrame := func(fi *stackFrameInfo) *RenderedFrame {
if len(ret.Stacks) == 0 || ret.Stacks[len(ret.Stacks)-1].GoID != fi.forStack.id {
lastAnnotatedFrame = len(fi.forStack.frames) - 1
toAdd := &RenderedStack{
@@ -565,8 +580,8 @@ func RenderStack(err error) *RenderedError {
}
for err != nil {
- if sc, ok := err.(StackContexter); ok {
- ctx := sc.StackContext()
+ if sc, ok := err.(stackContexter); ok {
+ ctx := sc.stackContext()
if stk := ctx.FrameInfo.forStack; stk != nil {
frm := getCurFrame(&ctx.FrameInfo)
if rendered := ctx.render(); len(rendered) > 0 {
@@ -581,7 +596,7 @@ func RenderStack(err error) *RenderedError {
switch x := err.(type) {
case MultiError:
// TODO(riannucci): it's kinda dumb that we have to walk the MultiError
- // twice (once in its StackContext method, and again here).
+ // twice (once in its stackContext method, and again here).
err = x.First()
case Wrapped:
err = x.InnerError()
@@ -613,7 +628,7 @@ func Annotate(err error) *Annotator {
if err == nil {
return nil
}
- return &Annotator{err, StackContext{FrameInfo: StackFrameInfoForError(1, err)}}
+ return &Annotator{err, stackContext{FrameInfo: stackFrameInfoForError(1, err)}}
}
// Reason builds a new Annotator starting with reason. This allows you to use
@@ -625,16 +640,23 @@ func Annotate(err error) *Annotator {
// Prefer this form to errors.New(fmt.Sprintf("..."))
func Reason(reason string) *Annotator {
currentStack := captureStack(1)
- frameInfo := StackFrameInfo{0, currentStack}
- return (&Annotator{nil, StackContext{FrameInfo: frameInfo}}).Reason(reason)
+ frameInfo := stackFrameInfo{0, currentStack}
+ return (&Annotator{nil, stackContext{FrameInfo: frameInfo}}).Reason(reason)
}
// New is an API-compatible version of the standard errors.New function. Unlike
// the stdlib errors.New, this will capture the current stack information at the
// place this error was created.
-func New(msg string) error {
- return &terminalStackError{errors.New(msg),
- StackFrameInfo{forStack: captureStack(1)}}
+func New(msg string, tags ...TagValue) error {
+ tse := &terminalStackError{
+ errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil}
+ if len(tags) > 0 {
+ tse.tags = make(map[tagKey]interface{}, len(tags))
dnj 2017/06/24 14:53:54 Handle all of the cases that "Tag" handles (e.g.,
iannucci 2017/06/24 20:16:09 Done.
+ for _, t := range tags {
+ tse.tags[t.getKey()] = t.getValue()
+ }
+ }
+ return tse
}
func captureStack(skip int) *stack {
@@ -651,8 +673,8 @@ func captureStack(skip int) *stack {
func getCapturedStack(err error) (ret *stack) {
Walk(err, func(err error) bool {
- if sc, ok := err.(StackContexter); ok {
- ret = sc.StackContext().FrameInfo.forStack
+ if sc, ok := err.(stackContexter); ok {
+ ret = sc.stackContext().FrameInfo.forStack
return false
}
return true
@@ -660,46 +682,26 @@ func getCapturedStack(err error) (ret *stack) {
return
}
-// StackFrameInfoForError returns a StackFrameInfo suitable for use to implement
-// the StackContexter interface.
+// stackFrameInfoForError returns a stackFrameInfo suitable for use to implement
+// the stackContexter interface.
//
// It skips the provided number of frames when collecting the current trace
// (which should be equal to the number of functions between your error library
// and the user's code).
//
-// The returned StackFrameInfo will find the appropriate frame in the error's
+// The returned stackFrameInfo will find the appropriate frame in the error's
// existing stack information (if the error was created with errors.New), or
// include the current stack if it was not.
-func StackFrameInfoForError(skip int, err error) StackFrameInfo {
+func stackFrameInfoForError(skip int, err error) stackFrameInfo {
currentStack := captureStack(skip + 1)
currentlyCapturedStack := getCapturedStack(err)
if currentlyCapturedStack == nil || currentStack.id != currentlyCapturedStack.id {
// This is the very first annotation on this error OR
// We switched goroutines.
- return StackFrameInfo{forStack: currentStack}
+ return stackFrameInfo{forStack: currentStack}
}
- return StackFrameInfo{
+ return stackFrameInfo{
frameIdx: currentlyCapturedStack.findPointOfDivergence(currentStack),
forStack: currentlyCapturedStack,
}
}
-
-// ExtractData walks the error and extracts the given key's data from
-// Annotations (e.g. data added with D(key, <value>) will return <value>).
-//
-// The first matching key encountered (e.g. highest up the callstack) will be
-// returned.
-//
-// If the error does not contain key at all, this returns nil.
-func ExtractData(err error, key string) (ret interface{}) {
- Walk(err, func(err error) bool {
- if sc, ok := err.(StackContexter); ok {
- if d, ok := sc.StackContext().Data[key]; ok {
- ret = d.Value
- return false
- }
- }
- return true
- })
- return
-}

Powered by Google App Engine
This is Rietveld 408576698