LOG_FORMAT (see Logs).
Only logs are exported. Traces and metrics are not.
Requirements
Export needs theotel extra, which the published Docker images include. For a source install:
OTEL_ENABLED=true and the extra missing, every backend process stops at startup with:
Configuration
OTEL_ENABLED and OTEL_EXPORT_REDACT_KEYS can also be set in backend/config/.env. All other OTEL_* variables are read by the OpenTelemetry SDK from the process environment only. Docker Compose passes every entry of its env_file to the container, so there it makes no difference; when you start the backend with uv run, export them in the shell instead.
Only http/protobuf is supported. If OTEL_EXPORTER_OTLP_PROTOCOL is set to anything else, the backend logs a warning and still uses HTTP. Batching can be tuned with the standard OTEL_BLRP_* variables.
Example .env entries for a collector on the same Compose network:
What is exported
Each process reports itself with its ownservice.name, in the open-wearables service namespace:
service.version and deployment.environment.name (from ENVIRONMENT) are set as well. Values in OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES take precedence.
Structured app logs (log_structured) keep their message as the log body; their fields become attributes under app., for example app.user_id, app.provider and app.trace_id (the app’s own correlation id, not an OpenTelemetry trace id). Fields that app code passes with extra= to standard loggers keep their names. Extra fields of library loggers are not exported: they are not part of the log line on stdout either, and Celery uses them to attach task arguments, keyword arguments and return values to its task records. Celery also formats task results into its own succeeded messages; in the exported copy they read <omitted> (stdout is unchanged). Its Received unregistered task and invalid-task errors, which only occur with a misconfiguration, still contain the full task message.
Before export, an attribute value is replaced with REDACTED when its name, converted to snake_case (X-Api-Key and xApiKey both become x_api_key), contains password, passwd, secret, token, authorization, api_key, apikey, cookie, signature, credential, private_key, bearer, session or one of the entries in OTEL_EXPORT_REDACT_KEYS, or is response_body or body_preview. This also applies to keys inside nested objects and to the fields of pydantic models and dataclasses, which are read field by field. Other objects, exceptions included, are exported as their type name only (<ClassName>), never as their text.
Log attributes are high-cardinality (user and record ids). Keep them as attributes in your log backend and do not turn them into index labels, for example Loki labels.
- Raw payloads printed with
RAW_PAYLOAD_STORAGE=log. They go to stdout only. - Logs of the CPU worker’s main process (task receipt and pool management). Its child processes, which run the tasks, export their logs.
- The last uvicorn lines after the API has stopped (
Application shutdown complete.,Finished server process), and beat’sbeat: Starting...line, which Celery logs before export starts. - Output that does not go through logging: Celery’s startup banner and
worker: Warm shutdownlines.
OTEL_EXPORTER_OTLP_TIMEOUT well below your container stop timeout.
When the collector is unreachable or rejects the request, the exporter logs the failure (Failed to export logs batch ...). These lines go to stdout like other logs but do not become Sentry events or breadcrumbs. (Sentry Logs, which this project does not turn on, is not covered by that.)
Example: OpenTelemetry Collector
A collector that prints what it receives, useful to check the setup.otel-collector-config.yaml:
docker compose up -d and one API request, docker compose logs otel-collector shows records such as:
debug exporter with the one for your log backend.
Without exporting from the app
If your platform can read container logs (Grafana Alloy, the OpenTelemetry Collectorfilelog receiver, Azure Monitor Agent, Datadog Agent), LOG_FORMAT=json is often enough: every log record is then one JSON line that these agents parse without app-side export. Some output does not go through logging and stays plain text, such as startup banners and start script output (see Logs). Configure the agent to keep lines that are not JSON as plain messages instead of rejecting them.
Related settings
- FastAPI’s built-in OpenTelemetry support (traces, metrics, exception logs) stays off;
OTEL_*variables do not turn it on. - The OpenTelemetry API reads
OTEL_PROPAGATORSwhen the backend starts. A propagator that is not installed (for exampleb3) stops the backend at import, whether export is on or not. Leave the variable unset unless you install the propagator package.

