2023-02-15 16:04:19 +01:00
package exerr
import (
"bytes"
2023-07-24 10:42:39 +02:00
"context"
2023-02-15 16:04:19 +01:00
"encoding/json"
"fmt"
2023-07-24 10:42:39 +02:00
"github.com/gin-gonic/gin"
2023-02-15 16:04:19 +01:00
"github.com/rs/zerolog"
"go.mongodb.org/mongo-driver/bson/primitive"
2023-07-24 10:42:39 +02:00
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
2023-08-09 14:40:16 +02:00
"gogs.mikescher.com/BlackForestBytes/goext/enums"
2023-02-15 16:04:19 +01:00
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"net/http"
"os"
"runtime/debug"
"strings"
"time"
)
//
// ==== USAGE =====
//
2023-07-24 10:42:39 +02:00
// If some method returns an error _always wrap it into an exerror:
2023-02-15 16:04:19 +01:00
// value, err := do_something(..)
// if err != nil {
2023-07-24 10:42:39 +02:00
// return nil, exerror.Wrap(err, "do something failed").Build()
2023-02-15 16:04:19 +01:00
// }
//
// If possible add metadata to the error (eg the id that was not found, ...), the methods are the same as in zerolog
2023-07-24 10:42:39 +02:00
// return nil, exerror.Wrap(err, "do something failed").Str("someid", id).Int("count", in.Count).Build()
2023-02-15 16:04:19 +01:00
//
2024-07-27 23:44:18 +02:00
// You can also add extra-data to an error with Extra(..)
// in contrast to metadata is extradata always printed in the resulting error and is more intended for additional (programmatically readable) data in addition to the errortype
// (metadata is more internal debug info/help)
//
2023-02-15 16:04:19 +01:00
// You can change the errortype with `.User()` and `.System()` (User-errors are 400 and System-errors 500)
// You can also manually set the statuscode with `.WithStatuscode(http.NotFound)`
// You can set the type with `WithType(..)`
//
// New Errors (that don't wrap an existing err object) are created with New
2023-07-24 10:42:39 +02:00
// return nil, exerror.New(exerror.TypeInternal, "womethign wen horrible wrong").Build()
2023-02-15 16:04:19 +01:00
// You can eitehr use an existing ErrorType, the "catch-all" ErrInternal, or add you own ErrType in consts.go
//
// All errors should be handled one of the following four ways:
// - return the error to the caller and let him handle it:
// (also auto-prints the error to the log)
// => Wrap/New + Build
// - Print the error
// (also auto-sends it to the error-service)
// This is useful for errors that happen asynchron or are non-fatal for the current request
// => Wrap/New + Print
// - Return the error to the Rest-API caller
// (also auto-prints the error to the log)
// (also auto-sends it to the error-service)
// => Wrap/New + Output
// - Print and stop the service
// (also auto-sends it to the error-service)
// => Wrap/New + Fatal
//
type Builder struct {
2024-05-03 15:28:53 +02:00
wrappedErr error
errorData * ExErr
containsGinData bool
containsContextData bool
noLog bool
2023-02-15 16:04:19 +01:00
}
func Get ( err error ) * Builder {
2023-07-24 11:11:15 +02:00
return & Builder { errorData : FromError ( err ) }
2023-02-15 16:04:19 +01:00
}
func New ( t ErrorType , msg string ) * Builder {
2023-07-24 10:42:39 +02:00
return & Builder { errorData : newExErr ( CatSystem , t , msg ) }
2023-02-15 16:04:19 +01:00
}
func Wrap ( err error , msg string ) * Builder {
2023-08-08 18:01:00 +02:00
if err == nil {
return & Builder { errorData : newExErr ( CatSystem , TypeInternal , msg ) } // prevent NPE if we call Wrap with err==nil
}
2024-07-27 23:44:18 +02:00
v := FromError ( err )
2023-07-24 11:11:15 +02:00
if ! pkgconfig . RecursiveErrors {
v . Message = msg
2024-01-13 14:10:25 +01:00
return & Builder { wrappedErr : err , errorData : v }
2024-07-27 23:44:18 +02:00
} else {
return & Builder { wrappedErr : err , errorData : wrapExErr ( v , msg , CatWrap , 1 ) }
2023-07-24 11:11:15 +02:00
}
2023-02-15 16:04:19 +01:00
}
// ----------------------------------------------------------------------------
func ( b * Builder ) WithType ( t ErrorType ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Type = t
2023-02-15 16:04:19 +01:00
return b
}
func ( b * Builder ) WithStatuscode ( status int ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . StatusCode = & status
2023-02-15 16:04:19 +01:00
return b
}
func ( b * Builder ) WithMessage ( msg string ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Message = msg
2023-02-15 16:04:19 +01:00
return b
}
// ----------------------------------------------------------------------------
// Err changes the Severity to ERROR (default)
// The error will be:
//
// - On Build():
//
// - Short-Logged as Err
//
// - On Print():
//
// - Logged as Err
//
// - Send to the error-service
//
// - On Output():
//
// - Logged as Err
//
// - Send to the error-service
func ( b * Builder ) Err ( ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Severity = SevErr
2023-02-15 16:04:19 +01:00
return b
}
// Warn changes the Severity to WARN
// The error will be:
//
// - On Build():
//
// - -(nothing)-
//
// - On Print():
//
// - Short-Logged as Warn
//
// - On Output():
//
// - Logged as Warn
func ( b * Builder ) Warn ( ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Severity = SevWarn
2023-02-15 16:04:19 +01:00
return b
}
// Info changes the Severity to INFO
// The error will be:
//
// - On Build():
//
// - -(nothing)-
//
// - On Print():
//
// - -(nothing)-
//
// - On Output():
//
// - -(nothing)-
func ( b * Builder ) Info ( ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Severity = SevInfo
2023-02-15 16:04:19 +01:00
return b
}
// ----------------------------------------------------------------------------
// User sets the Category to CatUser
//
// Errors with category
func ( b * Builder ) User ( ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Category = CatUser
2023-02-15 16:04:19 +01:00
return b
}
func ( b * Builder ) System ( ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Category = CatSystem
2023-02-15 16:04:19 +01:00
return b
}
// ----------------------------------------------------------------------------
2023-11-14 16:00:14 +01:00
func ( b * Builder ) NoLog ( ) * Builder {
b . noLog = true
return b
}
// ----------------------------------------------------------------------------
2023-02-15 16:04:19 +01:00
func ( b * Builder ) Id ( key string , val fmt . Stringer ) * Builder {
return b . addMeta ( key , MDTID , newIDWrap ( val ) )
}
func ( b * Builder ) StrPtr ( key string , val * string ) * Builder {
return b . addMeta ( key , MDTStringPtr , val )
}
func ( b * Builder ) Str ( key string , val string ) * Builder {
return b . addMeta ( key , MDTString , val )
}
func ( b * Builder ) Int ( key string , val int ) * Builder {
return b . addMeta ( key , MDTInt , val )
}
func ( b * Builder ) Int8 ( key string , val int8 ) * Builder {
return b . addMeta ( key , MDTInt8 , val )
}
func ( b * Builder ) Int16 ( key string , val int16 ) * Builder {
return b . addMeta ( key , MDTInt16 , val )
}
func ( b * Builder ) Int32 ( key string , val int32 ) * Builder {
return b . addMeta ( key , MDTInt32 , val )
}
func ( b * Builder ) Int64 ( key string , val int64 ) * Builder {
return b . addMeta ( key , MDTInt64 , val )
}
func ( b * Builder ) Float32 ( key string , val float32 ) * Builder {
return b . addMeta ( key , MDTFloat32 , val )
}
func ( b * Builder ) Float64 ( key string , val float64 ) * Builder {
return b . addMeta ( key , MDTFloat64 , val )
}
func ( b * Builder ) Bool ( key string , val bool ) * Builder {
return b . addMeta ( key , MDTBool , val )
}
func ( b * Builder ) Bytes ( key string , val [ ] byte ) * Builder {
return b . addMeta ( key , MDTBytes , val )
}
func ( b * Builder ) ObjectID ( key string , val primitive . ObjectID ) * Builder {
return b . addMeta ( key , MDTObjectID , val )
}
func ( b * Builder ) Time ( key string , val time . Time ) * Builder {
return b . addMeta ( key , MDTTime , val )
}
func ( b * Builder ) Dur ( key string , val time . Duration ) * Builder {
return b . addMeta ( key , MDTDuration , val )
}
func ( b * Builder ) Strs ( key string , val [ ] string ) * Builder {
return b . addMeta ( key , MDTStringArray , val )
}
func ( b * Builder ) Ints ( key string , val [ ] int ) * Builder {
return b . addMeta ( key , MDTIntArray , val )
}
func ( b * Builder ) Ints32 ( key string , val [ ] int32 ) * Builder {
return b . addMeta ( key , MDTInt32Array , val )
}
func ( b * Builder ) Type ( key string , cls interface { } ) * Builder {
return b . addMeta ( key , MDTString , fmt . Sprintf ( "%T" , cls ) )
}
func ( b * Builder ) Interface ( key string , val interface { } ) * Builder {
return b . addMeta ( key , MDTAny , newAnyWrap ( val ) )
}
func ( b * Builder ) Any ( key string , val any ) * Builder {
return b . addMeta ( key , MDTAny , newAnyWrap ( val ) )
}
2023-08-08 11:52:40 +02:00
func ( b * Builder ) Stringer ( key string , val fmt . Stringer ) * Builder {
2023-10-30 13:37:31 +01:00
if langext . IsNil ( val ) {
2023-08-08 14:28:09 +02:00
return b . addMeta ( key , MDTString , "(!nil)" )
} else {
return b . addMeta ( key , MDTString , val . String ( ) )
}
2023-08-08 11:52:40 +02:00
}
2023-08-09 14:40:16 +02:00
func ( b * Builder ) Enum ( key string , val enums . Enum ) * Builder {
return b . addMeta ( key , MDTEnum , newEnumWrap ( val ) )
}
2023-02-15 16:04:19 +01:00
func ( b * Builder ) Stack ( ) * Builder {
return b . addMeta ( "@Stack" , MDTString , string ( debug . Stack ( ) ) )
}
func ( b * Builder ) Errs ( key string , val [ ] error ) * Builder {
for i , valerr := range val {
2023-07-24 10:42:39 +02:00
b . addMeta ( fmt . Sprintf ( "%v[%v]" , key , i ) , MDTString , Get ( valerr ) . errorData . FormatLog ( LogPrintFull ) )
2023-02-15 16:04:19 +01:00
}
return b
}
func ( b * Builder ) GinReq ( ctx context . Context , g * gin . Context , req * http . Request ) * Builder {
if v := ctx . Value ( "start_timestamp" ) ; v != nil {
if t , ok := v . ( time . Time ) ; ok {
2024-05-03 13:24:08 +02:00
b . Time ( "ctx_startTimestamp" , t )
b . Time ( "ctx_endTimestamp" , time . Now ( ) )
2023-02-15 16:04:19 +01:00
}
}
2024-05-03 13:24:08 +02:00
b . Str ( "gin_method" , req . Method )
b . Str ( "gin_path" , g . FullPath ( ) )
b . Strs ( "gin_header" , extractHeader ( g . Request . Header ) )
2023-02-15 16:04:19 +01:00
if req . URL != nil {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_url" , req . URL . String ( ) )
2023-02-15 16:04:19 +01:00
}
if ctxVal := g . GetString ( "apiversion" ) ; ctxVal != "" {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_context_apiversion" , ctxVal )
2023-02-15 16:04:19 +01:00
}
if ctxVal := g . GetString ( "uid" ) ; ctxVal != "" {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_context_uid" , ctxVal )
2023-02-15 16:04:19 +01:00
}
if ctxVal := g . GetString ( "fcmId" ) ; ctxVal != "" {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_context_fcmid" , ctxVal )
2023-02-15 16:04:19 +01:00
}
if ctxVal := g . GetString ( "reqid" ) ; ctxVal != "" {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_context_reqid" , ctxVal )
2023-02-15 16:04:19 +01:00
}
2023-08-14 15:36:12 +02:00
if req . Method != "GET" && req . Body != nil {
if req . Header . Get ( "Content-Type" ) == "application/json" {
if brc , ok := req . Body . ( dataext . BufferedReadCloser ) ; ok {
if bin , err := brc . BufferedAll ( ) ; err == nil {
if len ( bin ) < 16 * 1024 {
var prettyJSON bytes . Buffer
err = json . Indent ( & prettyJSON , bin , "" , " " )
if err == nil {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_body" , string ( prettyJSON . Bytes ( ) ) )
2023-08-14 15:36:12 +02:00
} else {
2024-05-03 13:24:08 +02:00
b . Bytes ( "gin_body" , bin )
2023-08-14 15:36:12 +02:00
}
2023-02-15 16:04:19 +01:00
} else {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_body" , fmt . Sprintf ( "[[%v bytes | %s]]" , len ( bin ) , req . Header . Get ( "Content-Type" ) ) )
2023-08-14 15:36:12 +02:00
}
}
}
}
if req . Header . Get ( "Content-Type" ) == "multipart/form-data" || req . Header . Get ( "Content-Type" ) == "x-www-form-urlencoded" {
if brc , ok := req . Body . ( dataext . BufferedReadCloser ) ; ok {
if bin , err := brc . BufferedAll ( ) ; err == nil {
if len ( bin ) < 16 * 1024 {
2024-05-03 13:24:08 +02:00
b . Bytes ( "gin_body" , bin )
2023-08-14 15:36:12 +02:00
} else {
2024-05-03 13:24:08 +02:00
b . Str ( "gin_body" , fmt . Sprintf ( "[[%v bytes | %s]]" , len ( bin ) , req . Header . Get ( "Content-Type" ) ) )
2023-02-15 16:04:19 +01:00
}
}
}
}
2023-08-14 15:36:12 +02:00
2023-02-15 16:04:19 +01:00
}
2024-05-03 15:28:53 +02:00
pkgconfig . ExtendGinMeta ( ctx , b , g , req )
2023-02-15 16:04:19 +01:00
b . containsGinData = true
return b
}
2024-05-03 15:28:53 +02:00
func ( b * Builder ) CtxData ( method Method , ctx context . Context ) * Builder {
pkgconfig . ExtendContextMeta ( b , method , ctx )
b . containsContextData = true
return b
}
2023-08-09 19:51:41 +02:00
func extractHeader ( header map [ string ] [ ] string ) [ ] string {
r := make ( [ ] string , 0 , len ( header ) )
for k , v := range header {
for _ , hval := range v {
value := hval
value = strings . ReplaceAll ( value , "\n" , "\\n" )
value = strings . ReplaceAll ( value , "\r" , "\\r" )
value = strings . ReplaceAll ( value , "\t" , "\\t" )
r = append ( r , k + ": " + value )
}
}
return r
}
2023-02-15 16:04:19 +01:00
// ----------------------------------------------------------------------------
2024-07-27 23:44:18 +02:00
// Extra adds additional data to the error
// this is not like the other metadata (like Id(), Str(), etc)
// this data is public and will be printed/outputted
func ( b * Builder ) Extra ( key string , val any ) * Builder {
b . errorData . Extra [ key ] = val
return b
}
// ----------------------------------------------------------------------------
2023-02-15 16:04:19 +01:00
// Build creates a new error, ready to pass up the stack
// If the errors is not SevWarn or SevInfo it gets also logged (in short form, without stacktrace) onto stdout
2023-11-14 16:00:14 +01:00
// Can be gloablly configured with ZeroLogErrTraces and ZeroLogAllTraces
// Can be locally suppressed with Builder.NoLog()
2024-05-03 15:28:53 +02:00
func ( b * Builder ) Build ( ctxs ... context . Context ) error {
2023-07-24 10:42:39 +02:00
warnOnPkgConfigNotInitialized ( )
2024-05-03 15:28:53 +02:00
for _ , dctx := range ctxs {
b . CtxData ( MethodBuild , dctx )
}
2024-01-13 14:10:25 +01:00
if pkgconfig . DisableErrorWrapping && b . wrappedErr != nil {
return b . wrappedErr
}
2023-11-14 16:00:14 +01:00
if pkgconfig . ZeroLogErrTraces && ! b . noLog && ( b . errorData . Severity == SevErr || b . errorData . Severity == SevFatal ) {
2024-06-14 23:18:58 +02:00
b . errorData . ShortLog ( pkgconfig . ZeroLogger . Error ( ) )
2023-11-14 16:00:14 +01:00
} else if pkgconfig . ZeroLogAllTraces && ! b . noLog {
2024-06-14 23:18:58 +02:00
b . errorData . ShortLog ( pkgconfig . ZeroLogger . Error ( ) )
2023-02-15 16:04:19 +01:00
}
2024-01-07 04:01:13 +01:00
b . errorData . CallListener ( MethodBuild )
2023-02-15 16:04:19 +01:00
2023-07-24 10:42:39 +02:00
return b . errorData
2023-02-15 16:04:19 +01:00
}
// Output prints the error onto the gin stdout.
// The error also gets printed to stdout/stderr
// If the error is SevErr|SevFatal we also send it to the error-service
func ( b * Builder ) Output ( ctx context . Context , g * gin . Context ) {
if ! b . containsGinData && g . Request != nil {
// Auto-Add gin metadata if the caller hasn't already done it
b . GinReq ( ctx , g , g . Request )
}
2024-05-03 15:28:53 +02:00
b . CtxData ( MethodOutput , ctx )
2023-07-24 11:11:15 +02:00
b . errorData . Output ( g )
2023-02-15 16:04:19 +01:00
2024-04-15 10:25:30 +02:00
if ( b . errorData . Severity == SevErr || b . errorData . Severity == SevFatal ) && ( pkgconfig . ZeroLogErrGinOutput || pkgconfig . ZeroLogAllGinOutput ) {
2024-06-14 23:18:58 +02:00
b . errorData . Log ( pkgconfig . ZeroLogger . Error ( ) )
2024-04-15 10:25:30 +02:00
} else if ( b . errorData . Severity == SevWarn ) && ( pkgconfig . ZeroLogAllGinOutput ) {
2024-06-14 23:18:58 +02:00
b . errorData . Log ( pkgconfig . ZeroLogger . Warn ( ) )
2023-02-15 16:04:19 +01:00
}
2024-01-07 04:01:13 +01:00
b . errorData . CallListener ( MethodOutput )
2023-02-15 16:04:19 +01:00
}
// Print prints the error
// If the error is SevErr we also send it to the error-service
2024-05-03 15:28:53 +02:00
func ( b * Builder ) Print ( ctxs ... context . Context ) {
warnOnPkgConfigNotInitialized ( )
for _ , dctx := range ctxs {
b . CtxData ( MethodPrint , dctx )
}
2023-07-24 10:42:39 +02:00
if b . errorData . Severity == SevErr || b . errorData . Severity == SevFatal {
2024-06-14 23:18:58 +02:00
b . errorData . Log ( pkgconfig . ZeroLogger . Error ( ) )
2023-07-24 10:42:39 +02:00
} else if b . errorData . Severity == SevWarn {
2024-06-14 23:18:58 +02:00
b . errorData . ShortLog ( pkgconfig . ZeroLogger . Warn ( ) )
2024-07-27 23:44:18 +02:00
} else if b . errorData . Severity == SevInfo {
b . errorData . ShortLog ( pkgconfig . ZeroLogger . Info ( ) )
} else {
b . errorData . ShortLog ( pkgconfig . ZeroLogger . Debug ( ) )
2023-02-15 16:04:19 +01:00
}
2024-01-07 04:01:13 +01:00
b . errorData . CallListener ( MethodPrint )
2023-02-15 16:04:19 +01:00
}
func ( b * Builder ) Format ( level LogPrintLevel ) string {
2023-07-24 10:42:39 +02:00
return b . errorData . FormatLog ( level )
2023-02-15 16:04:19 +01:00
}
// Fatal prints the error and terminates the program
// If the error is SevErr we also send it to the error-service
2024-05-03 15:28:53 +02:00
func ( b * Builder ) Fatal ( ctxs ... context . Context ) {
2024-06-03 13:48:30 +02:00
b . errorData . Severity = SevFatal
2024-05-03 15:28:53 +02:00
for _ , dctx := range ctxs {
b . CtxData ( MethodFatal , dctx )
}
2024-06-14 23:18:58 +02:00
b . errorData . Log ( pkgconfig . ZeroLogger . WithLevel ( zerolog . FatalLevel ) )
2023-02-15 16:04:19 +01:00
2024-01-07 04:01:13 +01:00
b . errorData . CallListener ( MethodFatal )
2023-02-15 16:04:19 +01:00
os . Exit ( 1 )
}
// ----------------------------------------------------------------------------
func ( b * Builder ) addMeta ( key string , mdtype metaDataType , val interface { } ) * Builder {
2023-07-24 10:42:39 +02:00
b . errorData . Meta . add ( key , mdtype , val )
2023-02-15 16:04:19 +01:00
return b
}