github.com/jxskiss/gopkg@v0.17.3/easy/recover.go (about)

     1  package easy
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"runtime/debug"
     8  	"strings"
     9  )
    10  
    11  // PanicError represents an captured panic error.
    12  type PanicError struct {
    13  	Exception  interface{}
    14  	Location   string
    15  	Stacktrace []byte
    16  }
    17  
    18  func (p *PanicError) Error() string {
    19  	return fmt.Sprintf("panic: %v, location: %v", p.Exception, p.Location)
    20  }
    21  
    22  // Go calls the given function with panic recover, in case of panic happens,
    23  // the panic message, location and the calling stack will be logged using
    24  // the default logger configured by `ConfigLog` in this package.
    25  func Go(f func()) {
    26  	go func() {
    27  		defer Recover(nil, nil)
    28  		f()
    29  	}()
    30  }
    31  
    32  // Go1 calls the given function with panic recover, in case an error is returned,
    33  // or panic happens, the error message or panic information will be logged
    34  // using the default logger configured by `ConfigLog` in this package.
    35  func Go1(f func() error) {
    36  	go func() {
    37  		defer Recover(nil, nil)
    38  		err := f()
    39  		if err == nil {
    40  			return
    41  		}
    42  		_logcfg.getLogger(nil).Errorf("catch error: %v", err)
    43  	}()
    44  }
    45  
    46  // Safe returns an wrapped function with panic recover.
    47  //
    48  // Note that if panic happens, the wrapped function does not log messages,
    49  // instead it will be returned as a `*PanicError`, the caller take
    50  // responsibility to log the panic messages.
    51  func Safe(f func()) func() error {
    52  	return func() (err error) {
    53  		defer func() {
    54  			e := recover()
    55  			if e == nil {
    56  				return
    57  			}
    58  			panicLoc := IdentifyPanic()
    59  			stack := debug.Stack()
    60  			err = &PanicError{
    61  				Exception:  e,
    62  				Location:   panicLoc,
    63  				Stacktrace: stack,
    64  			}
    65  		}()
    66  		f()
    67  		return nil
    68  	}
    69  }
    70  
    71  // Safe1 returns an wrapped function with panic recover.
    72  //
    73  // Note that if panic or error happens, the wrapped function does not log
    74  // messages, instead it will be returned as an error, the caller take
    75  // responsibility to log the panic or error messages.
    76  func Safe1(f func() error) func() error {
    77  	return func() (err error) {
    78  		defer func() {
    79  			e := recover()
    80  			if e == nil {
    81  				return
    82  			}
    83  			panicLoc := IdentifyPanic()
    84  			stack := debug.Stack()
    85  			err = &PanicError{
    86  				Exception:  e,
    87  				Location:   panicLoc,
    88  				Stacktrace: stack,
    89  			}
    90  		}()
    91  		err = f()
    92  		return
    93  	}
    94  }
    95  
    96  // Recover recovers unexpected panic, and log error messages using
    97  // logger associated with the given context, if `err` is not nil,
    98  // an wrapped `PanicError` will be assigned to it.
    99  //
   100  // Note that this function should not be wrapped be another function,
   101  // instead it should be called directly by the `defer` statement,
   102  // or it won't work as you may expect.
   103  func Recover(ctx context.Context, err *error) {
   104  	e := recover()
   105  	if e == nil {
   106  		return
   107  	}
   108  
   109  	panicLoc := IdentifyPanic()
   110  	stack := debug.Stack()
   111  	pErr := &PanicError{
   112  		Exception:  e,
   113  		Location:   panicLoc,
   114  		Stacktrace: stack,
   115  	}
   116  
   117  	// If the caller receives the error, we don't log it here,
   118  	// else we log the panic error with stack information.
   119  	if err != nil {
   120  		*err = pErr
   121  		return
   122  	}
   123  	_logcfg.getLogger(&ctx).Errorf("catch %v\n%s", pErr, stack)
   124  }
   125  
   126  // IdentifyPanic reports the panic location when a panic happens.
   127  func IdentifyPanic() string {
   128  	var name, file string
   129  	var line int
   130  	var pc [16]uintptr
   131  
   132  	n := runtime.Callers(3, pc[:])
   133  	for _, pc := range pc[:n] {
   134  		fn := runtime.FuncForPC(pc)
   135  		if fn == nil {
   136  			continue
   137  		}
   138  		file, line = fn.FileLine(pc)
   139  		name = fn.Name()
   140  		if !strings.HasPrefix(name, "runtime.") {
   141  			break
   142  		}
   143  	}
   144  	switch {
   145  	case name != "":
   146  		return fmt.Sprintf("%v:%v", name, line)
   147  	case file != "":
   148  		return fmt.Sprintf("%v:%v", file, line)
   149  	}
   150  
   151  	return fmt.Sprintf("pc:%x", pc)
   152  }
   153  
   154  // EnsureError ensures the given value (should be non-nil) is an error.
   155  // If it's not an error, `fmt.Errorf("%v", v)` will be used to convert it.
   156  func EnsureError(v interface{}) error {
   157  	if v == nil {
   158  		return nil
   159  	}
   160  	err, ok := v.(error)
   161  	if !ok {
   162  		err = fmt.Errorf("%v", v)
   163  	}
   164  	return err
   165  }
   166  
   167  // PanicOnError fires a panic if any of the args is non-nil error.
   168  func PanicOnError(args ...interface{}) {
   169  	for _, arg := range args {
   170  		if err, ok := arg.(error); ok && err != nil {
   171  			panic(err)
   172  		}
   173  	}
   174  }