Configuration¶
Sentry¶
Sentry integration uses sentry_sdk package under the hood.
To bootstrap Sentry, you must provide at least:
sentry_dsn- tells sentry-sdk where to send the events.
Additional parameters can also be supplied through the settings object:
sentry_traces_sample_rate- in the range of 0.0 to 1.0, the percentage chance a given transaction will be sentsentry_sample_rate- in the range of 0.0 to 1.0, the sample rate for error eventssentry_max_breadcrumbs- the total amount of breadcrumbssentry_max_value_length- the max event payload lengthsentry_attach_stacktrace- if True, stack traces are automatically attached to all messages loggedsentry_auto_session_tracking- whether every request opens and closes a Sentry release-healthSession(default:True), measured at ~7.7 µs per request. Set it toFalseif you do not use Sentry release health.sentry_integrations- list of integrations to enablesentry_logging_breadcrumb_level- the minimum standard-library log level recorded as a breadcrumb (default:logging.INFO). Passed asLoggingIntegration(level=...); see below.sentry_tags- key/value string pairs that are both indexed and searchablesentry_additional_params- additional params, which will be passed tosentry_sdk.init, overriding any of the above that map to the same keywordsentry_default_integrations- whether to use sentry's default integrations (default:True)sentry_before_send- optional callback chained after the built-in structlog enricher, passed tosentry_sdk.init(before_send=...)
Read more about sentry_sdk params here.
Sentry is the second most expensive instrument in the stack. Its cost comes almost entirely from the ASGI integration; see Performance.
Sentry logging integration¶
Unless sentry_integrations already contains a LoggingIntegration, lite-bootstrap appends one built
as LoggingIntegration(level=sentry_logging_breadcrumb_level, sentry_logs_level=None). Its event
handler keeps the sentry-sdk default (ERROR), so the only departure from sentry-sdk's own default
integration is sentry_logs_level.
Turning sentry_logs_level off is free. lite-bootstrap never sets enable_logs, so Sentry Logs is
off, but SentryLogsHandler.emit formats the record before it checks whether logs are enabled
(getsentry/sentry-python#7402) - it formats
every INFO+ record and discards the result. Dropping breadcrumbs as well, with
sentry_logging_breadcrumb_level=None, saves more but costs you log breadcrumbs on error events, so
it stays on by default. Both are measured on the performance page.
Two ways to opt out of the appended integration: supply your own LoggingIntegration in
sentry_integrations, which lite-bootstrap leaves untouched, or set
sentry_default_integrations=False, which suppresses it along with every other default integration.
Under either, sentry_logging_breadcrumb_level is ignored and lite-bootstrap warns.
Prometheus¶
Prometheus is the cheapest of the three non-logging instruments; see Performance.
Prometheus is on by default when its extra is installed, serving metrics at prometheus_metrics_path
(default: /metrics). Set prometheus_metrics_path to an empty string to disable it.
Additional parameters:
prometheus_metrics_include_in_schema.
Prometheus Litestar¶
Prometheus's integration for Litestar requires prometheus_client package.
Additional parameters for Litestar integration:
prometheus_group_path- defaults toTrueso thepathmetric label uses the route template (/users/{id}) instead of the raw URL, bounding metric cardinality. Set toFalseto record raw paths.prometheus_additional_params- passed tolitestar.plugins.prometheus.PrometheusConfig.
Prometheus FastStream¶
Prometheus's integration for FastStream requires prometheus_client package.
To bootstrap Prometheus for FastStream, you must provide at least one of:
prometheus_middleware_cls- the broker metrics middleware, e.g.faststream.redis.prometheus.RedisPrometheusMiddleware. It is constructed with the instrument's registry and added to every broker of the first broker's type. For an application with brokers of different types, pass a mapping from broker class to middleware class instead; see multiple brokers.prometheus_collector_registry- aprometheus_client.CollectorRegistryof your own, used in place of the fresh one the instrument would otherwise build.
With neither, nothing would populate the registry, so the metrics endpoint is not mounted and the
instrument is skipped. Unlike the other frameworks, FastStream serves a private registry rather than
prometheus_client.REGISTRY, so an endpoint with no middleware and no injected registry would have
nothing to report.
Prometheus FastAPI¶
Prometheus's integration for FastAPI uses prometheus_fastapi_instrumentator package.
Additional parameters for FastAPI integration:
prometheus_instrumentator_params- passed toprometheus_fastapi_instrumentator.Instrumentatorprometheus_instrument_params- passed tomethod Instrumentator(...).instrumentprometheus_expose_params- passed tomethod Instrumentator(...).expose.
Prometheus FastMCP¶
Prometheus's integration for FastMCP uses the prometheus_client and prometheus_fastapi_instrumentator
packages, and serves prometheus_client.REGISTRY at prometheus_metrics_path.
Every ASGI application application.http_app() builds counts its requests in http_requests_total,
http_request_duration_seconds and the instrumentator's other default metrics, labelled by route
(handler="/mcp"); requests to unknown paths are grouped into handler="none", and the metrics
route itself is not counted. Tool calls are counted in fastmcp_tool_calls_total{tool, status}, with
status either success or error, and timed in fastmcp_tool_call_duration_seconds{tool}.
Additional parameters for FastMCP integration:
prometheus_instrumentator_params- passed toprometheus_fastapi_instrumentator.Instrumentator;excluded_handlersdefaults to the metrics route.prometheus_instrument_params- passed tomethod Instrumentator(...).instrument.fastmcp_prometheus_tool_metrics_enabled- count and time tool calls (default:True).
Opentelemetry¶
To bootstrap Opentelemetry, you must provide at least one of:
opentelemetry_endpoint, for traces.opentelemetry_metrics_endpoint, for metrics.opentelemetry_log_traces=True, to log spans to stdout.
Additional parameters:
opentelemetry_service_name- if provided, will be passed to theResourceinstead ofservice_name.opentelemetry_container_name- will be passed to theResource.opentelemetry_endpoint- will be passed toOTLPSpanExporteras endpoint. Underopentelemetry_exporter_protocol="http"this is a full URL (e.g.http://collector:4318/v1/traces).opentelemetry_namespace- will be passed to theResource.opentelemetry_exporter_protocol- OTLP exporter transport:"grpc"(default, needs theotlextra) or"http"(needs theotl-httpextra, nogrpcio- installable on free-threaded Python).opentelemetry_insecure- whether the gRPC OTLP connection is insecure (default:True; gRPC only; forhttpthe endpoint URL scheme carries security). While it isTrue, an endpoint pointing at a non-local host triggers a warning that telemetry is sent unencrypted.opentelemetry_instrumentors- a list of extra instrumentors.opentelemetry_log_traces- traces will be logged to stdout.opentelemetry_sampler- anopentelemetry.sdk.trace.sampling.Samplerdeciding which traces are recorded. Unset, the SDK's own default applies:parentbased_always_on, which records every trace that is not the child of a non-recording remote parent, unlessOTEL_TRACES_SAMPLER/OTEL_TRACES_SAMPLER_ARGsay otherwise.opentelemetry_generate_health_check_spans- generate spans for health check handlers ifTrue.opentelemetry_excluded_urls- extra regexes for URLs excluded from tracing; the metrics path and (unless health-check spans are enabled) the health-check path are excluded automatically. Litestar searches them in the request path, FastAPI and FastMCP in the full URL. The environment variable OpenTelemetry defines for the framework (OTEL_PYTHON_LITESTAR_EXCLUDED_URLS,OTEL_PYTHON_FASTAPI_EXCLUDED_URLSorOTEL_PYTHON_STARLETTE_EXCLUDED_URLSfor FastMCP), falling back toOTEL_PYTHON_EXCLUDED_URLS, adds to them.opentelemetry_metrics_endpoint- will be passed toOTLPMetricExporteras endpoint, and turns on the metrics signal. Its own field rather than a flag onopentelemetry_endpoint, so upgrading never starts exporting metrics you did not ask for. Underopentelemetry_exporter_protocol="http"this is a full URL (e.g.http://collector:4318/v1/metrics);opentelemetry_exporter_protocolandopentelemetry_insecureare shared with traces.
Metrics¶
Left unset, no MeterProvider is installed and the metrics signal stays off. Set it, and the
FastAPI, Litestar, FastStream and FastMCP instrumentations start recording their duration histograms
against it; until then they build those instruments against a no-op provider and the data goes
nowhere.
The export interval is the SDK's, 60 seconds, overridable with OTEL_METRIC_EXPORT_INTERVAL.
If the Prometheus instrument is also configured, request metrics are recorded twice: once for the scrape endpoint and once for the OTLP pipeline, under different names. That is supported, and it is what you want when the two feed different backends, but pointing both at one backend double-counts.
config = FastAPIConfig(
service_name="my-service",
opentelemetry_endpoint="localhost:4317",
opentelemetry_metrics_endpoint="localhost:4317",
)
Sampling is the cheapest way to cut what tracing costs: on a benchmark endpoint returning a constant,
ParentBased(TraceIdRatioBased(0.01)) saved ~55 µs per request against the always-on default.
OpenTelemetry is the most expensive instrument in the stack; see Performance.
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
config = FastAPIConfig(
service_name="my-service",
opentelemetry_endpoint="otl",
opentelemetry_sampler=ParentBased(TraceIdRatioBased(0.01)),
)
For FastAPI and Litestar there is additionally:
opentelemetry_exclude_spans- drops the ASGIreceiveand/orsendspans, leaving only the server span. Empty by default, which records all three.["receive", "send"]is worth ~34 µs per request on the benchmark endpoint, at the cost of two thirds of the spans disappearing from your trace view.
For FastStream there is additionally:
opentelemetry_middleware_cls- the broker telemetry middleware, e.g.faststream.redis.opentelemetry.RedisTelemetryMiddleware, or a mapping from broker class to middleware class (see multiple brokers). You must provide it to trace broker messages. Without it the rest of the configuration above still applies: the exporter is built, the health-check span is recorded unlessopentelemetry_generate_health_check_spansis False, and anyopentelemetry_instrumentorsare applied.
Resource¶
The Resource is built with the SDK's Resource.create, so OpenTelemetry resource detectors work
with no lite-bootstrap setting. Name them, comma-separated, in OTEL_EXPERIMENTAL_RESOURCE_DETECTORS;
each name is an entry point in the opentelemetry_resource_detector group. The SDK registers
process, os and host; separately installed detector packages register their own names:
OTEL_RESOURCE_ATTRIBUTES is honoured as well.
The attributes lite-bootstrap sets (service.name, service.namespace, service.version,
container.name) are merged last and override anything a detector or environment variable supplies.
service_name defaults to "micro-service", so OTEL_SERVICE_NAME is overridden unless service_name
is empty: set opentelemetry_service_name or service_name instead.
Pyroscope¶
Pyroscope integration uses pyroscope-io package under the hood. Install it with the pyroscope extra, e.g. lite-bootstrap[fastapi-all,pyroscope].
To bootstrap Pyroscope, you must provide at least:
pyroscope_endpoint- the Pyroscope server address (e.g.http://pyroscope:4040).
Additional parameters:
pyroscope_sample_rate- CPU profiling sample rate in Hz (default:100).pyroscope_tags- key/value string pairs attached to all profiles.pyroscope_additional_params- additional params passed directly topyroscope.configure.
When OpenTelemetry is also enabled, a PyroscopeSpanProcessor is automatically added to the tracer provider. It tags root spans with a pyroscope.profile.id attribute and sets Pyroscope thread tags so that traces and profiles can be linked in the Grafana UI.
pyroscope-io publishes no free-threaded wheels, so the pyroscope extra cannot install on
free-threaded CPython (#171). Sentry's
continuous profiler is pure Python and runs there; enable it through sentry_additional_params:
import sentry_sdk
config = FreeConfig(
sentry_dsn="https://key@sentry.example.com/1",
sentry_additional_params={"profile_session_sample_rate": 1.0, "profile_lifecycle": "manual"},
)
FreeBootstrapper(config).bootstrap()
sentry_sdk.profiler.start_profiler()
Read more about Sentry's profiling options here.
Structlog¶
Structlog is bootstrapped by default. To opt out, set logging_enabled=False.
Configuring it costs almost nothing; the cost arrives per log record, and most of it is Sentry's. See Performance.
Additional parameters:
logging_enabled- whether to configure structlog (default:True).logging_log_levellogging_flush_levellogging_buffer_capacitylogging_extra_processorslogging_unset_handlerslogging_record_filters- standard-librarylogging.Filterinstances to attach to named loggers (see below).logging_time_stamper- astructlog.processors.TimeStamperinstance controlling timestamp format (default:TimeStamper(fmt="iso")). Pass a custom instance to change the format or enable UTC:
import structlog
from lite_bootstrap import FastAPIConfig
config = FastAPIConfig(
logging_time_stamper=structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=True),
)
Filtering standard-library log records¶
logging_record_filters attaches logging.Filter instances to loggers by name. A filter sees every
record its logger emits, before any handler renders it, and may rewrite the record or return False
to drop it. This lets you demote a third-party error you already handle:
import logging
from lite_bootstrap import FreeBootstrapper, FreeConfig
class ExpectedFailureFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
if record.levelno == logging.ERROR and "expected failure" in record.getMessage():
record.levelno = logging.WARNING
record.levelname = "WARNING"
return True
bootstrapper = FreeBootstrapper(
bootstrap_config=FreeConfig(
logging_record_filters={"some_library.worker": (ExpectedFailureFilter(),)},
),
)
- Set both
levelnoandlevelnamewhen changing a level: handlers read them independently. - The logger name is exact. A filter on
some_librarynever seessome_library.worker, and""is the root logger, matching only records logged through root itself. That is standardloggingbehaviour: a logger consults its own filters, then its ancestors' handlers. - Teardown removes only what it attached, leaving filters your application put on the same logger in place.
Structlog Litestar¶
When using Litestar, the StructlogPlugin is automatically registered, which enables request.logger inside route handlers:
from litestar import Litestar, Request, get
from lite_bootstrap import LitestarConfig, LitestarBootstrapper
@get("/")
async def handler(request: Request) -> dict[str, str]:
request.logger.info("handling request")
return {"status": "ok"}
Additional parameters for Litestar's access-log middleware:
litestar_logging_middleware_enabled- turn on request/response access logging (default:False).litestar_logging_middleware_config- a caller-suppliedLoggingMiddlewareConfigthat replaces the built-in defaults wholesale. It is ignored, with a warning, unlesslitestar_logging_middleware_enabledisTrue.
See the Litestar integration guide for what gets logged and why access logging defaults to off.
Structlog FastAPI¶
FastAPI ships no access log of its own, so lite-bootstrap provides one. It is off by default:
fastapi_logging_middleware_enabled- turn on the structured access log (default:False).
See the FastAPI integration guide for what gets logged and why it defaults to off.
Structlog FastMCP¶
The per-MCP-message access log is off by default:
fastmcp_logging_middleware_enabled- turn on the access log (default:False).
See the FastMCP integration guide for what gets logged.
Structlog FastStream¶
When using FastStream, the structlog logger is automatically injected into the broker so that all broker service messages (e.g. "Received", "Processed") are routed through structlog.
The broker log level is controlled independently from the application log level:
faststream_log_level- log level for FastStream broker service messages (default:logging.WARNING).
This allows you to suppress broker noise while keeping your application logs at a lower level:
import logging
from lite_bootstrap import FastStreamConfig
config = FastStreamConfig(
logging_log_level=logging.INFO, # your application logs
faststream_log_level=logging.WARNING, # broker "Received"/"Processed" messages (default)
)
CORS¶
To bootstrap CORS headers, you must provide cors_allowed_origins or cors_allowed_origin_regex.
Additional params:
cors_allowed_methodscors_allowed_headerscors_exposed_headerscors_allowed_credentialscors_max_age
Swagger¶
To bootstrap swagger, you have the following parameters:
swagger_static_path- path for offline docs staticswagger_pathswagger_offline_docs- option to turn on offline docs.
Swagger is on by default at swagger_path (default: /docs). For Litestar, set swagger_path to an empty string to disable it.
Health checks¶
Health checks are on by default; set health_checks_enabled=False to disable them.
Additional params:
health_checks_pathhealth_checks_include_in_schemahealth_checks_probes: named async callables, each returningbool, that the route runs on every request
The health check route on Litestar, FastAPI, FastMCP and FastStream runs every probe concurrently. On FastStream the probes run alongside the broker pings. The route returns 200 with health_status: true when everything passes. If any probe returns False or raises, the route returns 500 with the same JSON body and health_status: false. Each failed probe logs one warning with its name; the response body does not say which probe failed.
import functools
import sqlalchemy
from lite_bootstrap import LitestarConfig
async def database_probe() -> bool:
async with engine.connect() as connection:
await connection.execute(sqlalchemy.text("SELECT 1"))
return True
config = LitestarConfig(
health_checks_probes={
"database": database_probe,
"outbox-broker": functools.partial(outbox_broker.ping, timeout=5),
},
)
lite-bootstrap puts no timeout on probes, so a probe that hangs makes the route hang too. Give each probe its own timeout, as functools.partial(broker.ping, timeout=5) does.
Be careful with probes on dependencies outside the service, such as a database, when the route also serves a Kubernetes liveness probe. During the database outage every pod fails its liveness check and gets restarted, and the restarts add load once the database comes back. Readiness probes are the usual place for dependency checks.
Skipped instruments¶
When a bootstrapper is constructed, each registered instrument is checked twice:
-
is_configured(config)(classmethod, runs before instantiation) returns False if the user's config indicates this instrument shouldn't run (e.g.sentry_dsnempty,logging_enabled=False,pyroscope_endpointempty). When False, the instrument is silently skipped and recorded inbootstrapper.skipped_instruments: list[tuple[type, str]]. Each entry is the instrument class plus itsnot_configured_reason. -
dependencies_installed()runs only ifis_configured()returned True. If the instrument's optional package is missing, anInstrumentDependencyMissingWarningis emitted, since a configured instrument with a missing dependency is usually a deployment mistake.
After the loop, the bootstrapper emits one INFO-level summary log listing configured + skipped instruments. Default Python logging suppresses INFO; opt in via logging.basicConfig(level=logging.INFO).
Filter the dep-missing warning the same way as any UserWarning:
import warnings
from lite_bootstrap import InstrumentDependencyMissingWarning
warnings.filterwarnings("ignore", category=InstrumentDependencyMissingWarning)
InstrumentSkippedWarning is kept as the base class for forward-compatibility (additional skip categories may emerge); today, InstrumentDependencyMissingWarning is its only concrete subclass.
To inspect skipped instruments programmatically:
bootstrapper = FastAPIBootstrapper(bootstrap_config=config)
for cls, reason in bootstrapper.skipped_instruments:
print(f"{cls.__name__}: {reason}")
To get a human-readable view of the same information at any later point (e.g. for debugging from a REPL or a health endpoint), call bootstrapper.build_summary(). It returns the multi-line string that the INFO summary log emits, which helps when log levels are filtered or when you want to render the bootstrapper state inline.