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 }