wrappederror/pkg.go

174 lines
3.4 KiB
Go
Raw Normal View History

2024-09-27 09:41:07 +02:00
/*
wrappederror provides traceable errors:
package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
// Standard
"encoding/json"
"errors"
"fmt"
"os"
)
func errorHandler(err werr.Error) {
// For example print or log error to file
fmt.Printf("\x1b[31;1m%s\x1b[0m\n", err)
fmt.Printf("\x1b[33;1m%s\x1b[0m\n", err.NoTrace())
j, _ := json.MarshalIndent(err, "", " ")
fmt.Printf("%s\n\n", j)
}
func main() {
// Make file paths relative to this file.
werr.Init()
// Handler to call when using Log().
werr.SetLogCallback(errorHandler)
// Wrap an existing error.
err := errors.New("foobar 1")
err1 := werr.Wrap(err)
// Create a new error with extra information.
err2 := werr.New("foobar 2").WithCode("FOO-100").WithData(137)
// The os error contains more information.
_, errOS := os.ReadFile("/tmp/does_not_exist")
werr.Wrap(errOS).Log()
// Log the previously wrapped errors.
werr.Wrap(err1).Log()
werr.Wrap(err2).Log()
}
*/
2023-09-26 08:34:09 +02:00
package WrappedError
import (
// Standard
"fmt"
"path"
"regexp"
2023-09-26 08:34:09 +02:00
"runtime"
)
type Error struct {
2023-09-26 09:33:46 +02:00
Wrapped error
ErrStr string // wrapped error isn't necessarily json encodable
2024-04-05 08:30:30 +02:00
Code string
2023-09-26 09:37:09 +02:00
File string
Line int
2024-04-05 08:30:30 +02:00
Data any
2024-03-29 11:14:01 +01:00
}
type CodableError interface {
error
WithCode(string) CodableError
WithData(any) CodableError
2024-04-05 08:30:30 +02:00
Log() CodableError
2023-09-26 08:34:09 +02:00
}
type LogCallback func(Error)
var (
2023-09-26 09:37:09 +02:00
logCallback LogCallback
2023-09-26 08:34:09 +02:00
baseDirLength int
)
2023-09-26 09:17:17 +02:00
// Init only works if called from the main package and sets the length of code base path
// to later be removed in file paths to receive relative paths.
2023-09-26 09:11:10 +02:00
func Init() {
_, file, _, _ := runtime.Caller(1)
2023-09-26 09:12:57 +02:00
dirBase := path.Dir(file)
2023-09-26 08:34:09 +02:00
baseDirLength = len(dirBase)
}
2023-09-26 09:17:17 +02:00
// SetLogCallback gives a possibility to automatically run code to handle any errors.
2023-09-26 08:34:09 +02:00
func SetLogCallback(cbk LogCallback) {
logCallback = cbk
}
func callback(wrapped Error) {
if logCallback != nil {
logCallback(wrapped)
}
}
2023-09-26 09:17:17 +02:00
// Error implements the error inteface and adds filename and line to the error.
2023-09-26 08:34:09 +02:00
func (wrapped Error) Error() string {
2024-03-29 11:14:01 +01:00
var code string
if wrapped.Code != "" {
2024-03-29 11:16:11 +01:00
code = wrapped.Code + ":"
2024-03-29 11:14:01 +01:00
}
2023-09-26 08:34:09 +02:00
return fmt.Sprintf(
2024-03-29 11:16:11 +01:00
"[%s%s:%d] %s",
2024-03-29 11:14:01 +01:00
code,
2023-09-26 09:31:18 +02:00
wrapped.File,
wrapped.Line,
2023-09-26 09:33:46 +02:00
wrapped.Wrapped.Error(),
2023-09-26 08:34:09 +02:00
)
}
2024-03-29 11:14:01 +01:00
func create(err error, data interface{}) *Error {
2023-09-27 07:32:50 +02:00
if err == nil {
return nil
}
2023-09-26 08:34:09 +02:00
_, file, line, _ := runtime.Caller(2)
file = file[baseDirLength+1:]
wrapped := Error{
2023-09-26 09:33:46 +02:00
Wrapped: err,
ErrStr: err.Error(),
2023-09-26 09:37:09 +02:00
File: file,
Line: line,
Data: data,
2023-09-26 08:34:09 +02:00
}
2024-03-29 11:14:01 +01:00
return &wrapped
2023-09-26 08:34:09 +02:00
}
2023-09-26 09:17:17 +02:00
// Wrap wraps an existing error with file and line.
2024-03-29 11:14:01 +01:00
func Wrap(err error) *Error {
return create(err, nil)
2023-09-26 09:31:18 +02:00
}
2023-09-26 09:17:17 +02:00
// New creates a new wrapped error with file and line.
2024-03-29 11:14:01 +01:00
func New(msg string, params ...any) *Error {
err := fmt.Errorf(msg, params...)
2024-03-29 11:14:01 +01:00
wrapped := create(err, nil)
2023-09-26 09:31:18 +02:00
return wrapped
}
// WithCode associates a string code with the error.
2024-03-29 11:14:01 +01:00
func (e *Error) WithCode(code string) CodableError {
e.Code = code
return e
}
// WithData associates any data with the error to make troubleshooting easier.
2024-03-29 11:14:01 +01:00
func (e *Error) WithData(data any) CodableError {
e.Data = data
return e
}
2024-04-05 08:30:30 +02:00
// Log calls the log callback.
2024-04-05 08:30:30 +02:00
func (e *Error) Log() CodableError {
callback(*e)
return e
}
// NoTrace returns the Error() string without the trace.
func (e *Error) NoTrace() string {
rxp := regexp.MustCompile(`^(\[[^\]]+\.go:\d+\]\s*)*`)
return string(
rxp.ReplaceAll(
[]byte(e.Error()),
[]byte(""),
),
)
}