Skip to content

Releases: modern-python/lite-bootstrap

1.10.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 19:03
1.10.0
6cb8322

OpenTelemetry metrics, opt-in. Until now this library bootstrapped one of OpenTelemetry's three
signals: it built a TracerProvider and no MeterProvider, so the FastAPI, Litestar and FastStream
instrumentations recorded their request-duration histograms against a no-op provider and the data
went nowhere.

Turning it on

config = FastAPIConfig(
    service_name="my-service",
    opentelemetry_endpoint="localhost:4317",
    opentelemetry_metrics_endpoint="localhost:4317",
)

opentelemetry_metrics_endpoint is a field of its own rather than a flag on
opentelemetry_endpoint, so upgrading changes nothing. Leave it unset and no MeterProvider is
constructed at all, exactly as before.

Set it and the instrumentations start recording against a real provider, exported over OTLP.
opentelemetry_exporter_protocol and opentelemetry_insecure are shared with traces, so under
"http" the value is a full URL such as http://collector:4318/v1/metrics. The export interval is
the SDK's own 60 seconds, overridable with OTEL_METRIC_EXPORT_INTERVAL.

One thing to watch

If you also run the Prometheus instrument, request metrics are now recorded twice: once for the
scrape endpoint, once for the OTLP pipeline, under different names. That is intended when the two
feed different backends. Pointing both at the same backend double-counts.

Two silent failures made audible

Both providers are set-once per process. If your application installs its own before
lite-bootstrap runs, the SDK refuses the call and logs a complaint to a logger lite-bootstrap has
just silenced, so nothing reported it. Meanwhile the provider lite-bootstrap built kept the
configured exporter, sampler and resource, was fed no telemetry, and held its export thread and
collector connection open until teardown.

Bootstrap now warns in that case, for the tracer provider and for the meter provider. Tracing and
metrics keep working through your provider, as before; the difference is that you are told the
configured exporter, sampler and resource are not the ones in use. See
#227.

The insecure-endpoint warning now covers the metrics endpoint as well, and its wording changed from
"sending traces unencrypted" to "sending telemetry unencrypted". If you match on that text, match
on unencrypted.

Full Changelog: 1.9.4...1.10.0

1.9.4

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:10
1.9.4
74e5da9

sentry-sdk 2.69.0 corrupts SigV4-signed request headers, which breaks AWS calls made through
aiobotocore. This release excludes that one version from the sentry extra.

Are you affected

You are if all three hold: you install a sentry extra (sentry, free-all, fastapi-sentry,
litestar-sentry, faststream-sentry, or any *-all), your environment resolved sentry-sdk to
exactly 2.69.0, and your service reaches AWS through aiobotocore or aioboto3.

The symptom is SignatureDoesNotMatch on AWS calls, but only while a Sentry transaction is open,
so it usually looks intermittent rather than total. Synchronous boto3 is unaffected on every
version.

One thing that widens the blast radius: sentry's Boto3Integration enables on botocore, not on
boto3, and both it and the aiohttp integration turn themselves on by default. Having aiobotocore
installed is enough for both to be active.

What went wrong

sentry-sdk 2.69.0 moved boto3 trace propagation into botocore's before-sign event, so
sentry-trace and baggage are set before SigV4 signing and end up listed in SignedHeaders. The
same change taught sentry's http.client integration to leave signed propagation headers alone,
but not its aiohttp integration, which still rewrote those headers after signing. The bytes sent
then no longer matched the bytes signed, and AWS rejected the request.

Upstream fixed it in 2.69.1 with
getsentry/sentry-python#7427, tracked as
getsentry/sentry-python#7426. 2.69.0 is
the only affected release.

What to do

Upgrading to 1.9.4 is enough. The resolver can no longer pick 2.69.0, and every other version
stays available: the declared floors are unchanged at >=2.1, >=2.11 on Python 3.13 and
>=2.59 on 3.14.

If something else pins you to 2.69.0, either of these works through sentry_additional_params:

  • trace_propagation_targets set to a pattern that excludes your AWS endpoints. This keeps spans
    everywhere and keeps trace propagation everywhere else. Available on every supported sentry-sdk.
  • disabled_integrations=[AioHttpIntegration()]. Heavier, because it drops all aiohttp client
    spans and propagation rather than only the AWS ones. Requires sentry-sdk 2.11 or newer, where
    the option was added.

Passing AioHttpIntegration() explicitly in sentry_integrations does not help. The explicit
instance installs the same client hook that causes the problem.

Also in this release

Housekeeping only, with no runtime effect: coverage pragmas now carry reasons
(#261,
#262) and the package keywords mirror
the GitHub topics (#260).

Full Changelog: 1.9.3...1.9.4

1.9.3

Choose a tag to compare

@github-actions github-actions released this 27 Sep 15:52
1.9.3
e13c5cb

What's Changed

  • ci: gate a release tag on a green dependency-floor run by @lesnik512 in #252
  • test: patch params_storage on the class so the teardown test survives faststream 0.7.7's slots by @lesnik512 in #256
  • ci: run the floors job on pull requests and drop the release floors gate by @lesnik512 in #257
  • chore: align with the org standard (TS1) by @lesnik512 in #258
  • fix(deps): declare the floors the extras require, and pin them before installing wheel-only by @lesnik512 in #259

Full Changelog: 1.9.2...1.9.3

1.9.2

Choose a tag to compare

@github-actions github-actions released this 20 Sep 17:35
1.9.2
e4c844e

Three fixes, all for failures that produced no error message. One affects every Litestar service
that turns health-check spans off. The other two only bite below the newest dependency versions, so
they hit anyone who pins, or who resolves near the declared floors. Sentry users on Python 3.13 or
3.14 also get a raised sentry-sdk floor, described at the bottom.

skip_sentry=True stopped suppressing events

logger.error("...", skip_sentry=True) is meant to keep a log line out of Sentry. Below a certain
sentry-sdk version it did nothing, and the event was sent anyway.

lite-bootstrap reads the log text back out of the Sentry event to decide. It read
event["logentry"]["formatted"], but that key is not universal: at sentry-sdk==2.1, which the
declared floor resolves to on Python 3.10 to 3.12, the text lives in logentry.message and there is
no formatted key at all.

sentry-sdk 2.1.0
logentry: {'message': '{"event": "hello", "skip_sentry": true}', 'params': []}

So the guard fell through, the payload was never parsed, and the line reached Sentry. Nothing raised
and nothing logged. It now reads whichever key the installed SDK filled, and writes the rewritten
message back to that same key so the enrichment lands where the SDK will render it.

Who is affected: services with sentry_dsn set, on a sentry-sdk old enough to use
logentry.message. The failure is silent and the symptom is data you meant to withhold arriving in
Sentry, so it is worth checking rather than waiting to notice.

Who is not: services on a recent sentry-sdk, where formatted is present and was already read.

Litestar with OpenTelemetry returned 500 on every request

At the declared OpenTelemetry floor, a Litestar service with tracing enabled failed every request.

OpenTelemetryMiddleware was handed the excluded URLs as a raw comma-joined string. It only learned
to parse a string itself in opentelemetry-instrumentation 0.56b0; the declared floor is
>=0.49b0. Below 0.56b0 the string reached self.excluded_urls.url_disabled(url) directly:

AttributeError: 'str' object has no attribute 'url_disabled'

raised inside the middleware on every request, for every route. The list is now parsed with
parse_excluded_urls before it is handed over, which both versions accept.

Who is affected: Litestar services with OpenTelemetry enabled on opentelemetry-instrumentation
below 0.56b0. Not conditional on configuration: the exclusion list is never empty, because the
Prometheus metrics path is always in it.

Who is not: FastAPI, FastStream and FastMCP, which never took this path, and any Litestar service
on 0.56b0 or later.

Health-check spans were recorded despite being switched off

opentelemetry_generate_health_check_spans=False did nothing on Litestar, at every dependency
version. The health check was traced on every poll, which for a Kubernetes liveness probe is a
steady stream of spans you asked not to have.

Litestar normalizes the trailing slash out of scope["path"], so the middleware built
http://host/health while the exclusion entry was the configured /health/. OpenTelemetry's
ExcludeList regex-searches, missed, and traced it. The default health_checks_path is /health/,
so this was the default configuration.

Simply stripping the slash would have been wrong in the other direction. ExcludeList.url_disabled
is an unanchored re.search, so a bare /health entry would have started silently excluding an
unrelated /healthy route from tracing. The derived paths are now anchored to the start of the URL's
path component, and admit sub-paths but not lookalikes. Entries you supply in
opentelemetry_excluded_urls are untouched, because OpenTelemetry documents those as regexes.

Who is affected: Litestar services with OpenTelemetry enabled and
opentelemetry_generate_health_check_spans=False, on the default health path or any path ending in
a slash. Also prometheus_metrics_path if you changed it to end in a slash; the default /metrics
does not, so it was already excluded correctly.

Who is not: FastAPI, because Starlette does not normalize the slash away and the entry matched as
written.

sentry-sdk floors raised for Python 3.13 and 3.14

sentry-sdk>=2.1 predates Python 3.13's FrameLocalsProxy, which the SDK fails to pickle while
capturing a request:

TypeError: cannot pickle 'FrameLocalsProxy' object

The floors were bisected against the suite, with checks either side of each boundary, and are now
declared per interpreter:

interpreter sentry-sdk floor
3.10 to 3.12 >=2.1, unchanged
3.13 >=2.11
3.14 >=2.59

Marked rather than raised outright, so Python 3.10 to 3.12 keep the lower floor. This is a metadata
change: it can move the version a fresh resolve picks for you on 3.13 and 3.14.

On how these were found

All three came out of a single thread: 1.9.1 fixed a crash that reached PyPI because the
lowest-direct CI job runs only on a schedule, never on pull requests. Pulling on that turned up the
rest. The floors job was dispatched by hand against this commit before the tag was cut, and was green
across all 25 cells, five interpreters by five install targets. Making that check automatic rather
than manual is still open as
#245.


What's Changed

  • test: drop FromPath so the litestar suite runs at its declared floor by @lesnik512 in #246
  • fix: parse excluded_urls before handing them to the ASGI instrumentor by @lesnik512 in #249
  • fix(sentry): read the logentry key the installed SDK fills, and mark its floor by @lesnik512 in #251
  • fix: anchor litestar trace-exclusion patterns to the normalized path by @lesnik512 in #250

Full Changelog: 1.9.1...1.9.2

1.9.1

Choose a tag to compare

@github-actions github-actions released this 20 Sep 14:18
1.9.1
88d3709

Fixes a crash in 1.8.0 and 1.9.0. If you use Sentry and are not on a recent sentry-sdk, those two
releases fail at startup. Upgrade straight to this one.

Sentry-enabled services crashed on sentry-sdk below 2.25

1.8.0 added sentry_logs_level=None to the LoggingIntegration that lite-bootstrap builds, which
stops sentry-sdk formatting every INFO+ log record and throwing the result away. That parameter
only exists from sentry-sdk 2.25.0. lite-bootstrap declares sentry-sdk>=2.1, so every service
in that range failed at bootstrap:

TypeError: LoggingIntegration.__init__() got an unexpected keyword argument 'sentry_logs_level'

It is now passed only to SDK versions that accept it, decided once at import. The floor stays at
>=2.1 rather than moving to >=2.25: below 2.25 there is no Sentry Logs feature, so there is no
handler to disable and nothing is lost by omitting the keyword. On 2.25 and later the behaviour is
unchanged and the saving still applies.

Who is affected: any service with sentry_dsn set and sentry-sdk between 2.1 and 2.24. It fails
at startup rather than silently, so this is visible rather than lurking.

Who is not: services on sentry-sdk 2.25 or later, and services without Sentry configured, behave
identically to 1.9.0.

How it escaped

lite-bootstrap runs a lowest-direct job that installs every declared floor and bootstraps against
it. scripts/floor_smoke.py does catch this. But that job runs only on the scheduled workflow, never
on pull requests, so the change that introduced the bug never exercised a floor. The scheduled check
moved from weekly to daily in 1.9.0, which would have surfaced it within a day.


Full Changelog: 1.9.0...1.9.1

1.9.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 13:51
1.9.0
5e96b5c

Breaking for FastMCP services. Read the first section before upgrading. FastAPI and Litestar
services are unaffected unless they opt in to something new.

FastMCP: the access log is now off, and a config field was removed

FastMCP installed FastMcpLoggingMiddleware by default, so every MCP message produced a log line.
It is now off unless you ask for it (#243). Litestar's equivalent has always been off by default and
FastAPI's arrives off in this release, so all three frameworks now behave the same way.

logging_turn_off_middleware has been removed. It is not deprecated, it is gone, and
FastMcpConfig is frozen, so setting it fails at construction:

TypeError: FastMcpConfig.__init__() got an unexpected keyword argument 'logging_turn_off_middleware'

To restore the previous behaviour:

FastMcpConfig(
    service_name="microservice",
    fastmcp_logging_middleware_enabled=True,
)

A compatibility shim was written and then dropped. The default flipped from on to off, so a shim
could not have been silent: it needed a tri-state field, a warning and a rule for setting both names.
A hard failure is the better trade here, because the change is that log output disappears. A service
that configured this setting has to decide again, rather than upgrade past the change without
noticing that the default moved for everything else in its codebase.

Who is affected: any FastMCP service. Those that set logging_turn_off_middleware fail loudly at
startup. Those that never touched it lose their per-message access log silently, which is the
intended change.

FastAPI gains a structured access log

FastAPI was the only supported framework whose logging instrument bound nothing framework-specific:
structlog was configured process-wide and no access log existed (#241). There is one now, off by
default
:

FastAPIConfig(
    service_name="microservice",
    fastapi_logging_middleware_enabled=True,
)

Enabled, it writes one http_request line per request to the http.access logger, with method,
path, content_type, path_params and status_code under http, plus duration in nanoseconds.
A request that raises is logged at exception level and the exception is re-raised unchanged.

Request and response bodies are never read or logged. path and path_params are, so a secret
in the URL itself is recorded; keep secrets in the body. swagger_path, swagger_static_path,
health_checks_path and prometheus_metrics_path are skipped.

It is off by default because uvicorn already writes an access line per request, so enabling it
without clearing uvicorn's handlers gives you two lines per request, and because an HTTP service is
the highest-volume place to add a log record. See
the FastAPI integration guide
for the logging_unset_handlers recipe.

Consistent naming

All three frameworks now spell the same setting the same way:

framework field
FastAPI fastapi_logging_middleware_enabled
FastMCP fastmcp_logging_middleware_enabled
Litestar litestar_logging_middleware_enabled

Internal

The lowest-direct CI job now records that it smoke-tests the dependency floors and type-checks
nothing there, so the gap is a decision rather than an omission (#239). No effect on the package.


What's Changed

  • docs(ci): record that the dependency floors are smoke-tested only by @lesnik512 in #239
  • feat: add an opt-in structured access log to FastAPI by @lesnik512 in #241
  • feat: make the FastMCP access log opt-in by @lesnik512 in #243

Full Changelog: 1.8.1...1.9.0

1.8.1

Choose a tag to compare

@github-actions github-actions released this 20 Sep 12:02
1.8.1
2a9fd06

A single user-facing fix, on FastStream. Everything else is benchmark tooling.

FastStream mounts /metrics for an injected registry

FastStreamConfig accepts prometheus_collector_registry, and the instrument prefers an injected
registry over the fresh one it would otherwise build. But the endpoint was only mounted when
prometheus_middleware_cls was also set, so a service that injected a registry holding its own
collectors and did not use the broker telemetry middleware got nothing to scrape them from. The
registry was accepted and did nothing, and the skip was silent, because config-level skips are
deliberately quiet (#238).

Providing either field is now enough. The rule is that the endpoint is mounted when something will
populate the registry: the broker middleware populates it, an injected registry arrives already
populated, and with neither there is nothing to report.

Who is affected: only FastStream services that inject prometheus_collector_registry without
prometheus_middleware_cls. Those gain a /metrics endpoint at prometheus_metrics_path that was
previously missing.

Who is not: services setting prometheus_middleware_cls behave exactly as before, and services
setting neither still mount nothing. No default changes, and no endpoint appears on a service that
did not ask for one.

Unlike the other frameworks, FastStream serves a private registry rather than
prometheus_client.REGISTRY, which is why it needs one of the two fields rather than mounting
unconditionally the way FastAPI, Litestar and FastMCP do. See
Prometheus FastStream.

Benchmarks

verify.py advertised a multi-scenario invocation its argument parser rejected, and --help printed
the command the file refused to run. It now accepts several scenarios, running each in its own
process because sentry_sdk.init cannot be undone between them (#237). No effect on the published
package.


What's Changed

  • fix(benchmarks): accept the multi-scenario verify.py invocation its docs advertise by @lesnik512 in #237
  • fix: mount FastStream metrics for an injected collector registry by @lesnik512 in #238

Full Changelog: 1.8.0...1.8.1

1.8.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 10:55
1.8.0
927dc99

Two fixes in this release change what a service does without any configuration change on your part.
Both are on FastStream, and one affects every Sentry user. Read the first two sections before
upgrading.

FastStream OpenTelemetry now actually traces

If you run FastStreamBootstrapper with opentelemetry_endpoint set, this release changes what
happens at bootstrap.

Two separate defects were keeping the instrument inert:

  • It never called super().bootstrap(), so no TracerProvider was ever built (#228). The broker
    telemetry middleware received whatever ambient provider the process had, normally a non-recording
    proxy, so broker spans went nowhere.
  • It also required opentelemetry_middleware_cls. Without one the whole instrument was skipped,
    silently
    (#230), because a config-level skip is deliberately quiet. The exporter, the
    health-check span and any opentelemetry_instrumentors were all inert as a result.

After upgrading, such a service builds a real TracerProvider, starts an OTLP exporter and claims
the process-global tracer provider slot
, which is set-once per process. If your application
installs its own provider, install it before calling bootstrap(); the broker middleware still reads
get_tracer_provider(), so an application that gets there first still wins.

Expect a background exporter thread and outbound OTLP traffic from services that previously produced
neither.

Sentry stops paying for logs it throws away

SentryInstrument now appends LoggingIntegration(sentry_logs_level=None) unless you already supply
a LoggingIntegration in sentry_integrations, or set sentry_default_integrations=False (#231).

lite-bootstrap never enables Sentry Logs, but SentryLogsHandler.emit formats each record before
checking whether logs are enabled (getsentry/sentry-python#7402),
so every INFO+ record was being formatted and discarded. The event and breadcrumb handlers keep
their sentry-sdk defaults, so nothing else about Sentry's behaviour changes.

sentry_additional_params also changes: it now overrides the parameters lite-bootstrap passes to
sentry_sdk.init instead of colliding with them. Previously a key that lite-bootstrap also set
raised TypeError: got multiple values for keyword argument. Nothing that worked before behaves
differently, since those collisions were crashes.

New configuration

field what it does
sentry_auto_session_tracking Sentry release-health sessions, default True. False saves ~7.7 µs/request.
sentry_logging_breadcrumb_level breadcrumb handler level, default logging.INFO. None saves ~7 µs per log record, at the cost of log breadcrumbs on error events.
opentelemetry_exclude_spans (FastAPI) drops the ASGI receive and send spans, leaving the server span. Empty by default. ["receive", "send"] saves ~34 µs/request (#225).

New: a Performance page

What the observability stack costs
documents the measured cost of each instrument and what each saving gives up (#234). Short version:
OpenTelemetry costs about twice what Sentry does, structlog costs nothing until you log, and the
Sentry knobs people reach for first (attach_stacktrace, max_breadcrumbs, dropping default
integrations) all measure inside noise.

Internal

Dependency drift is now checked daily rather than weekly, so an upstream release reddens main with
a tracking issue instead of the next pull request (#233). The benchmark harness reaches its tuned
configuration through real config fields rather than monkeypatches (#234), and the test suite's
broker annotation was updated for faststream 0.7.6 (#232).


What's Changed

  • test: report a bare docs/adr/NNNN citation as unresolved by @lesnik512 in #224
  • feat: make the ASGI receive/send spans opt-out on FastAPI by @lesnik512 in #225
  • fix: build a TracerProvider on FastStream by @lesnik512 in #228
  • fix: trace FastStream without a broker telemetry middleware by @lesnik512 in #230
  • test: annotate the broker helper for faststream 0.7.6 by @lesnik512 in #232
  • feat: expose the Sentry settings that cost RPS by @lesnik512 in #231
  • chore(ci): run the dependency check daily instead of weekly by @lesnik512 in #233
  • docs: document what the observability stack costs in RPS by @lesnik512 in #234

Full Changelog: 1.7.0...1.8.0

1.7.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 10:28
1.7.0
7b32953

What's Changed

  • feat: make the OpenTelemetry sampler configurable, and compress the ADRs by @lesnik512 in #222

Full Changelog: 1.6.0...1.7.0

1.6.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 19:14
1.6.0
ffbd378

What's Changed

  • Rename the instrument protocol to match the configured/ready split by @lesnik512 in #196
  • fix: attribute config warnings to the line that built the config by @lesnik512 in #200
  • chore: record issue tracker, triage labels and domain doc layout for agents by @lesnik512 in #201
  • refactor: split OpenTelemetryInstrument.bootstrap() into named steps by @lesnik512 in #205
  • refactor: mechanical readability pass (#199) by @lesnik512 in #206
  • fix: attribute instrument bootstrap() warnings to the user's call site (#202) by @lesnik512 in #208
  • chore(ruff): converge on the standard's ruff block by @lesnik512 in #207
  • docs: prune AGENTS.md by @lesnik512 in #209
  • chore(coverage): declare the gate in [tool.coverage.report] and measure only in test-ci by @lesnik512 in #212
  • feat: declare minimum compatible versions for fastmcp and faststream by @lesnik512 in #211
  • feat: declare minimum compatible version for typing-extensions by @lesnik512 in #214
  • feat: declare minimum compatible versions for the OpenTelemetry train by @lesnik512 in #215
  • docs(agents): drop the retired paragraphs and keep the canonical ones verbatim by @lesnik512 in #217
  • feat: declare minimum compatible version for fastapi by @lesnik512 in #216
  • feat: declare floors for the last four bare dependencies by @lesnik512 in #218
  • test: fail when an ADR cited from Python does not resolve by @lesnik512 in #219
  • fix: repair the litestar and faststream floors by @lesnik512 in #220
  • feat: exercise the declared dependency floors in CI by @lesnik512 in #221

Full Changelog: 1.5.0...1.6.0