Skip to content

Golang structured logging (slog) handler middleware for bugsnag

License

Notifications You must be signed in to change notification settings

veqryn/slog-bugsnag

Repository files navigation

slog-bugsnag

tag Go Version GoDoc Build Status Go report Coverage Contributors License

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.

Other Great SLOG Utilities

  • 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.

Install

go get github.com/veqryn/slog-bugsnag

import (
	slogbugsnag "github.com/veqryn/slog-bugsnag"
)

Usage

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.
}

slog-multi Middleware

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{})),
))