11"""OpenTelemetry helpers for Temporal workers running inside AWS Lambda.
22
33Use :py:func:`apply_defaults` inside a :py:func:`run_worker` configure callback for a
4- batteries-included setup that creates an OTel collector exporter and tracing interceptor , suitable
4+ batteries-included setup that creates an OTel collector exporter and tracing plugin , suitable
55for use with the AWS Distro for OpenTelemetry (ADOT) Lambda layer.
66
77Use :py:func:`apply_tracing` or :py:func:`build_metrics_telemetry_config` individually if you only
1010
1111from __future__ import annotations
1212
13+ import logging
1314import os
1415from dataclasses import dataclass , field
1516from datetime import timedelta
1617
1718from opentelemetry .sdk .resources import Resource
18- from opentelemetry .sdk .trace import TracerProvider
1919from opentelemetry .sdk .trace .export import BatchSpanProcessor
2020from opentelemetry .semconv .attributes .service_attributes import SERVICE_NAME
21+ from opentelemetry .trace import get_tracer_provider , set_tracer_provider
2122
2223from temporalio .contrib .aws .lambda_worker ._configure import LambdaWorkerConfig
24+ from temporalio .contrib .opentelemetry import OpenTelemetryPlugin , create_tracer_provider
2325from temporalio .runtime import OpenTelemetryConfig , Runtime , TelemetryConfig
2426
27+ logger = logging .getLogger (__name__ )
28+
2529
2630@dataclass
2731class OtelOptions :
@@ -73,14 +77,20 @@ def apply_defaults(
7377 """Configure OTel metrics and tracing with AWS Lambda defaults.
7478
7579 Sets up Core SDK metrics export via a :py:class:`temporalio.runtime.Runtime` with an
76- :py:class:`temporalio.runtime.OpenTelemetryConfig` pointing at the OTLP collector, and adds a
77- :py:class:`temporalio.contrib.opentelemetry.TracingInterceptor` for distributed tracing.
80+ :py:class:`temporalio.runtime.OpenTelemetryConfig` pointing at the OTLP collector, and adds the
81+ :py:class:`temporalio.contrib.opentelemetry.OpenTelemetryPlugin` for distributed tracing with
82+ workflow sandbox passthrough.
83+
84+ Creates a replay-safe ``TracerProvider`` (with X-Ray ID generator and OTLP gRPC exporter if
85+ available) and sets it as the global OpenTelemetry tracer provider. The
86+ :py:class:`temporalio.contrib.opentelemetry.OpenTelemetryPlugin` uses the global provider, so
87+ it must be set before the worker starts.
7888
7989 The collector endpoint defaults to ``http://localhost:4317``, which is the endpoint expected by
8090 the ADOT collector Lambda layer.
8191
82- Registers a per-invocation ``ForceFlush`` shutdown hook for the ``TracerProvider`` so pending
83- traces are exported before each Lambda invocation completes.
92+ Registers a per-invocation ``ForceFlush`` shutdown hook for the global ``TracerProvider`` so
93+ pending traces are exported before each Lambda invocation completes.
8494
8595 Metrics are exported on the ``metric_periodicity`` interval by the runtime's internal thread.
8696 There is no explicit flush API for these metrics; set ``metric_periodicity`` short enough to
@@ -112,11 +122,16 @@ def apply_defaults(
112122 AwsXRayIdGenerator ,
113123 )
114124
115- tracer_provider = TracerProvider (
125+ tracer_provider = create_tracer_provider (
116126 resource = resource , id_generator = AwsXRayIdGenerator ()
117127 )
118128 except ImportError :
119- tracer_provider = TracerProvider (resource = resource )
129+ logger .warning (
130+ "opentelemetry-sdk-extension-aws is not installed; "
131+ "X-Ray trace ID generation is disabled. "
132+ "Install the 'lambda-worker-otel' extra for full ADOT support."
133+ )
134+ tracer_provider = create_tracer_provider (resource = resource )
120135
121136 # Use OTLP gRPC exporter if available, otherwise skip trace export.
122137 try :
@@ -128,9 +143,16 @@ def apply_defaults(
128143 BatchSpanProcessor (OTLPSpanExporter (endpoint = endpoint , insecure = True ))
129144 )
130145 except ImportError :
131- pass
146+ logger .warning (
147+ "opentelemetry-exporter-otlp-proto-grpc is not installed; "
148+ "traces will not be exported to the OTLP collector. "
149+ "Install the 'lambda-worker-otel' extra for full ADOT support."
150+ )
151+
152+ # Set as global so the OpenTelemetryPlugin picks it up.
153+ set_tracer_provider (tracer_provider )
132154
133- apply_tracing (config , tracer_provider )
155+ apply_tracing (config )
134156
135157
136158def build_metrics_telemetry_config (
@@ -191,28 +213,29 @@ def build_metrics_telemetry_config(
191213 )
192214
193215
194- def apply_tracing (
195- config : LambdaWorkerConfig ,
196- tracer_provider : TracerProvider ,
197- ) -> None :
216+ def apply_tracing (config : LambdaWorkerConfig ) -> None :
198217 """Configure only OTel tracing (no metrics) on the Lambda worker config.
199218
200- Adds a :py:class:`temporalio.contrib.opentelemetry.TracingInterceptor` to
201- ``config.client_connect_config["interceptors"]`` and registers a ``ForceFlush`` shutdown hook
202- for the provider.
219+ Adds an :py:class:`temporalio.contrib.opentelemetry.OpenTelemetryPlugin` to
220+ ``config.worker_config["plugins"]``. The plugin uses the global
221+ ``TracerProvider`` set via ``opentelemetry.trace.set_tracer_provider``.
222+ Ensure your provider is set globally before the worker starts.
223+
224+ Also registers a ``ForceFlush`` shutdown hook that flushes the global
225+ ``TracerProvider`` (if it supports ``force_flush``).
203226
204227 Args:
205228 config: The :py:class:`LambdaWorkerConfig` to configure.
206- tracer_provider: The ``TracerProvider`` to use for tracing.
207229 """
208- from temporalio .contrib .opentelemetry import TracingInterceptor
209-
210- interceptor = TracingInterceptor (tracer = tracer_provider .get_tracer ("temporal-sdk" ))
211- interceptors = list (config .client_connect_config .get ("interceptors" , []))
212- interceptors .append (interceptor )
213- config .client_connect_config ["interceptors" ] = interceptors
230+ plugin = OpenTelemetryPlugin ()
231+ plugins = list (config .worker_config .get ("plugins" , []))
232+ plugins .append (plugin )
233+ config .worker_config ["plugins" ] = plugins
214234
215235 async def _flush () -> None :
216- tracer_provider .force_flush ()
236+ provider = get_tracer_provider ()
237+ flush = getattr (provider , "force_flush" , None )
238+ if flush is not None :
239+ flush ()
217240
218241 config .shutdown_hooks .append (_flush )
0 commit comments