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 }