---
title: Insights overview
description: Stream Go application logs and custom events into Honeybadger Insights, then query everything with BadgerQL.
url: https://docs.honeybadger.io/lib/go/insights/
---

[Insights](https://docs.honeybadger.io/guides/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](https://docs.honeybadger.io/guides/insights/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.

## Wire up structured logging

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

```go
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.

[Capturing logs](https://docs.honeybadger.io/lib/go/insights/capturing-logs/)slog and zerolog setup options.

[Sending custom events](https://docs.honeybadger.io/lib/go/insights/sending-events/)The full honeybadger.Event API.

## Add per-request context

`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

```go
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

```badgerql
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
```

| path                 | p95   |
| -------------------- | ----- |
| /checkouts/authorize | 412ms |
| /reports/generate    | 287ms |
| /search              | 138ms |
| /accounts/upgrade    | 96ms  |
| /users/me            | 41ms  |

## Record application events

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

```go
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

```badgerql
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
```

| authorizations | authorized\_amount | checkout\_variant | payment\_provider |
| -------------- | ------------------ | ----------------- | ----------------- |
| 413            | 34108.00           | new               | stripe            |
| 218            | 18722.00           | new               | paypal            |
| 418            | 32167.00           | control           | stripe            |
| 220            | 13639.00           | control           | paypal            |

---

## Try Honeybadger for FREE

Intelligent logging, error tracking, and Just Enough APM™ in one dev-friendly platform. Find and fix problems before users notice.

[Start free trial](https://app.honeybadger.io/users/sign_up)

[See plans and pricing](https://www.honeybadger.io/plans/)
