github.com/safing/portbase@v0.19.5/modules/error.go (about)

     1  package modules
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime/debug"
     7  	"sync"
     8  	"time"
     9  )
    10  
    11  var (
    12  	errorReportingChannel chan *ModuleError
    13  	reportToStdErr        = true
    14  	lastReportedError     *ModuleError //nolint:errname
    15  	reportingLock         sync.Mutex
    16  )
    17  
    18  // ModuleError wraps a panic, error or message into an error that can be reported.
    19  type ModuleError struct {
    20  	Message string
    21  
    22  	ModuleName string
    23  	TaskName   string
    24  	TaskType   string // one of "worker", "task", "microtask" or custom
    25  	Severity   string // one of "info", "error", "panic" or custom
    26  
    27  	PanicValue interface{}
    28  	StackTrace string
    29  }
    30  
    31  // NewInfoMessage creates a new, reportable, info message (including a stack trace).
    32  func (m *Module) NewInfoMessage(message string) *ModuleError {
    33  	return &ModuleError{
    34  		Message:    message,
    35  		ModuleName: m.Name,
    36  		Severity:   "info",
    37  		StackTrace: string(debug.Stack()),
    38  	}
    39  }
    40  
    41  // NewErrorMessage creates a new, reportable, error message (including a stack trace).
    42  func (m *Module) NewErrorMessage(taskName string, err error) *ModuleError {
    43  	return &ModuleError{
    44  		Message:    err.Error(),
    45  		ModuleName: m.Name,
    46  		TaskName:   taskName,
    47  		Severity:   "error",
    48  		StackTrace: string(debug.Stack()),
    49  	}
    50  }
    51  
    52  // NewPanicError creates a new, reportable, panic error message (including a stack trace).
    53  func (m *Module) NewPanicError(taskName, taskType string, panicValue interface{}) *ModuleError {
    54  	me := &ModuleError{
    55  		Message:    fmt.Sprintf("panic: %s", panicValue),
    56  		ModuleName: m.Name,
    57  		TaskName:   taskName,
    58  		TaskType:   taskType,
    59  		Severity:   "panic",
    60  		PanicValue: panicValue,
    61  		StackTrace: string(debug.Stack()),
    62  	}
    63  	me.Message = me.Error()
    64  	return me
    65  }
    66  
    67  // Error returns the string representation of the error.
    68  func (me *ModuleError) Error() string {
    69  	return me.Message
    70  }
    71  
    72  // Format returns the error formatted in key/value form.
    73  func (me *ModuleError) Format() string {
    74  	return fmt.Sprintf(
    75  		`Message: %s
    76  Timestamp: %s
    77  ModuleName: %s
    78  TaskName: %s
    79  TaskType: %s
    80  Severity: %s
    81  PanicValue: %s
    82  StackTrace:
    83  
    84  %s
    85  `,
    86  		me.Message,
    87  		time.Now(),
    88  		me.ModuleName,
    89  		me.TaskName,
    90  		me.TaskType,
    91  		me.Severity,
    92  		me.PanicValue,
    93  		me.StackTrace,
    94  	)
    95  }
    96  
    97  // Report reports the error through the configured reporting channel.
    98  func (me *ModuleError) Report() {
    99  	reportingLock.Lock()
   100  	defer reportingLock.Unlock()
   101  
   102  	lastReportedError = me
   103  
   104  	if errorReportingChannel != nil {
   105  		select {
   106  		case errorReportingChannel <- me:
   107  		default:
   108  		}
   109  	}
   110  
   111  	if reportToStdErr {
   112  		// default to writing to stderr
   113  		fmt.Fprintf(
   114  			os.Stderr,
   115  			"===== Error Report =====\n%s\n===== End of Report =====\n",
   116  			me.Format(),
   117  		)
   118  	}
   119  }
   120  
   121  // IsPanic returns whether the given error is a wrapped panic by the modules package and additionally returns it, if true.
   122  func IsPanic(err error) (bool, *ModuleError) {
   123  	switch val := err.(type) { //nolint:errorlint // TODO: improve
   124  	case *ModuleError:
   125  		return true, val
   126  	default:
   127  		return false, nil
   128  	}
   129  }
   130  
   131  // SetErrorReportingChannel sets the channel to report module errors through. By default only panics are reported, all other errors need to be manually wrapped into a *ModuleError and reported.
   132  func SetErrorReportingChannel(reportingChannel chan *ModuleError) {
   133  	reportingLock.Lock()
   134  	defer reportingLock.Unlock()
   135  
   136  	errorReportingChannel = reportingChannel
   137  }
   138  
   139  // SetStdErrReporting controls error reporting to stderr.
   140  func SetStdErrReporting(on bool) {
   141  	reportingLock.Lock()
   142  	defer reportingLock.Unlock()
   143  
   144  	reportToStdErr = on
   145  }
   146  
   147  // GetLastReportedError returns the last reported module error.
   148  func GetLastReportedError() *ModuleError {
   149  	reportingLock.Lock()
   150  	defer reportingLock.Unlock()
   151  
   152  	return lastReportedError
   153  }