mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(logging); improve messages and errors (#336)
* feat(logger): add module context * feat(logger): change errors package * feat(logger): update tests
This commit is contained in:
parent
95471a4cf7
commit
0e88117702
69 changed files with 1172 additions and 957 deletions
171
pkg/errors/errors.go
Normal file
171
pkg/errors/errors.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Export a number of functions or variables from pkg/errors. We want people to be able to
|
||||
// use them, if only via the entrypoints we've vetted in this file.
|
||||
var (
|
||||
As = errors.As
|
||||
Is = errors.Is
|
||||
Cause = errors.Cause
|
||||
Unwrap = errors.Unwrap
|
||||
)
|
||||
|
||||
// StackTrace should be aliases rather than newtype'd, so it can work with any of the
|
||||
// functions we export from pkg/errors.
|
||||
type StackTrace = errors.StackTrace
|
||||
|
||||
type StackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
|
||||
// Sentinel is used to create compile-time errors that are intended to be value only, with
|
||||
// no associated stack trace.
|
||||
func Sentinel(msg string, args ...interface{}) error {
|
||||
return fmt.Errorf(msg, args...)
|
||||
}
|
||||
|
||||
// New acts as pkg/errors.New does, producing a stack traced error, but supports
|
||||
// interpolating of message parameters. Use this when you want the stack trace to start at
|
||||
// the place you create the error.
|
||||
func New(msg string, args ...interface{}) error {
|
||||
return PopStack(errors.New(fmt.Sprintf(msg, args...)))
|
||||
}
|
||||
|
||||
// Wrap creates a new error from a cause, decorating the original error message with a
|
||||
// prefix.
|
||||
//
|
||||
// It differs from the pkg/errors Wrap/Wrapf by idempotently creating a stack trace,
|
||||
// meaning we won't create another stack trace when there is already a stack trace present
|
||||
// that matches our current program position.
|
||||
func Wrap(cause error, msg string, args ...interface{}) error {
|
||||
causeStackTracer := new(StackTracer)
|
||||
if errors.As(cause, causeStackTracer) {
|
||||
// If our cause has set a stack trace, and that trace is a child of our own function
|
||||
// as inferred by prefix matching our current program counter stack, then we only want
|
||||
// to decorate the error message rather than add a redundant stack trace.
|
||||
if ancestorOfCause(callers(1), (*causeStackTracer).StackTrace()) {
|
||||
return errors.WithMessagef(cause, msg, args...) // no stack added, no pop required
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we can't see a stack trace that represents ourselves, so let's add one.
|
||||
return PopStack(errors.Wrapf(cause, msg, args...))
|
||||
}
|
||||
|
||||
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
|
||||
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
|
||||
// should imply the error was generated directly from our goroutine.
|
||||
func ancestorOfCause(ourStack []uintptr, causeStack errors.StackTrace) bool {
|
||||
// Stack traces are ordered such that the deepest frame is first. We'll want to check
|
||||
// for prefix matching in reverse.
|
||||
//
|
||||
// As an example, imagine we have a prefix-matching stack for ourselves:
|
||||
// [
|
||||
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync",
|
||||
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite",
|
||||
// "testing.tRunner",
|
||||
// "runtime.goexit"
|
||||
// ]
|
||||
//
|
||||
// We'll want to compare this against an error cause that will have happened further
|
||||
// down the stack. An example stack trace from such an error might be:
|
||||
// [
|
||||
// "github.com/incident-io/core/server/pkg/errors.New",
|
||||
// "github.com/incident-io/core/server/pkg/errors_test.glob..func1.2.2.2.1",,
|
||||
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync",
|
||||
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite",
|
||||
// "testing.tRunner",
|
||||
// "runtime.goexit"
|
||||
// ]
|
||||
//
|
||||
// They prefix match, but we'll have to handle the match carefully as we need to match
|
||||
// from back to forward.
|
||||
|
||||
// We can't possibly prefix match if our stack is larger than the cause stack.
|
||||
if len(ourStack) > len(causeStack) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We know the sizes are compatible, so compare program counters from back to front.
|
||||
for idx := 0; idx < len(ourStack); idx++ {
|
||||
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All comparisons checked out, these stacks match
|
||||
return true
|
||||
}
|
||||
|
||||
func callers(skip int) []uintptr {
|
||||
pc := make([]uintptr, 32) // assume we'll have at most 32 frames
|
||||
n := runtime.Callers(skip+3, pc) // capture those frames, skipping runtime.Callers, ourself and the calling function
|
||||
|
||||
return pc[:n] // return everything that we captured
|
||||
}
|
||||
|
||||
// RecoverPanic turns a panic into an error, adjusting the stacktrace so it originates at
|
||||
// the line that caused it.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Do() (err error) {
|
||||
// defer func() {
|
||||
// errors.RecoverPanic(recover(), &err)
|
||||
// }()
|
||||
// }
|
||||
func RecoverPanic(r interface{}, errPtr *error) {
|
||||
var err error
|
||||
if r != nil {
|
||||
if panicErr, ok := r.(error); ok {
|
||||
err = errors.Wrap(panicErr, "caught panic")
|
||||
} else {
|
||||
err = errors.New(fmt.Sprintf("caught panic: %v", r))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Pop twice: once for the errors package, then again for the defer function we must
|
||||
// run this under. We want the stacktrace to originate at the source of the panic, not
|
||||
// in the infrastructure that catches it.
|
||||
err = PopStack(err) // errors.go
|
||||
err = PopStack(err) // defer
|
||||
|
||||
*errPtr = err
|
||||
}
|
||||
}
|
||||
|
||||
// PopStack removes the top of the stack from an errors stack trace.
|
||||
func PopStack(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We want to remove us, the internal/errors.New function, from the error stack we just
|
||||
// produced. There's no official way of reaching into the error and adjusting this, as
|
||||
// the stack is stored as a private field on an unexported struct.
|
||||
//
|
||||
// This does some unsafe badness to adjust that field, which should not be repeated
|
||||
// anywhere else.
|
||||
stackField := reflect.ValueOf(err).Elem().FieldByName("stack")
|
||||
if stackField.IsZero() {
|
||||
return err
|
||||
}
|
||||
stackFieldPtr := (**[]uintptr)(unsafe.Pointer(stackField.UnsafeAddr()))
|
||||
|
||||
// Remove the first of the frames, dropping 'us' from the error stack trace.
|
||||
frames := (**stackFieldPtr)[1:]
|
||||
|
||||
// Assign to the internal stack field
|
||||
*stackFieldPtr = &frames
|
||||
|
||||
return err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue