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

Unified Diff: common/errors/annotate.go

Issue 2951393002: [errors] de-specialize Transient in favor of Tags. (Closed)
Patch Set: more refactor 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..91dab743a84d59a5c5dfff394fdf9ef411fd7454 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,33 +68,33 @@ 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
- // Reason is the publicly-facing reason, and will show up in the Error()
+type stackContext struct {
+ frameInfo stackFrameInfo
+ // reason is the publicly-facing reason, and will show up in the Error()
// string.
- Reason string
+ reason string
// InternalReason is used for printing tracebacks, but is otherwise formatted
- // like Reason.
- InternalReason string
- Data Data
+ // like reason.
+ internalReason string
+ data Data
- Transient bool
+ tags map[TagKey]interface{}
}
// We're looking for %(sometext) which is not preceded by a %. sometext may be
@@ -136,17 +141,17 @@ func (d Data) Format(format string) string {
return fmt.Sprintf(strings.Join(parts, ""), args...)
}
-// RenderPublic renders the public error.Error()-style string for this frame,
+// 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 {
- if s.Reason == "" {
+func (s *stackContext) renderPublic(inner error) string {
+ if s.reason == "" {
if inner != nil {
return inner.Error()
}
return ""
}
- basis := s.Data.Format(s.Reason)
+ basis := s.data.Format(s.reason)
if inner != nil {
return fmt.Sprintf("%s: %s", basis, inner)
}
@@ -156,15 +161,15 @@ func (s *StackContext) RenderPublic(inner error) string {
// render renders the frame as a single entry in a stack trace. This looks like:
//
// I am an internal reson formatted with key1: value
-// reason: "The literal content of the Reason field: %(key2)d"
+// reason: "The literal content of the reason field: %(key2)d"
// "key1" = "value"
// "key2" = 10
-func (s *StackContext) render() Lines {
- siz := len(s.Data)
- if s.InternalReason != "" {
+func (s *stackContext) render() Lines {
+ siz := len(s.data)
+ if s.internalReason != "" {
siz++
}
- if s.Reason != "" {
+ if s.reason != "" {
siz++
}
if siz == 0 {
@@ -173,76 +178,78 @@ func (s *StackContext) render() Lines {
ret := make(Lines, 0, siz)
- if s.InternalReason != "" {
- ret = append(ret, s.Data.Format(s.InternalReason))
+ if s.internalReason != "" {
+ ret = append(ret, s.data.Format(s.internalReason))
}
- if s.Reason != "" {
- ret = append(ret, fmt.Sprintf("reason: %q", s.Data.Format(s.Reason)))
+ 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", key.description, val))
}
- if len(s.Data) > 0 {
- for k, v := range s.Data {
+ if len(s.data) > 0 {
+ for k, v := range s.data {
if v.StackFormat == "" || v.StackFormat == "%#v" {
ret = append(ret, fmt.Sprintf("%q = %#v", k, v.Value))
} else {
ret = append(ret, fmt.Sprintf("%q = "+v.StackFormat, k, v.Value))
}
}
- sort.Strings(ret[len(ret)-len(s.Data):])
+ sort.Strings(ret[len(ret)-len(s.data):])
}
return ret
}
-// AddData does a 'dict.update' addition of the data.
-func (s *StackContext) AddData(data Data) {
- if s.Data == nil {
- s.Data = make(Data, len(data))
+// addData does a 'dict.update' addition of the data.
+func (s *stackContext) addData(data Data) {
+ if s.data == nil {
+ s.data = make(Data, len(data))
}
for k, v := range data {
- s.Data[k] = v
+ s.data[k] = v
}
}
-// AddDatum adds a single data item to the Data in this frame
-func (s *StackContext) AddDatum(key string, value interface{}, format string) {
- if s.Data == nil {
- s.Data = Data{key: {value, format}}
+// addDatum adds a single data item to the Data in this frame
+func (s *stackContext) addDatum(key string, value interface{}, format string) {
+ if s.data == nil {
+ s.data = Data{key: {value, format}}
} else {
- s.Data[key] = Datum{value, format}
+ s.data[key] = Datum{value, format}
}
}
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) Error() string { return e.ctx.renderPublic(e.inner) }
+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.
@@ -273,7 +280,7 @@ func (a *Annotator) Reason(reason string) *Annotator {
if a == nil {
return a
}
- a.ctx.Reason = reason
+ a.ctx.reason = reason
return a
}
@@ -284,7 +291,7 @@ func (a *Annotator) InternalReason(reason string) *Annotator {
if a == nil {
return a
}
- a.ctx.InternalReason = reason
+ a.ctx.internalReason = reason
return a
}
@@ -302,7 +309,7 @@ func (a *Annotator) D(key string, value interface{}, format ...string) *Annotato
default:
panic(fmt.Errorf("len(format) > 1: %d", len(format)))
}
- a.ctx.AddDatum(key, value, formatVal)
+ a.ctx.addDatum(key, value, formatVal)
return a
}
@@ -311,18 +318,31 @@ func (a *Annotator) Data(data Data) *Annotator {
if a == nil {
return a
}
- a.ctx.AddData(data)
+ a.ctx.addData(data)
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 unary optional argument, and must be a simple type (i.e. has
+// a reflect.Kind which is a base data type like bool, string, or int).
+func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator {
if a == nil {
return a
}
- if !IsTransient(a.inner) {
- a.ctx.Transient = true
+ tagMap := make(map[TagKey]interface{}, len(tags))
+ for _, t := range tags {
+ v := t.GenerateErrorTagValue()
+ tagMap[v.Key] = v.Value
+ }
+ if len(tagMap) > 0 {
+ if a.ctx.tags == nil {
+ a.ctx.tags = tagMap
+ } else {
+ for k, v := range tagMap {
+ a.ctx.tags[k] = v
+ }
+ }
}
return a
}
@@ -345,6 +365,9 @@ func Log(c context.Context, err error) {
}
// Lines is just a list of printable lines.
+//
+// It's a type because it's most frequently used as []Lines, and [][]string
+// doesn't read well.
type Lines []string
// RenderedFrame represents a single, rendered stack frame.
@@ -538,7 +561,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,10 +588,10 @@ func RenderStack(err error) *RenderedError {
}
for err != nil {
- if sc, ok := err.(StackContexter); ok {
- ctx := sc.StackContext()
- if stk := ctx.FrameInfo.forStack; stk != nil {
- frm := getCurFrame(&ctx.FrameInfo)
+ 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 {
frm.Annotations = append(frm.Annotations, rendered)
}
@@ -581,7 +604,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 +636,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 +648,24 @@ 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 ...TagValueGenerator) error {
+ tse := &terminalStackError{
+ errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil}
+ if len(tags) > 0 {
+ tse.tags = make(map[TagKey]interface{}, len(tags))
+ for _, t := range tags {
+ v := t.GenerateErrorTagValue()
+ tse.tags[v.Key] = v.Value
+ }
+ }
+ return tse
}
func captureStack(skip int) *stack {
@@ -651,8 +682,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 +691,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