Skip to content

Insights overview

View Markdown

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

Honeybadger records common Python activity automatically, including Django and Flask requests, ORM queries, ASGI traffic, and Celery tasks. From there, you can add context and custom events from your own code, then 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.

Set insights_enabled=True in your config and the package starts recording events as soon as your app boots.

Automatic events power built-in dashboards.

Context adds fields to the current thread. Once set, every event emitted from that thread carries them.

Say the app is A/B testing a new checkout flow against the control. Each checkout request sets context like this:

Set the variant on context
honeybadger.set_event_context(checkout_variant=checkout_variant)

The checkout_variant field is now on every database event for that request. You can group by it like any other field.

Database work by checkout variant
filter event_type::str == "db.query" and isNotNull(checkout_variant::str)
| stats
count() as queries,
avg(duration::float) as avg_ms
by checkout_variant::str
| sort queries desc
queriesavg_mscheckout_variant
268152.48new
118732.21control

The new variant ran more than twice as many queries with similar per-query time. Keep in mind that adding another A/B variant will extend any of the examples here without the need to change anything on the Honeybadger side.

Go deeper: check for possible N+1 queries

The package attaches a request_id to every event from the same request. To turn total database work into queries per request, group events by request_id first to get a per-request count, then aggregate by variant.

filter event_type::str == "db.query" and isNotNull(checkout_variant::str)
| stats count() as queries by request_id::str, checkout_variant::str
| stats
count() as request_count,
avg(queries) as avg_q,
percentile(95, queries) as p95_q
by checkout_variant
| sort p95_q desc
| only
toHumanString(request_count) as requests,
toHumanString(avg_q) as avg_queries,
toHumanString(p95_q) as p95_queries,
checkout_variant
requestsavg_queriesp95_queriescheckout_variant
63142.1897new
63818.6131control

The new variant runs more queries per request, and the p95 is much higher than control. That pattern often points at an N+1.

Custom events record activity the framework cannot see at all. Django knows a checkout request ran. Only your app knows whether the payment authorized:

Send a custom payment event
honeybadger.event("payment.authorized", {
"payment_provider": payment.provider,
"amount": checkout.total,
"currency": checkout.currency,
"authorization_id": payment.authorization_id,
})

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
Go deeper: more insights, same instrumentation
Conversion rate by variant
filter event_type::str == "payment.authorized" or view::str == "checkout_create"
| stats count(event_type::str == "payment.authorized") as auth_events
by request_id::str, checkout_variant::str
| stats
count() as auths,
count(auth_events > 0) as checkouts,
checkouts / auths as conv_rate
by checkout_variant::str
| only conv_rate, checkout_variant
conv_ratecheckout_variant
0.92new
0.86control
Revenue per payment provider per variant
filter event_type::str == "payment.authorized"
| stats sum(amount::float) as total
by payment_provider::str, checkout_variant::str
| sort total desc
| only toHumanString(total) as revenue, payment_provider, checkout_variant
revenuepayment_providercheckout_variant
34,108stripenew
32,167stripecontrol
18,722paypalnew
13,639paypalcontrol
Average checkout response time by variant
filter event_type::str == "django.request"
and view::str == "checkout_create"
| stats avg(duration::float) as avg_ms by checkout_variant::str
| only toHumanString(avg_ms, "milliseconds") as avg, checkout_variant
avgcheckout_variant
142msnew
78mscontrol
Conversion rate over time, by variant
filter event_type::str == "payment.authorized" or view::str == "checkout_create"
| stats
count(event_type::str == "payment.authorized") as auth_events,
min(@ts) as request_ts
by request_id::str, checkout_variant::str
| stats count(auth_events > 0) / count() as conv_rate
by checkout_variant::str, bin(1h, request_ts) as hour
| sort hour asc
conv_ratecheckout_varianthour
0.93new2026-06-26 14:00:00
0.86control2026-06-26 14:00:00
0.92new2026-06-26 15:00:00
0.86control2026-06-26 15:00:00
0.91new2026-06-26 16:00:00
0.87control2026-06-26 16:00:00

The new variant holds a consistent lead over control across the rollout window.