github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/errors_timestamp.go (about) 1 package grip 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/mongodb/grip/level" 8 "github.com/pkg/errors" 9 ) 10 11 type errorCauser interface { 12 Cause() error 13 } 14 15 // ErrorTimeFinder unwraps a timestamp annotated error if possible and 16 // is capable of finding a timestamp in an error that has been 17 // annotated using pkg/errors. 18 func ErrorTimeFinder(err error) (time.Time, bool) { 19 if err == nil { 20 return time.Time{}, false 21 } 22 23 if tserr, ok := err.(*timestampError); ok { 24 if tserr == nil { 25 return time.Time{}, false 26 } 27 return tserr.time, true 28 } 29 30 for { 31 if e, ok := err.(errorCauser); ok { 32 if tserr, ok := e.(*timestampError); ok { 33 if tserr == nil { 34 break 35 } 36 return tserr.time, true 37 } 38 err = e.Cause() 39 continue 40 } 41 break 42 } 43 44 return time.Time{}, false 45 } 46 47 type timestampError struct { 48 err error 49 time time.Time 50 extended bool 51 52 // message.Composer data 53 ErrorValue string `bson:"error" json:"error" yaml:"error"` 54 Extended string `bson:"extended,omitempty" json:"extended,omitempty" yaml:"extended,omitempty"` 55 Metadata struct { 56 Level level.Priority `bson:"level,omitempty" json:"level,omitempty" yaml:"level,omitempty"` 57 Context map[string]interface{} `bson:"context,omitempty" json:"context,omitempty" yaml:"context,omitempty"` 58 } `bson:"metadata,omitempty" json:"metadata,omitempty" yaml:"metadata,omitempty"` 59 } 60 61 func newTimeStampError(err error) *timestampError { 62 if err == nil { 63 return nil 64 } 65 66 switch v := err.(type) { 67 case *timestampError: 68 return v 69 default: 70 return ×tampError{ 71 err: err, 72 time: time.Now(), 73 } 74 } 75 } 76 77 func (e *timestampError) setExtended(v bool) *timestampError { e.extended = v; return e } 78 79 // WrapErrorTime annotates an error with the timestamp. The underlying 80 // concrete object implements message.Composer as well as error. 81 func WrapErrorTime(err error) error { 82 return newTimeStampError(err) 83 } 84 85 // WrapErrorTimeMessage annotates an error with the timestamp and a 86 // string form. The underlying concrete object implements 87 // message.Composer as well as error. 88 func WrapErrorTimeMessage(err error, m string) error { 89 return newTimeStampError(errors.WithMessage(err, m)) 90 } 91 92 // WrapErrorTimeMessagef annotates an error with a timestamp and a 93 // string formated message, like fmt.Sprintf or fmt.Errorf. The 94 // underlying concrete object implements message.Composer as well as 95 // error. 96 func WrapErrorTimeMessagef(err error, m string, args ...interface{}) error { 97 return newTimeStampError(errors.WithMessage(err, fmt.Sprintf(m, args...))) 98 } 99 100 func (e *timestampError) String() string { 101 if e.err == nil { 102 return "" 103 } 104 105 if e.extended { 106 return fmt.Sprintf("%+v", e.err) 107 } 108 109 return e.err.Error() 110 } 111 112 func (e *timestampError) Cause() error { return e.err } 113 114 func (e *timestampError) Error() string { 115 return fmt.Sprintf("[%s], %s", e.time.Format(time.RFC3339), e.String()) 116 } 117 118 func (e *timestampError) Format(s fmt.State, verb rune) { 119 switch verb { 120 case 'v': 121 if s.Flag('+') { 122 _, _ = fmt.Fprintf(s, "[%s] %+v", e.time.Format(time.RFC3339), e.Cause()) 123 } 124 fallthrough 125 case 's': 126 _, _ = fmt.Fprintf(s, "[%s] %s", e.time.Format(time.RFC3339), e.String()) 127 case 'q': 128 _, _ = fmt.Fprintf(s, "[%s] %q", e.time.Format(time.RFC3339), e.String()) 129 } 130 } 131 132 func (e *timestampError) Loggable() bool { return e.err != nil } 133 func (e *timestampError) Raw() interface{} { 134 if e.ErrorValue == "" { 135 e.ErrorValue = e.String() 136 137 extended := fmt.Sprintf("%+v", e.err) 138 if extended != e.ErrorValue { 139 e.Extended = extended 140 } 141 } 142 143 return e 144 } 145 146 func (e *timestampError) Annotate(key string, v interface{}) error { 147 if e.Metadata.Context == nil { 148 e.Metadata.Context = map[string]interface{}{ 149 key: v, 150 } 151 return nil 152 } 153 154 if _, ok := e.Metadata.Context[key]; ok { 155 return fmt.Errorf("key '%s' already exists", key) 156 } 157 158 e.Metadata.Context[key] = v 159 160 return nil 161 } 162 163 func (e *timestampError) Priority() level.Priority { 164 return e.Metadata.Level 165 } 166 func (e *timestampError) SetPriority(l level.Priority) error { 167 if !l.IsValid() { 168 return fmt.Errorf("%s (%d) is not a valid priority", l, l) 169 } 170 171 e.Metadata.Level = l 172 return nil 173 }