This package provides an interface for pushing panics and arbitrary errors into error logging systems:
type Notifier interface {
Notify(interface{}, *http.Request) error
}
The second parameter *http.Request
is optional, and will allow the
notifier to provide added context/metadata from the request in a
notification.
There are 4 implementations of the interface:
- Push to Airbrake
- Print to appkit logger
- Buffering notifier (for testing)
- Failing notifier (for testing)
The package provides AirbrakeConfig
:
type AirbrakeConfig struct {
ProjectID int64
Token string
Environment string `default:"dev"`
}
Include a field of type errornotifier.AirbrakeConfig
in your app
config:
type AppConfig struct {
// ...
Airbrake errnotifier.AirbrakeConfig
}
And set appropriate values for this struct as part of your application configuration.
Add filters to appConfig.Airbrake:
appConfig.Airbrake.Filters = []interface{}{
regexp.MustCompile("secret"),
"context canceled",
}
Or use os.Setenv("AIRBRAKE_FILTERS", "['operation was canceled', 'context canceled']")
Create an Airbrake error notifier:
if notifier, err := errornotifier.NewAirbrakeNotifier(appConfig.Airbrake); err != nil {
// handle the error
}
Then use the returned Notifier
as described below.
Use NewLogNotifier
to construct your logging notifier:
notifier := errornotifier.NewLogNotifier(logger)
Then use the returned Notifier
as described below.
Create an errornotifier/utils.BufferNotifier
notifier := utils.BufferNotifier{}
Any calls to errornotifier.Notifier.Notify
will be stored in
notifier.Notices
. Use it as a Notifier
as described below.
Inside a Go test, create a errornotifier/utils.TestNotifier
with the
received *testing.T
:
func TestStuff(t *testing.T) {
notifier := utils.TestNotifier{t}
}
Any calls to notifier.Notify
will trigger a test failure via
testing.T.Fatal
. Use it as a Notifier
as described below.
Send error notifications via errornotifier.Notifier.Notify
:
func handler(w http.ResponseWriter, r *http.Request) {
var notifier errornotifier.Notifier = // ...
err := doWork()
if err != nil {
notifier.Notify(err, r)
}
}
The package provides a middleware that sends any panic
s in
downstream HTTP handlers to the notifier. Wrap your existing handler
using the middleware:
var notifier errornotifier.Notifier = // ...
notifierMiddleware := errornotifier.Recover(notifier)
return notifierMiddleware(handler)
This middleware will also make the notifier available to the HTTP
handler via the request's context, using errornotifier.ForceContext
:
func handler(w http.ResponseWriter, r *http.Request) {
notifier := errornotifier.ForceContext(r.Context())
notifier.Notify(..., r)
}
This will always return a valid notifier:
- Return notifier from request context, if any.
- If there is no notifier in the context, return a logging notifier
using
log.ForceContext
. This means that the logging notifier will use the requests logger, if present. Otherwise falling back tolog.Default()
.
errornotifier.NotifyOnPanic
is used to make using goroutines in HTTP
handlers a bit safer. It:
- Executes the passed function.
- Recovers a
panic
if necessary. - Notifies the passed notifier if a
panic
occurred. - Returns
nil
on no panic, or theerror
fromrecover
.
Goroutines by default are not recover
ed, so for example this will
crash a HTTP server (meaning program exit, not just aborting the
handler for a specific request), as the panic
reaches to the top
level of the goroutine's stack without being handled.
func handler(w http.ResponseWriter, r *http.Request) {
go func() {
panic(errors.New("error"))
}
}
to avoid this, and spawn goroutines without fear that an unhandled
panic
brings down the whole webserver, wrap the goroutine:
func handler(w http.ResponseWriter, r *http.Request) {
go errornotifier.NotifyOnPanic(
errornotifier.ForceContext(r.Context()),
r,
func() {
panic(errors.New("error"))
},
)
}