vitess.io/vitess@v0.16.2/go/vt/vterrors/vterrors.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package vterrors provides simple error handling primitives for Vitess 18 // 19 // In all Vitess code, errors should be propagated using vterrors.Wrapf() 20 // and not fmt.Errorf(). This makes sure that stacktraces are kept and 21 // propagated correctly. 22 // 23 // # New errors should be created using vterrors.New or vterrors.Errorf 24 // 25 // Vitess uses canonical error codes for error reporting. This is based 26 // on years of industry experience with error reporting. This idea is 27 // that errors should be classified into a small set of errors (10 or so) 28 // with very specific meaning. Each error has a code, and a message. When 29 // errors are passed around (even through RPCs), the code is 30 // propagated. To handle errors, only the code should be looked at (and 31 // not string-matching on the error message). 32 // 33 // Error codes are defined in /proto/vtrpc.proto. Along with an 34 // RPCError message that can be used to transmit errors through RPCs, in 35 // the message payloads. These codes match the names and numbers defined 36 // by gRPC. 37 // 38 // A standardized error implementation that allows you to build an error 39 // with an associated canonical code is also defined. 40 // While sending an error through gRPC, these codes are transmitted 41 // using gRPC's error propagation mechanism and decoded back to 42 // the original code on the other end. 43 // 44 // # Retrieving the cause of an error 45 // 46 // Using vterrors.Wrap constructs a stack of errors, adding context to the 47 // preceding error, instead of simply building up a string. 48 // Depending on the nature of the error it may be necessary to reverse the 49 // operation of errors.Wrap to retrieve the original error for inspection. 50 // Any error value which implements this interface 51 // 52 // type causer interface { 53 // Cause() error 54 // } 55 // 56 // can be inspected by vterrors.Cause and vterrors.RootCause. 57 // 58 // - vterrors.Cause will find the immediate cause if one is available, or nil 59 // if the error is not a `causer` or if no cause is available. 60 // 61 // - vterrors.RootCause will recursively retrieve 62 // the topmost error which does not implement causer, which is assumed to be 63 // the original cause. For example: 64 // 65 // switch err := errors.RootCause(err).(type) { 66 // case *MyError: 67 // // handle specifically 68 // default: 69 // // unknown error 70 // } 71 // 72 // causer interface is not exported by this package, but is considered a part 73 // of stable public API. 74 // 75 // # Formatted printing of errors 76 // 77 // All error values returned from this package implement fmt.Formatter and can 78 // be formatted by the fmt package. The following verbs are supported 79 // 80 // %s print the error. If the error has a Cause it will be 81 // printed recursively 82 // %v extended format. Each Frame of the error's StackTrace will 83 // be printed in detail. 84 // 85 // Most but not all of the code in this file was originally copied from 86 // https://github.com/pkg/errors/blob/v0.8.0/errors.go 87 package vterrors 88 89 import ( 90 "context" 91 "errors" 92 "fmt" 93 "io" 94 95 "github.com/spf13/pflag" 96 97 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 98 ) 99 100 // logErrStacks controls whether or not printing errors includes the 101 // embedded stack trace in the output. 102 var logErrStacks bool 103 104 // RegisterFlags registers the command-line options that control vterror 105 // behavior on the provided FlagSet. 106 func RegisterFlags(fs *pflag.FlagSet) { 107 fs.BoolVar(&logErrStacks, "log_err_stacks", false, "log stack traces for errors") 108 } 109 110 // New returns an error with the supplied message. 111 // New also records the stack trace at the point it was called. 112 func New(code vtrpcpb.Code, message string) error { 113 return &fundamental{ 114 msg: message, 115 code: code, 116 stack: callers(), 117 } 118 } 119 120 // Errorf formats according to a format specifier and returns the string 121 // as a value that satisfies error. 122 // Errorf also records the stack trace at the point it was called. 123 // Use this for Vitess-specific errors that don't have a MySQL counterpart 124 func Errorf(code vtrpcpb.Code, format string, args ...any) error { 125 return &fundamental{ 126 msg: fmt.Sprintf(format, args...), 127 code: code, 128 stack: callers(), 129 } 130 } 131 132 // NewErrorf formats according to a format specifier and returns the string 133 // as a value that satisfies error. 134 // NewErrorf also records the stack trace at the point it was called. 135 // Use this for errors in Vitess that we eventually want to mimic as a MySQL error 136 func NewErrorf(code vtrpcpb.Code, state State, format string, args ...any) error { 137 msg := format 138 if len(args) != 0 { 139 msg = fmt.Sprintf(format, args...) 140 } 141 return &fundamental{ 142 msg: msg, 143 code: code, 144 state: state, 145 stack: callers(), 146 } 147 } 148 149 // fundamental is an error that has a message and a stack, but no caller. 150 type fundamental struct { 151 msg string 152 code vtrpcpb.Code 153 state State 154 *stack 155 } 156 157 func (f *fundamental) Error() string { return f.msg } 158 159 func (f *fundamental) Format(s fmt.State, verb rune) { 160 switch verb { 161 case 'v': 162 panicIfError(io.WriteString(s, "Code: "+f.code.String()+"\n")) 163 panicIfError(io.WriteString(s, f.msg+"\n")) 164 if logErrStacks { 165 f.stack.Format(s, verb) 166 } 167 return 168 case 's': 169 panicIfError(io.WriteString(s, f.msg)) 170 case 'q': 171 panicIfError(fmt.Fprintf(s, "%q", f.msg)) 172 } 173 } 174 175 // Code returns the error code if it's a vtError. 176 // If err is nil, it returns ok. 177 func Code(err error) vtrpcpb.Code { 178 if err == nil { 179 return vtrpcpb.Code_OK 180 } 181 if err, ok := err.(ErrorWithCode); ok { 182 return err.ErrorCode() 183 } 184 185 cause := Cause(err) 186 if cause != err && cause != nil { 187 // If we did not find an error code at the outer level, let's find the cause and check it's code 188 return Code(cause) 189 } 190 191 // Handle some special cases. 192 switch err { 193 case context.Canceled: 194 return vtrpcpb.Code_CANCELED 195 case context.DeadlineExceeded: 196 return vtrpcpb.Code_DEADLINE_EXCEEDED 197 } 198 return vtrpcpb.Code_UNKNOWN 199 } 200 201 // ErrState returns the error state if it's a vtError. 202 // If err is nil, it returns Undefined. 203 func ErrState(err error) State { 204 if err == nil { 205 return Undefined 206 } 207 208 if err, ok := err.(ErrorWithState); ok { 209 return err.ErrorState() 210 } 211 212 cause := Cause(err) 213 if cause != err && cause != nil { 214 // If we did not find an error state at the outer level, let's find the cause and check it's state 215 return ErrState(cause) 216 } 217 return Undefined 218 } 219 220 // Wrap returns an error annotating err with a stack trace 221 // at the point Wrap is called, and the supplied message. 222 // If err is nil, Wrap returns nil. 223 func Wrap(err error, message string) error { 224 if err == nil { 225 return nil 226 } 227 return &wrapping{ 228 cause: err, 229 msg: message, 230 stack: callers(), 231 } 232 } 233 234 // Wrapf returns an error annotating err with a stack trace 235 // at the point Wrapf is call, and the format specifier. 236 // If err is nil, Wrapf returns nil. 237 func Wrapf(err error, format string, args ...any) error { 238 if err == nil { 239 return nil 240 } 241 return &wrapping{ 242 cause: err, 243 msg: fmt.Sprintf(format, args...), 244 stack: callers(), 245 } 246 } 247 248 // Unwrap attempts to return the Cause of the given error, if it is indeed the result of a vterrors.Wrapf() 249 // The function indicates whether the error was indeed wrapped. If the error was not wrapped, the function 250 // returns the original error. 251 func Unwrap(err error) (wasWrapped bool, unwrapped error) { 252 var w *wrapping 253 if errors.As(err, &w) { 254 return true, w.Cause() 255 } 256 return false, err 257 } 258 259 // UnwrapAll attempts to recursively unwrap the given error, and returns the most underlying cause 260 func UnwrapAll(err error) error { 261 wasWrapped := true 262 for wasWrapped { 263 wasWrapped, err = Unwrap(err) 264 } 265 return err 266 } 267 268 type wrapping struct { 269 cause error 270 msg string 271 stack *stack 272 } 273 274 func (w *wrapping) Error() string { return w.msg + ": " + w.cause.Error() } 275 func (w *wrapping) Cause() error { return w.cause } 276 277 func (w *wrapping) Format(s fmt.State, verb rune) { 278 if rune('v') == verb { 279 panicIfError(fmt.Fprintf(s, "%v\n", w.Cause())) 280 panicIfError(io.WriteString(s, w.msg)) 281 if logErrStacks { 282 w.stack.Format(s, verb) 283 } 284 return 285 } 286 287 if rune('s') == verb || rune('q') == verb { 288 panicIfError(io.WriteString(s, w.Error())) 289 } 290 } 291 292 // since we can't return an error, let's panic if something goes wrong here 293 func panicIfError(_ int, err error) { 294 if err != nil { 295 panic(err) 296 } 297 } 298 299 // RootCause returns the underlying cause of the error, if possible. 300 // An error value has a cause if it implements the following 301 // interface: 302 // 303 // type causer interface { 304 // Cause() error 305 // } 306 // 307 // If the error does not implement Cause, the original error will 308 // be returned. If the error is nil, nil will be returned without further 309 // investigation. 310 func RootCause(err error) error { 311 for { 312 cause := Cause(err) 313 if cause == nil { 314 return err 315 } 316 err = cause 317 } 318 } 319 320 // Cause will return the immediate cause, if possible. 321 // An error value has a cause if it implements the following 322 // interface: 323 // 324 // type causer interface { 325 // Cause() error 326 // } 327 // 328 // If the error does not implement Cause, nil will be returned 329 func Cause(err error) error { 330 type causer interface { 331 Cause() error 332 } 333 334 causerObj, ok := err.(causer) 335 if !ok { 336 return nil 337 } 338 339 return causerObj.Cause() 340 } 341 342 // Equals returns true iff the error message and the code returned by Code() 343 // are equal. 344 func Equals(a, b error) bool { 345 if a == nil && b == nil { 346 // Both are nil. 347 return true 348 } 349 350 if a == nil || b == nil { 351 // One of the two is nil, since we know both are not nil. 352 return false 353 } 354 355 return a.Error() == b.Error() && Code(a) == Code(b) 356 } 357 358 // Print is meant to print the vtError object in test failures. 359 // For comparing two vterrors, use Equals() instead. 360 func Print(err error) string { 361 return fmt.Sprintf("%v: %v\n", Code(err), err.Error()) 362 } 363 364 func (f *fundamental) ErrorState() State { return f.state } 365 func (f *fundamental) ErrorCode() vtrpcpb.Code { return f.code }