Golang structured logging (slog) handler middleware for Bugsnag. Automatically send all Error level logs to Bugsnag, along with all attributes and context. Never forget to snag another bug again.
- slogctx: Add attributes to context and have them automatically added to all log lines. Work with a logger stored in context.
- slogotel: Automatically extract and add OpenTelemetry TraceID's to all log lines.
- slogdedup: Middleware that deduplicates and sorts attributes. Particularly useful for JSON logging. Format logs for aggregators (Graylog, GCP/Stackdriver, etc).
- slogbugsnag: Middleware that pipes Errors to Bugsnag.
- slogjson: Formatter that uses the JSON v2 library, with optional single-line pretty-printing.
go get github.com/veqryn/slog-bugsnag
import (
slogbugsnag "github.com/veqryn/slog-bugsnag"
)
package main
import (
"context"
"log/slog"
"net/http"
"os"
"github.com/bugsnag/bugsnag-go/v2"
"github.com/pkg/errors"
slogbugsnag "github.com/veqryn/slog-bugsnag"
)
func main() {
// Configure bugsnag
bugsnag.Configure(bugsnag.Configuration{
APIKey: os.Getenv("BUGSNAG_API_KEY"),
AppVersion: "0.1.0",
})
// Setup slog handlers
h := slogbugsnag.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil)
slog.SetDefault(slog.New(h))
// Optional: closing the handler will flush all bugs in the queue to bugsnag
defer h.Close()
slog.Info("starting up...") // Not sent to bugsnag
err := errors.New("my horrible error")
slog.Error("oh no...", "err", err) // Sent to bugsnag, with original err's stack trace
// Can be used with context too
ctx := bugsnag.StartSession(context.Background())
defer bugsnag.AutoNotify()
// Bugsnag can capture basic http.Request data, if added to the ctx
req, _ := http.NewRequest("GET", "https://www.github.com/veqryn/slog-bugsnag", nil)
ctx = bugsnag.AttachRequestData(ctx, req)
// User information can be sent as a bugsnag.User
user := bugsnag.User{Id: "1234", Name: "joe", Email: "none"}
// Or using these string types, added to the log as attribute values (any key is fine)
id := slogbugsnag.ID("1234")
name := slogbugsnag.Name("joe")
email := slogbugsnag.Email("none")
// Tabs will be created in bugsnag for each group,
// with the root level attributes going into a "log" tab.
log := slog.With(slog.Any("id", id), slog.Any("name", name), slog.Any("email", email))
log = log.WithGroup("MyTab")
// If no "error" type is found among log attribute values,
// then an error will be created using the log message, with a stack trace at the log call.
log.ErrorContext(ctx, "more bad things", slog.Any("user", user), slog.String("foo", "bar"))
// All of the above will log out 3 lines and send 2 bugs reports to bugsnag.
// The second bug will have tabs for Stacktrace, App, Device, Request, User, Log, and MyTab,
// containing all the data in the log record.
}
This library has a convenience method that allow it to interoperate with github.com/samber/slog-multi, in order to easily setup slog workflows such as pipelines, fanout, routing, failover, etc.
notifiers := slogbugsnag.NewNotifierWorkers(&slogbugsnag.NotifierOptions{})
defer notifiers.Close()
slog.SetDefault(slog.New(slogmulti.
Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})).
Pipe(slogdedup.NewOverwriteMiddleware(&slogdedup.OverwriteHandlerOptions{})).
Pipe(slogbugsnag.NewMiddleware(&slogbugsnag.HandlerOptions{Notifiers: notifiers})).
Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})),
))