Skip to content

Architecture

graph LR
    A[pico-boot init] -->|entry point| B[OtelBootstrap @configure]
    B --> C[TracerProvider + exporter]
    B --> D[MeterProvider -> prometheus default REGISTRY]
    B --> E[SQLAlchemy / Celery / logging instrumentors]
    F[OtelFastApiConfigurer] -->|structural FastApiConfigurer| G[FastAPI app spans]
    D --> H["pico-actuator /actuator/metrics"]
Module Responsibility
bootstrap.py Idempotent SDK setup + instrumentors + FastAPI configurer
config.py OtelSettings (prefix otel)

Design decisions

The default Prometheus registry is the contract with pico-actuator. pico-otel writes (PrometheusMetricReader), pico-actuator reads (/actuator/metrics). Neither imports the other; installing both composes with zero config. Refactors on either side must preserve this.

Structural typing instead of importing pico-fastapi. OtelFastApiConfigurer satisfies the FastApiConfigurer protocol by shape (priority, configure_app). With pico-fastapi absent the component simply never runs — no optional import, no extra.

Idempotent, first-configuration-wins. OTel global providers are process-level; a second container must not fight the first. Setup checks for the SDK provider and skips — deterministic in test suites that boot many containers.

Every instrumentation is import-guarded and mapped 1:1 to an extra. The base install carries only the SDK; you pay (in dependencies) exactly for what you instrument. Missing extra = silent skip with an INFO log.

auto exporter mode. OTLP when an endpoint is configured, console otherwise — the dev-to-prod transition is one yaml line.