Observability¶
ArchiPy uses OpenTelemetry for traces, metrics, and logs. Providers are built from
BaseConfig.OTEL (pydantic-settings) — not from OTEL_* environment-variable autoconfiguration.
Enable the relevant extras, set OTEL__IS_ENABLED=true, and call AppUtils.create_fastapi_app /
create_grpc_app (or use the tracing/metrics decorators in workers and business logic).
Installation¶
| Extra | Purpose |
|---|---|
archipy[otel] |
SDK, OTLP exporters, B3 propagator, httpx/requests, threading, system metrics |
archipy[otel,fastapi] |
FastAPI native telemetry (FastAPI 0.142.2+) |
archipy[otel-grpc] |
gRPC server/client contrib interceptors |
archipy[otel-sqlalchemy] |
SQLAlchemy instrumentation (covers Postgres/MySQL/SQLite via ORM) |
archipy[otel-redis] |
Redis instrumentation |
archipy[otel-kafka] |
Confluent Kafka instrumentation |
archipy[otel-scylladb] |
Cassandra/ScyllaDB driver instrumentation |
archipy[otel-minio] |
Botocore (MinIO/S3) instrumentation |
archipy[otel-pydantic-ai] |
Pydantic AI agent, model request and tool spans plus token usage metrics |
Configuration¶
Configure OpenTelemetry through BaseConfig.OTEL (env prefix OTEL__):
# .env
OTEL__IS_ENABLED=true
OTEL__SERVICE_NAME=my-service
OTEL__OTLP_ENDPOINT=http://localhost:4317
OTEL__PROTOCOL=grpc
OTEL__TRACES_ENABLED=true
OTEL__METRICS_ENABLED=true
OTEL__METRICS_EXPORTER=otlp
OTEL__METRICS_PULL_HOST=0.0.0.0
OTEL__METRICS_PULL_PORT=8200
OTEL__SYSTEM_METRICS_ENABLED=true
OTEL__LOGS_ENABLED=true
OTEL__LOGS_EXPORTER=console
OTEL__TRACES_SAMPLE_RATIO=0.1
OTEL__FASTAPI_EXCLUDED_URLS=health,docs,redoc,openapi.json
import logging
from archipy.configs.base_config import BaseConfig
logger = logging.getLogger(__name__)
class AppConfig(BaseConfig):
"""Application configuration with OpenTelemetry enabled."""
config = AppConfig()
BaseConfig.set_global(config)
logger.info("OTel enabled=%s endpoint=%s", config.OTEL.IS_ENABLED, config.OTEL.OTLP_ENDPOINT)
Note: ArchiPy builds
TracerProvider/MeterProvider/LoggerProviderprogrammatically fromOpentelemetryConfig; the SDK's ownOTEL_*autoconfiguration is not used. SetUSE_STANDARD_ENVto let the standard variables fill in unset settings (see StandardOTEL_*variables).
Key fields¶
| Field | Default | Description |
|---|---|---|
IS_ENABLED |
false |
Master switch |
OTLP_ENDPOINT |
http://localhost:4317 |
Default OTLP collector URL |
TRACES_ENDPOINT |
None |
Optional per-signal OTLP traces URL override |
METRICS_ENDPOINT |
None |
Optional per-signal OTLP metrics URL override |
LOGS_ENDPOINT |
None |
Optional per-signal OTLP logs URL override |
PROTOCOL |
grpc |
grpc or http/protobuf |
TRACES_SAMPLER |
parentbased_traceidratio |
always_on, always_off, traceidratio or the parentbased_* variants |
TRACES_SAMPLE_RATIO |
0.1 |
Ratio of the traceidratio and parentbased_traceidratio samplers |
PROPAGATORS |
["tracecontext", "baggage"] |
Context propagators; also b3, b3multi; none disables them |
OTLP_COMPRESSION |
none |
gzip cuts realistic payloads to about half or less at some CPU cost; worth it when the collector is across a network |
SPAN_BATCH_* / LOG_BATCH_* |
queue 4096, batch 512 | SCHEDULE_DELAY_MS (spans 5000, logs 1000), EXPORT_TIMEOUT_MS (30000), MAX_QUEUE_SIZE, MAX_EXPORT_BATCH_SIZE of the batch processors. Keep a batch under the 4 MiB message limit gRPC receivers apply to the decompressed message: 512 spans with 25 attributes encode to about 0.75 MiB, with a 4 KB stack trace each to about 2.5 MiB, and 1024 of those already pass 4 MiB. Batch size barely changes the gzip ratio: on realistic spans gzip cut the bytes of a gRPC export to about 0.4 of the uncompressed size (200 spans, 25 attributes and a 4 KB stack trace each, measured through a proxy). A batch size left unset follows a lowered queue size; one set above its queue size is rejected |
SERVICE_NAME / SERVICE_VERSION |
None |
Resource service.name and service.version |
SERVICE_INSTANCE_ID |
generated UUID | Resource service.instance.id |
RESOURCE_DETECTORS |
["service_instance", "host", "os"] |
Detectors that add service.instance.id, host.*, os.*; process adds process.* including the full command line, so it is opt-in |
METRICS_DURATION_BUCKETS_S |
5 ms to 10 s | Bucket boundaries in seconds of the duration histograms |
USE_STANDARD_ENV |
false |
Fill unset settings from the standard OTEL_* variables (see below) |
METRIC_EXPORT_INTERVAL_MS |
60000 |
Periodic OTLP metric export interval |
METRICS_EXPORTER |
otlp |
Unique exporter: otlp, pull, or pushgateway |
METRICS_PULL_HOST |
0.0.0.0 |
Pull scrape bind host (unauthenticated) |
METRICS_PULL_PORT |
8200 |
Pull scrape bind port (/metrics) |
METRICS_PUSHGATEWAY_URL |
None |
Pushgateway URL (required for pushgateway) |
METRICS_PUSHGATEWAY_JOB |
None |
Pushgateway job (defaults to SERVICE_NAME) |
METRICS_PUSHGATEWAY_INTERVAL_SECONDS |
60 |
Push interval when exporter is pushgateway |
METRICS_PUSHGATEWAY_TIMEOUT_SECONDS |
10 |
HTTP timeout for push/delete |
METRICS_PUSHGATEWAY_GROUPING_KEY |
{} |
Extra grouping labels (max 16 entries); instance defaults to the pod (see below) |
METRICS_PUSHGATEWAY_DELETE_ON_SHUTDOWN |
true |
Delete grouping key on graceful shutdown |
SYSTEM_METRICS_ENABLED |
true |
Process/system metrics instrumentor |
INSTRUMENTATION_SKIP_DEP_CHECK |
["sqlalchemy"] |
Libraries instrumented even when newer than the instrumentor's declared range |
LOGS_ENABLED |
true |
Enable log export when OTel is on |
LOGS_EXPORTER |
console |
Unique logs exporter: console or otlp |
FASTAPI_EXCLUDED_URLS |
None |
Comma-separated URL patterns skipped by FastAPI |
FASTAPI_OPERATION_SPANS_ENABLED |
true |
FastAPI child spans (dependencies, endpoint, serialization, background tasks) |
FASTAPI_LOGS_ENABLED |
false |
FastAPI log records for validation failures and unhandled exceptions (includes stack traces) |
RESOURCE_ATTRIBUTES |
{} |
Extra OTel resource attributes |
LOGS_LEVEL |
INFO |
Minimum level for the root-logger handler |
Pushgateway and several replicas¶
A Pushgateway push replaces its whole group, so replicas that share a group overwrite each other's metrics. ArchiPy
therefore adds an instance label to the grouping key unless you set one: POD_NAME, else HOSTNAME (or the host
name). On Kubernetes the hostname is the pod name unless the pod uses hostNetwork; set POD_NAME explicitly with the
downward API:
On plain Docker the hostname is the container ID, because the container name is not visible inside the container.
Warning
Pushgateway keeps the last push of a group until it is deleted. A graceful shutdown deletes the pod's group
(METRICS_PUSHGATEWAY_DELETE_ON_SHUTDOWN), but a crashed pod leaves a stale group, and pod names change on every
restart. Clean stale groups up, for example with a CronJob that deletes groups that have not pushed recently.
Standard OTEL_* variables¶
With OTEL__USE_STANDARD_ENV=true, the standard variables fill in every setting that ArchiPy's own config leaves
unset. A field set in code, in an OTEL__* variable or in TOML always wins. An invalid value is logged and ignored, and
so are values that are valid alone but invalid together (for example a batch size above the queue size).
| Variable | Sets |
|---|---|
OTEL_SERVICE_NAME |
SERVICE_NAME |
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT |
OTLP_ENDPOINT, TRACES_ENDPOINT, METRICS_ENDPOINT, LOGS_ENDPOINT |
OTEL_EXPORTER_OTLP_PROTOCOL (grpc, http/protobuf) |
PROTOCOL |
OTEL_EXPORTER_OTLP_HEADERS (k=v,k=v) |
OTLP_HEADERS |
OTEL_EXPORTER_OTLP_TIMEOUT (seconds, as the Python SDK reads it) |
TIMEOUT |
OTEL_EXPORTER_OTLP_COMPRESSION (none, gzip) |
OTLP_COMPRESSION |
OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG |
TRACES_SAMPLER, TRACES_SAMPLE_RATIO (the Jaeger and X-Ray samplers are not supported) |
OTEL_PROPAGATORS |
PROPAGATORS |
OTEL_BSP_*, OTEL_BLRP_* |
SPAN_BATCH_*, LOG_BATCH_* |
OTEL_METRIC_EXPORT_INTERVAL |
METRIC_EXPORT_INTERVAL_MS |
OTEL_SDK_DISABLED=true |
Keeps OpenTelemetry off, whatever IS_ENABLED says |
OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME also reach the resource without the switch, because the SDK's
Resource.create reads them; explicit RESOURCE_ATTRIBUTES and SERVICE_NAME override them.
Metrics exporters¶
Exactly one metrics exporter when METRICS_ENABLED is true — set via
METRICS_EXPORTER (otlp | pull | pushgateway):
| Mode | METRICS_EXPORTER |
Behavior |
|---|---|---|
| OTLP (default) | otlp |
Periodic export to the OTLP collector |
| Pull scrape | pull |
Dedicated HTTP server on METRICS_PULL_HOST:METRICS_PULL_PORT/metrics |
| Pushgateway | pushgateway |
Daemon thread pushes Prometheus text to Pushgateway (no local scrape) |
otlp and pushgateway both push from the process, but to different wire
targets: OTLP collector vs Prometheus Pushgateway.
Pull scrape is a standalone process-wide HTTP server started from
OtelUtils.init_otel_if_needed — not a FastAPI /metrics route. Works for
FastAPI, gRPC, and workers. If the scrape port cannot bind, ArchiPy logs a
warning and keeps traces/logs/metrics providers running.
# Pull-only (no collector)
OTEL__METRICS_ENABLED=true
OTEL__METRICS_EXPORTER=pull
OTEL__METRICS_PULL_HOST=0.0.0.0
OTEL__METRICS_PULL_PORT=8200
# Prometheus Pushgateway (no local /metrics server)
OTEL__METRICS_ENABLED=true
OTEL__METRICS_EXPORTER=pushgateway
OTEL__METRICS_PUSHGATEWAY_URL=http://pushgateway.monitoring:9091
OTEL__METRICS_PUSHGATEWAY_JOB=my-service
OTEL__METRICS_PUSHGATEWAY_INTERVAL_SECONDS=60
OtelUtils.metrics_registry() returns the Prometheus CollectorRegistry when
exporter is pull or pushgateway.
Logs exporters¶
Exactly one logs exporter when LOGS_ENABLED is true — set via
LOGS_EXPORTER (console | otlp):
| Mode | LOGS_EXPORTER |
Behavior |
|---|---|---|
| Console (default) | console |
INFO/DEBUG → stdout; WARNING+ → stderr |
| OTLP | otlp |
Batch export to the OTLP collector |
Warning (scrape security): The pull scrape endpoint is unauthenticated. Default bind
0.0.0.0is for cluster scrape. Prefer127.0.0.1plus a sidecar/proxy when the network is not locked down. Do not expose scrape on the public internet.Warning (prefork): The parent process holds the scrape port. With gunicorn
preload+ fork, workers cannot share one scrape port — use a unique port per worker or disable preload.Note (HTTP endpoints): With
PROTOCOL=http/protobuf, each signal needs its own path (/v1/traces,/v1/metrics,/v1/logs). WhenOTLP_ENDPOINThas no path (e.g.http://localhost:4318), ArchiPy appends the correct/v1/{signal}suffix automatically. SetTRACES_ENDPOINT/METRICS_ENDPOINT/LOGS_ENDPOINTto override a single signal. gRPC mode uses one shared endpoint and needs no path.Warning (logs cost): When
LOGS_ENABLED=true, aLoggingHandleris attached to the root logger atLOGS_LEVEL. WithLOGS_EXPORTER=otlp, every matching record from every library (httpx, sqlalchemy, urllib3, …) is exported to OTLP — preferWARNINGin production.console(default) writes locally only. Records fromopentelemetry.*loggers are filtered out to avoid exporter feedback loops.Note (provider globals): ArchiPy sets the global tracer/meter provider once (
_globals_set). If another library already installed a global provider first, FastAPI/gRPC/Temporal interceptors (global) and ArchiPy decorators (owned provider) may emit to different backends — and ArchiPy OTLP/pull exporter configuration is ignored for an adoptedMeterProvider. Initialize ArchiPy OTel early viaAppUtilsorOtelUtils.init_otel_if_needed.Note (instrument names): The OTel SDK lowercases instrument names (
TestMethod→testmethod). Prefer lowercase dotted names in dashboards.
Initialization Order¶
Call OtelUtils.init_otel_if_needed(config) at bootstrap — before your DI container builds
adapters — right after BaseConfig.set_global(config). Init also installs the W3C TraceContext +
Baggage textmap propagator (traceparent / tracestate / baggage) so another library cannot
silently replace the global propagator first.
import logging
from archipy.configs.base_config import BaseConfig
from archipy.helpers.utils.otel_utils import OtelUtils
logger = logging.getLogger(__name__)
class AppConfig(BaseConfig):
"""Application configuration with OpenTelemetry enabled."""
config = AppConfig()
BaseConfig.set_global(config)
OtelUtils.init_otel_if_needed(config)
logger.info("OTel initialized before adapter construction")
# Only now build the DI container / adapters
The call is idempotent and thread-safe — AppUtils.create_fastapi_app / create_grpc_app
invoke it again safely.
Warning: If adapters are constructed before OTel initialization, some telemetry is lost permanently:
- SQLAlchemy — engines created before
SQLAlchemyInstrumentorruns are never traced (only futurecreate_enginecalls are wrapped).- Confluent Kafka — producers/consumers instantiated before instrumentation stay unwrapped.
- Cassandra/ScyllaDB — sessions created early miss span wrapping.
- Logs — records emitted before init bypass the OTLP logging handler.
Tracers and meters obtained via
OtelUtils.get_tracer/get_meterbefore init recover automatically (the global proxy provider resolves once providers are set), as do Redis, requests, httpx, and httpx2 clients — their instrumentors patch at class level.
Auto-instrumentation via AppUtils¶
FastAPI¶
AppUtils.create_fastapi_app passes FastAPIUtils.build_otel_telemetry_config(config) to
FastAPI(telemetry=...). That initializes providers (idempotent) and enables FastAPI's built-in
OpenTelemetry support (requires archipy[otel,fastapi], FastAPI 0.142.2+). Traces and metrics use
ArchiPy's providers; OTLP auto-configuration is always off. FastAPI log records (validation failures and
unhandled exceptions, with stack traces) are opt-in via OTEL__FASTAPI_LOGS_ENABLED=true and use ArchiPy's
logger provider. The HTTP duration
metric is http.server.request.duration (seconds). If you build the app yourself, pass the same
config to the constructor:
import logging
from archipy.configs.base_config import BaseConfig
from archipy.helpers.utils.app_utils import AppUtils
logger = logging.getLogger(__name__)
config = BaseConfig.global_config()
app = AppUtils.create_fastapi_app(config)
logger.info("FastAPI app created with OTel auto-instrumentation")
gRPC¶
AppUtils.create_grpc_app / create_async_grpc_app install OpenTelemetry server
interceptors when OTel is enabled (requires archipy[otel-grpc] for traces):
- Traces — contrib
server_interceptor/aio_server_interceptorwhenTRACES_ENABLED - Metrics — the OpenTelemetry
rpc.server.call.durationhistogram (seconds, same buckets as FastAPI'shttp.server.request.duration) and an ArchiPyrpc.server.active_requestsup-down counter (in-flight calls; the specification does not define it) whenMETRICS_ENABLED(works in metrics-only mode; no handler decorators required). The duration histogram follows the RPC semantic conventions:rpc.system.name(grpc), the fully qualifiedrpc.method(package.Service/Method) and therpc.response.status_codename the call finished with (OK,NOT_FOUND,CANCELLED, ...).error.typeis set to the status code only for the codes the conventions treat as server failures (UNKNOWN,DEADLINE_EXCEEDED,UNIMPLEMENTED,INTERNAL,UNAVAILABLE,DATA_LOSS); handled caller errors such asNOT_FOUNDorRESOURCE_EXHAUSTEDand client cancellations carry a status code but noerror.type. A handler that raises without a status is recorded asUNKNOWN, which is what gRPC answers.
Renamed in this release
The histogram used to be rpc.server.request.duration with rpc.system, rpc.service, a short rpc.method,
rpc.grpc.status_code and a custom status attribute. Update dashboards and alerts to the names above.
Order becomes: OTel traces (if any) → OTel metrics (if any) → exception interceptor → rate-limit (if enabled) → custom interceptors.
import logging
from archipy.configs.base_config import BaseConfig
from archipy.helpers.utils.app_utils import AppUtils
logger = logging.getLogger(__name__)
config = BaseConfig.global_config()
server = AppUtils.create_grpc_app(config)
logger.info("gRPC server created with OTel interceptor")
Library instrumentors¶
On first OtelUtils.init_otel_if_needed, ArchiPy best-effort instruments installed contrib
packages (SQLAlchemy, Redis, Confluent Kafka, Cassandra, Botocore, httpx, httpx2,
requests, threading) when the matching otel-* extras are present. Instrumentors whose
target library is not installed (e.g. httpx when only httpx2 is present) are skipped
silently. Elasticsearch needs no instrumentor — the elasticsearch client emits OTel
spans natively.
SystemMetricsInstrumentor runs only when METRICS_ENABLED and
SYSTEM_METRICS_ENABLED are both true.
Version ranges¶
Each instrumentor declares the library versions it supports. When a library is installed outside
that range, ArchiPy logs a warning (no spans will be emitted) instead of staying silent.
A library that is simply not installed is skipped at debug level.
opentelemetry-instrumentation-sqlalchemy still declares sqlalchemy<2.1.0, while ArchiPy requires
SQLAlchemy 2.1. The cap sits in the instrumentor's instruments extra, which is not a hard
dependency, so installs resolve without error and the instrumentor only refuses at runtime. To keep
SQLAlchemy spans, OTEL__INSTRUMENTATION_SKIP_DEP_CHECK (default ["sqlalchemy"]) lists the
libraries that are instrumented anyway; ArchiPy logs one warning naming the mismatch. Set it to []
to turn the bypass off. Remove a key once its instrumentor supports the installed version:
make check-instrumentor-compat resolves the instrumentor's instruments extra together with
ArchiPy's own SQLAlchemy bound and reports when upstream has caught up (a weekly CI job runs it too).
Pydantic AI¶
With archipy[otel-pydantic-ai] installed, OtelUtils.init_otel_if_needed calls Agent.instrument_all with
ArchiPy's tracer and meter providers, so agent runs, model requests and tool calls become spans and token usage
becomes a metric. An agent the application instrumented itself is left alone. Settings live in BaseConfig.PYDANTIC_AI:
| Field | Default | Description |
|---|---|---|
INSTRUMENTATION_ENABLED |
true |
Instrument Pydantic AI agents when OpenTelemetry is enabled |
INSTRUMENTATION_INCLUDE_CONTENT |
true |
Prompts, model output and tool arguments in span attributes (turn off for personal data) |
INSTRUMENTATION_INCLUDE_BINARY_CONTENT |
false |
Binary content (images, audio, documents) in span attributes |
INSTRUMENTATION_INCLUDE_MODEL_REQUEST_PARAMETERS |
true |
Model request parameters (temperature, max tokens, ...) in model request spans |
INSTRUMENTATION_VERSION |
6 |
Span and attribute format version (5 or 6) |
INSTRUMENTATION_USE_AGGREGATED_USAGE_ATTRIBUTE_NAMES |
true |
Name the agent run usage attributes gen_ai.aggregated_usage.* |
Client gRPC¶
For outbound gRPC clients, attach contrib interceptors explicitly:
import logging
import grpc
from archipy.helpers.utils.otel_utils import OtelUtils
logger = logging.getLogger(__name__)
channel = grpc.intercept_channel(
grpc.insecure_channel("localhost:50051"),
*OtelUtils.grpc_client_interceptors(),
)
logger.info("gRPC client channel wrapped with OTel interceptors")
For async clients use OtelUtils.async_grpc_client_interceptors().
Tracing Decorators¶
Every decorator span records code.function.name (the fully qualified name of the traced function), and a span for a
failed call also carries error.type (the BaseError.code, or the fully qualified exception class) next to the
recorded exception event.
For code outside FastAPI/gRPC — workers, schedulers, domain logic — use decorators from
archipy.helpers.decorators:
| Decorator | Purpose |
|---|---|
@trace_span |
Sync child span |
@trace_span |
Async child span |
@trace_root |
Sync root span (entry point) |
@trace_root |
Async root span |
@trace_class |
Wrap public methods of a class with spans |
import logging
from archipy.helpers.decorators import trace_span, trace_root, trace_span
logger = logging.getLogger(__name__)
@trace_root(name="process_order")
def process_order(order_id: int) -> dict[str, int | float]:
"""Process a single order end-to-end.
Args:
order_id: The order to process.
Returns:
A summary of the processing result.
"""
items = fetch_order_items(order_id)
total = calculate_total(items)
return {"order_id": order_id, "total": total}
@trace_span(name="fetch_order_items", capture_args=["order_id"])
def fetch_order_items(order_id: int) -> list[dict[str, float]]:
"""Load order items from the database.
Args:
order_id: The order to load items for.
Returns:
List of order item dicts.
"""
logger.debug("Fetching items for order %d", order_id)
return [{"price": 10.0}]
@trace_span(name="calculate_total")
def calculate_total(items: list[dict[str, float]]) -> float:
"""Sum item prices.
Args:
items: List of order item dicts.
Returns:
Total order value.
"""
return sum(item["price"] for item in items)
Decorators no-op when OTEL.IS_ENABLED is false. On exceptions they set span status via
OtelUtils.status_for_exception (BaseError with HTTP status below 500 leaves status UNSET per the OTel spec for
handled client errors; other exceptions become ERROR).
Warning (
capture_args): Prefer non-sensitive identifiers (user_id,order_id). Names matching a denylist (password,token,secret,authorization,api_key, …) are recorded as***. The denylist is substring-based and case-insensitive — still avoid capturing free-form blobs that may embed secrets.
Metrics Decorators¶
| Decorator | Purpose |
|---|---|
@measure_duration |
Sync duration histogram |
@measure_duration |
Async duration histogram |
@count_calls |
Sync call counter |
@count_calls |
Async call counter |
from archipy.helpers.decorators import count_calls, measure_duration
@measure_duration(attributes={"layer": "logic"})
@count_calls()
def process_payment(amount: float) -> None:
"""Charge a payment amount.
Args:
amount: Amount to charge.
"""
...
Instruments default to {module}.{qualname}.duration / .calls and record a status
attribute (ok / error / cancelled for async cancel). Duration histograms use
explicit second buckets and a description. status aligns with tracing:
BaseError with HTTP status below 500 records as ok (handled client error), not
error. A call that ends in error or cancelled also carries the standard error.type (the BaseError.code, or
the fully qualified exception class); a call recorded as ok carries none. No-op when OTel or METRICS_ENABLED is off.
Exception Capture¶
archipy.helpers.errors.capture_exception always logs locally. When OTel is enabled and a recording span
is active, it records the exception on the current span and sets span status — it does not
send to Sentry or Elastic APM:
from archipy.helpers.errors import capture_exception
from archipy.models.errors import InternalError
try:
raise InternalError()
except InternalError as exc:
capture_exception(exc)
raise
Temporal¶
Temporal metrics and traces reuse the global OTel config:
- Metrics: set
TEMPORAL__ENABLE_METRICS=trueandOTEL__IS_ENABLED=truewithOTEL__METRICS_ENABLED=true. The adapter builds a TemporalRuntimewithOpenTelemetryConfigpointing atOTEL.OTLP_ENDPOINT/PROTOCOL/OTLP_HEADERS. WithOTEL__METRICS_EXPORTER=pullthe Temporal SDK serves its own/metricsonTEMPORAL__METRICS_PULL_PORT; withpushgatewayit still exports over OTLP because the SDK cannot push. - Traces: when
OTEL.IS_ENABLEDandTRACES_ENABLED, the Temporal client attachestemporalio.contrib.opentelemetry.TracingInterceptorafterOtelUtils.init_otel_if_needed.
See Temporal adapter for a full example.
Known Gaps¶
| Area | Status |
|---|---|
| Kafka aio | otel-kafka covers Confluent Kafka sync instrumentation; async Kafka paths may not be instrumented |
| SMTP / email | No OpenTelemetry instrumentation for smtplib / the email adapter |
See Also¶
- Interceptors — gRPC exception and rate-limit interceptors; OTel via AppUtils
- Error Handling — recording exceptions on the current span
- Temporal — OTLP metrics and
TracingInterceptor - Installation —
otelandotel-*extras - Configuration Management — nested env vars (
OTEL__*)