Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ prometheus-client 0.26.0, structlog 26.1.0. In-process, baseline 12.1 µs per re
| OTel 0% + OTel HTTP metrics | +73.6 | +12.9 over OTel 0%, second run |
| OTel 0% + Prometheus | +76.5 | +15.8 over OTel 0%, second run |
| OTel 0% + OTel HTTP metrics + Prometheus | +90.2 | +29.5 over OTel 0%, second run |
| Sentry, lite-bootstrap defaults (tracing off) | +70.9 | LitestarIntegration |
| Sentry, lite-bootstrap 1.x defaults (tracing off) | +70.9 | LitestarIntegration |
| Sentry, `sentry_auto_session_tracking=False` | +63.7 | sessions cost 7.2 |
| Sentry, `auto_enabling_integrations=False` | -0.1 | LitestarIntegration is auto-enabling, so nothing captures request errors |
| Sentry, `sentry_default_integrations=False` | -0.2 | same reason |
Expand All @@ -386,6 +386,21 @@ prometheus-client 0.26.0, structlog 26.1.0. In-process, baseline 12.1 µs per re
The "second run" rows come from a noisier re-check (spread between rounds up to 67% per row). The
first run's OTel 0% row is +56.8, the re-check's +60.7, and the deltas in the notes are against the
re-check.
Since 2.0, lite-bootstrap reports Litestar request errors itself while Sentry tracing is off, without
`LitestarIntegration`. Rerun in-process on the M2 host (macOS, CPython 3.14.7, litestar 2.25.0,
`bench.py litestar bare,sentry,sentry_sampled_trace,sentry_integration,sentry_integration_sampled_trace --repeat 2`):

| scenario | sentry-sdk 2.73.0 | 3.0.0b2 |
|---|---:|---:|
| `sentry`: lean capture, the 2.0 default | +7.3 | +8.3 |
| `sentry_sampled_trace`: the same, request carries a sampled `sentry-trace` | +9.7 | +10.1 |
| `sentry_integration`: `sentry_lean_error_capture=False` | +70.7 | +59.5 |
| `sentry_integration_sampled_trace` | +74.4 | +147.4 |

On 3.0 an incoming sampled `sentry-trace` makes `LitestarIntegration` record a real span even with
tracing off. The table above was measured before 2.0. Its `sentry_nosess` scenario now has the same
config as `sentry`, because sessions are off by default since 2.0.

lite-bootstrap's Litestar defaults with no endpoint configured (Prometheus, health, logging and
Swagger on) cost +11.7 µs on `/ping`, all of it Prometheus. The access-log middleware writes each record
twice because Litestar's own stdlib logging config is applied at startup next to lite-bootstrap's.
Expand All @@ -407,8 +422,8 @@ prometheus-client 0.26.0, structlog 26.1.0. Baseline 33350 RPS, 30.0 µs per req
| OpenTelemetry, 10%, exclude receive/send | 11258 | +58.8 |
| Prometheus | 18823 | +23.1 |
| Litestar access log on | 6879 | +115.4 |
| Sentry, defaults | 8119 | +93.2 |
| Sentry, sessions off | 9184 | +78.9 |
| Sentry, 1.x defaults | 8119 | +93.2 |
| Sentry, 1.x defaults, sessions off | 9184 | +78.9 |
| CORS (ab sends no Origin) | 30942 | +2.3 |
| health check | 28337 | +5.3 |
| health check with OpenTelemetry spans | 4600 | +187.4 |
Expand Down
6 changes: 6 additions & 0 deletions benchmarks/frameworks/litestar_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
HEALTH: typing.Final = {"health_checks_enabled": True}
LITESTAR_DEFAULTS: typing.Final = {**PROM, **HEALTH, **LOG, "swagger_path": "/docs"}
FULL: typing.Final = {**OTEL, **PROM, **LOG, **sentry(), **HEALTH}
SAMPLED_SENTRY_TRACE: typing.Final = ((b"sentry-trace", b"771a43a4192642f0b136d5159a501700-1234567890abcdef-1"),)

SCENARIOS: typing.Final = {
"bare": Scenario(config=base_config()),
Expand All @@ -63,6 +64,11 @@
"sentry_noauto": Scenario(config=base_config(**sentry(auto_enabling_integrations=False))),
"sentry_nodefault": Scenario(config=base_config(**sentry(), sentry_default_integrations=False)),
"sentry_nointegration": Scenario(config=base_config(**sentry(disabled_integrations=[LitestarIntegration]))),
"sentry_integration": Scenario(config=base_config(**sentry(), sentry_lean_error_capture=False)),
"sentry_sampled_trace": Scenario(config=base_config(**sentry()), headers=SAMPLED_SENTRY_TRACE),
"sentry_integration_sampled_trace": Scenario(
config=base_config(**sentry(), sentry_lean_error_capture=False), headers=SAMPLED_SENTRY_TRACE
),
"cors_noorigin": Scenario(config=base_config(**CORS)),
"cors": Scenario(config=base_config(**CORS), headers=ORIGIN),
"bare_origin": Scenario(config=base_config(), headers=ORIGIN),
Expand Down
47 changes: 47 additions & 0 deletions docs/integrations/litestar.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,53 @@ A bare `LoggingMiddlewareConfig()` restores all of Litestar's own defaults,
including full request/response body logging, so pass explicit
`request_log_fields` / `response_log_fields`.

## Sentry

While Sentry tracing is off, lite-bootstrap reports request errors itself instead of through
sentry-sdk's `LitestarIntegration`. Tracing counts as off when `sentry_traces_sample_rate` is unset
and `sentry_additional_params` sets none of `traces_sample_rate`, `traces_sampler` and
`enable_tracing`. With tracing off, the integration still builds a transaction and a span per
middleware for every request and then discards them
([getsentry/sentry-python#7400](https://github.com/getsentry/sentry-python/issues/7400)). On
sentry-sdk 3.0, an incoming `sentry-trace` header marked as sampled makes it record a real span.

`sentry_lean_error_capture` picks the path:

- `None`, the default: lean capture while tracing is off and `sentry_integrations` holds no
`LitestarIntegration`, the integration otherwise.
- `True`: lean capture always. lite-bootstrap warns when tracing is on, since no request starts a
transaction, and when `sentry_integrations` holds a `LitestarIntegration`, which gets dropped.
- `False`: the integration always.

Lean capture passes `LitestarIntegration` in `disabled_integrations` and adds a middleware and an
`after_exception` hook. The middleware gives each request its own Sentry scope, and its own
release-health session when `sentry_auto_session_tracking` is on. It continues an incoming
`sentry-trace` and `baggage`, and attaches the request to every event sent while the request runs.

The request data follows the same [`data_collection`](../introduction/configuration.md#sentry-data-collection)
settings the integration reads. Method, URL, query string, headers and cookies are filtered by
their denylists. The client address and `scope["user"]` are attached only when `user_info` is on.
A body is attached only when `http_bodies` includes `"incoming_request"` and the handler has
already read it, because lean capture never reads the stream itself. The integration reads every
non-GET body before the handler runs.

An exception is reported only when its response is a 5xx, so one that your `exception_handlers`
answer with a 404 is not reported. The integration reports every exception except an
`HTTPException` below 500. Litestar logs the exceptions it handles, and lean capture keeps the
`litestar` logger from turning them into a second event, as the integration does.

Measured in-process on a do-nothing handler, over the same app without Sentry:

| Sentry setup | sentry-sdk 2.73.0 | 3.0.0b2 |
|---|---:|---:|
| lean capture, the default | +7.3 µs | +8.3 µs |
| `sentry_lean_error_capture=False` | +70.7 µs | +59.5 µs |
| the same, request carries a sampled `sentry-trace` | +74.4 µs | +147.4 µs |

sentry-sdk records installed integrations for the whole process, and `disabled_integrations`
cannot remove one that an earlier `sentry_sdk.init` installed. If that happened, lite-bootstrap
warns that errors may be reported twice.

## Prometheus

`prometheus_group_path` defaults to `True`, so the `path` metric label uses the
Expand Down
1 change: 1 addition & 0 deletions docs/introduction/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Additional parameters can also be supplied through the settings object:
- `sentry_additional_params` - additional params, which will be passed to `sentry_sdk.init`, overriding any of the above that map to the same keyword
- `sentry_default_integrations` - whether to use sentry's default integrations (default: `True`)
- `sentry_before_send` - optional callback chained after the built-in structlog enricher, passed to `sentry_sdk.init(before_send=...)`
- `sentry_lean_error_capture` - Litestar only: report request errors without Sentry's `LitestarIntegration` (default: `None`, meaning on while Sentry tracing is off). See [Litestar](../integrations/litestar.md#sentry).

Read more about sentry_sdk params [here](https://docs.sentry.io/platforms/python/configuration/options/).

Expand Down
10 changes: 6 additions & 4 deletions docs/introduction/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ The same app on Granian, over real sockets:

--8<-- "benchmarks/README.md:litestargranian"

**Sentry costs more on Litestar than on FastAPI**, and all of it is `LitestarIntegration`: turning
off auto-enabling integrations removes the cost and, with it, request error capture. The access log
([`logging_access_log_enabled`](../integrations/litestar.md#logging)) is the other large opt-in
item, at about 70 µs per request in-process.
**Before 2.0, Sentry cost more on Litestar than on FastAPI**, and all of it was `LitestarIntegration`.
Since 2.0, lite-bootstrap reports Litestar request errors itself while Sentry tracing is off, which
brings Sentry from about +71 to about +7 µs per request in-process
([Litestar: Sentry](../integrations/litestar.md#sentry)). The access log
([`logging_access_log_enabled`](../integrations/litestar.md#logging)) is the largest opt-in item
left, at about 70 µs per request in-process.

## FastStream

Expand Down
Loading
Loading