Skip to content

Insights overview

View Markdown

Insights lets you observe what your Go application does in production.

The Honeybadger Go package ships handlers for the standard slog package and for zerolog. Wire one in and every log line becomes a structured event in Insights, fields and all. From there, you can attach per-request fields to a derived logger, send custom events for moments that don’t fit a log shape, and use BadgerQL to ask questions across the whole event stream. Any field you send is queryable as soon as it arrives, with no schema to define ahead of time.

Construct an slog logger backed by the Honeybadger handler. The handler accepts a custom event type, which is the BadgerQL event_type field you will filter on later:

Build an Insights logger
import (
"log/slog"
"github.com/honeybadger-io/honeybadger-go"
hbslog "github.com/honeybadger-io/honeybadger-go/slog"
)
hbClient := honeybadger.New(honeybadger.Configuration{APIKey: "..."})
insightsLogger := slog.New(
hbslog.New(hbClient).WithEventType("http_request"),
).With("service", "checkouts", "commit", commit)

service and commit ride on every event from this logger, so you can filter by service across a fleet or split metrics by release.

Keep this logger separate from your application’s main slog logger. Stdout and Insights serve different purposes, and routing every log line through Honeybadger inflates your event volume with noise. Stash the request-scoped derivative on context (below) so handlers emit through it deliberately.

slog’s .With() returns a new logger with extra attributes attached to every subsequent log call. Use that to derive a request-scoped logger inside HTTP middleware. Attach only attributes that make sense for every request here, typically the request ID:

Per-request logger in middleware
func InsightsMiddleware(insightsLogger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &responseWriter{ResponseWriter: w, status: 200}
requestLogger := insightsLogger.With(
"request_id", r.Header.Get("X-Request-Id"),
)
r = r.WithContext(context.WithValue(r.Context(), loggerKey, requestLogger))
next.ServeHTTP(wrapped, r)
requestLogger.LogAttrs(r.Context(), slog.LevelInfo, "request",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.Int("status", wrapped.status),
slog.Int64("duration", time.Since(start).Microseconds()),
)
})
}
}

Every request now produces one http_request event with method, path, status, duration (microseconds), and request_id. Slowest endpoints by p95:

Slowest endpoints
filter event_type::str == "http_request"
| stats percentile(95, duration::float) as p95_us by path::str
| sort p95_us desc
| limit 5
| only path, toHumanString(p95_us, "microseconds") as p95
pathp95
/checkouts/authorize412ms
/reports/generate287ms
/search138ms
/accounts/upgrade96ms
/users/me41ms

For application events recorded from inside a handler (a payment authorized, a subscription upgrading, a feature toggle flipping), emit them through a further-derived logger pulled from context. Handler-specific attributes attached via .With() ride along with request_id and anything else the middleware put on the request logger:

Send a custom payment event
func authorizeCheckout(w http.ResponseWriter, r *http.Request) {
logger := r.Context().Value(loggerKey).(*slog.Logger).
With("checkout_variant", r.URL.Query().Get("variant"))
// ...
logger.LogAttrs(r.Context(), slog.LevelInfo, "payment authorized",
slog.String("event_type", "payment.authorized"),
slog.String("payment_provider", payment.Provider),
slog.Float64("amount", checkout.Total),
slog.String("currency", checkout.Currency),
slog.String("authorization_id", payment.AuthorizationID),
)
}

This query breaks down the amounts collected by variant and provider:

Payments by variant and provider
filter event_type::str == "payment.authorized"
| stats
count() as authorizations,
sum(amount::float) as authorized_amount
by checkout_variant::str, payment_provider::str
| sort authorized_amount desc
authorizationsauthorized_amountcheckout_variantpayment_provider
41334108.00newstripe
21818722.00newpaypal
41832167.00controlstripe
22013639.00controlpaypal