1111import io .opentelemetry .api .trace .TraceFlags ;
1212import io .opentelemetry .api .trace .TraceState ;
1313import io .opentelemetry .api .trace .Tracer ;
14- import io .opentelemetry .api .trace .TracerProvider ;
1514import io .opentelemetry .context .Context ;
1615import io .opentelemetry .context .Scope ;
1716import io .opentelemetry .sdk .trace .SdkTracerProvider ;
17+ import io .opentelemetry .sdk .trace .SdkTracerProviderBuilder ;
1818import java .time .Instant ;
1919import java .util .concurrent .ConcurrentHashMap ;
2020import org .slf4j .Logger ;
@@ -52,17 +52,15 @@ public class OpenTelemetryDurablePlugin implements DurableExecutionPlugin {
5252 private static final Logger logger = LoggerFactory .getLogger (OpenTelemetryDurablePlugin .class );
5353 private static final String INSTRUMENTATION_NAME = "aws-durable-execution-sdk-java" ;
5454
55- private final TracerProvider tracerProvider ;
55+ private final SdkTracerProvider tracerProvider ;
5656 private final Tracer tracer ;
5757 private final DeterministicIdGenerator idGenerator ;
5858 private final ContextExtractor contextExtractor ;
59- private final double samplingRate ;
6059 private final boolean enableMdc ;
6160
6261 // Per-invocation state
6362 private volatile Span invocationSpan ;
6463 private volatile String executionArn ;
65- private volatile boolean sampled = true ;
6664
6765 // Thread-safe storage for operation spans (keyed by operationId) — open spans that need ending
6866 private final ConcurrentHashMap <String , Span > operationSpans = new ConcurrentHashMap <>();
@@ -75,75 +73,59 @@ public class OpenTelemetryDurablePlugin implements DurableExecutionPlugin {
7573 private final ConcurrentHashMap <String , SpanContext > operationContexts = new ConcurrentHashMap <>();
7674
7775 /**
78- * Creates an OTel plugin with default settings: X-Ray context extraction, 100% sampling, MDC enabled.
76+ * Creates an OTel plugin with default settings: X-Ray context extraction, MDC enabled.
7977 *
80- * @param tracerProvider the OTel tracer provider (should use {@link DeterministicIdGenerator})
81- * @param idGenerator the deterministic ID generator (same instance configured in the tracer provider)
78+ * <p>The plugin internally creates a {@link DeterministicIdGenerator} and overrides the tracer provider's ID
79+ * generator to ensure deterministic trace/span IDs across invocations.
80+ *
81+ * <p>Sampling is handled by the tracer provider's configured {@code Sampler}. Since trace IDs are deterministic
82+ * (same execution ARN → same trace ID), using {@code TraceIdRatioBased} sampler gives consistent per-execution
83+ * sampling across invocations.
84+ *
85+ * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden)
8286 */
83- public OpenTelemetryDurablePlugin (TracerProvider tracerProvider , DeterministicIdGenerator idGenerator ) {
84- this (tracerProvider , idGenerator , new XRayContextExtractor (), 1.0 , true );
87+ public OpenTelemetryDurablePlugin (SdkTracerProviderBuilder tracerProviderBuilder ) {
88+ this (tracerProviderBuilder , new XRayContextExtractor (), true );
8589 }
8690
8791 /**
88- * Creates an OTel plugin with a custom context extractor and sampling rate, MDC enabled .
92+ * Creates an OTel plugin with a custom context extractor.
8993 *
90- * @param tracerProvider the OTel tracer provider
91- * @param idGenerator the deterministic ID generator
94+ * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden)
9295 * @param contextExtractor extracts parent trace context from the Lambda environment
93- * @param samplingRate value between 0.0 and 1.0 — fraction of executions to trace
9496 */
9597 public OpenTelemetryDurablePlugin (
96- TracerProvider tracerProvider ,
97- DeterministicIdGenerator idGenerator ,
98- ContextExtractor contextExtractor ,
99- double samplingRate ) {
100- this (tracerProvider , idGenerator , contextExtractor , samplingRate , true );
98+ SdkTracerProviderBuilder tracerProviderBuilder , ContextExtractor contextExtractor ) {
99+ this (tracerProviderBuilder , contextExtractor , true );
101100 }
102101
103102 /**
104103 * Creates an OTel plugin with full configuration.
105104 *
106- * @param tracerProvider the OTel tracer provider
107- * @param idGenerator the deterministic ID generator
105+ * <p>The plugin internally creates a {@link DeterministicIdGenerator} and sets it on the provided builder before
106+ * building the tracer provider. Customers configure exporters, span processors, and samplers on the builder — the
107+ * plugin handles ID generation.
108+ *
109+ * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden)
108110 * @param contextExtractor extracts parent trace context from the Lambda environment
109- * @param samplingRate value between 0.0 and 1.0 — fraction of executions to trace
110111 * @param enableMdc if true, injects trace_id/span_id into SLF4J MDC for log correlation
111112 */
112113 public OpenTelemetryDurablePlugin (
113- TracerProvider tracerProvider ,
114- DeterministicIdGenerator idGenerator ,
115- ContextExtractor contextExtractor ,
116- double samplingRate ,
117- boolean enableMdc ) {
118- this .tracerProvider = tracerProvider ;
119- this .idGenerator = idGenerator ;
114+ SdkTracerProviderBuilder tracerProviderBuilder , ContextExtractor contextExtractor , boolean enableMdc ) {
115+ this .idGenerator = new DeterministicIdGenerator ();
116+ this .tracerProvider = tracerProviderBuilder .setIdGenerator (idGenerator ).build ();
120117 this .tracer = tracerProvider .get (INSTRUMENTATION_NAME );
121118 this .contextExtractor = contextExtractor ;
122- this .samplingRate = samplingRate ;
123119 this .enableMdc = enableMdc ;
124120 }
125121
126- /**
127- * Creates an OTel plugin using a pre-configured {@link SdkTracerProvider}.
128- *
129- * @param sdkTracerProvider the SDK tracer provider
130- * @param idGenerator the deterministic ID generator
131- */
132- public OpenTelemetryDurablePlugin (SdkTracerProvider sdkTracerProvider , DeterministicIdGenerator idGenerator ) {
133- this ((TracerProvider ) sdkTracerProvider , idGenerator );
134- }
135-
136122 // ─── Invocation hooks ────────────────────────────────────────────────
137123
138124 @ Override
139125 public void onInvocationStart (InvocationInfo info ) {
140126 this .executionArn = info .executionArn ();
141127 idGenerator .setExecutionArn (info .executionArn ());
142128
143- // Determine sampling (consistent across all invocations of same execution)
144- this .sampled = SamplingUtil .shouldSampleExecution (info .executionArn (), samplingRate );
145- if (!sampled ) return ;
146-
147129 // Extract parent context from Lambda environment (X-Ray, W3C, etc.)
148130 var extractedParentContext = contextExtractor .extract ();
149131
@@ -162,7 +144,7 @@ public void onInvocationStart(InvocationInfo info) {
162144
163145 @ Override
164146 public void onInvocationEnd (InvocationEndInfo info ) {
165- if (! sampled || invocationSpan == null ) return ;
147+ if (invocationSpan == null ) return ;
166148
167149 // End any operation spans that are still open (operations that didn't complete in this invocation)
168150 for (var entry : operationSpans .entrySet ()) {
@@ -196,19 +178,17 @@ public void onInvocationEnd(InvocationEndInfo info) {
196178 invocationSpan = null ;
197179
198180 // Flush spans before Lambda freezes
199- if (tracerProvider instanceof SdkTracerProvider sdkProvider ) {
200- var flushResult = sdkProvider .forceFlush ().join (5 , java .util .concurrent .TimeUnit .SECONDS );
201- if (!flushResult .isSuccess ()) {
202- logger .warn ("OTel span flush failed or timed out — some spans may be lost" );
203- }
181+ var flushResult = tracerProvider .forceFlush ().join (5 , java .util .concurrent .TimeUnit .SECONDS );
182+ if (!flushResult .isSuccess ()) {
183+ logger .warn ("OTel span flush failed or timed out — some spans may be lost" );
204184 }
205185 }
206186
207187 // ─── Operation hooks ─────────────────────────────────────────────────
208188
209189 @ Override
210190 public void onOperationStart (OperationInfo info ) {
211- if (! sampled || info .id () == null ) return ;
191+ if (info .id () == null ) return ;
212192
213193 idGenerator .setNextSpanOperationId (info .id ());
214194
@@ -239,7 +219,7 @@ public void onOperationStart(OperationInfo info) {
239219
240220 @ Override
241221 public void onOperationEnd (OperationEndInfo info ) {
242- if (! sampled || info .id () == null ) return ;
222+ if (info .id () == null ) return ;
243223
244224 // End the operation span that was started in onOperationStart
245225 var span = operationSpans .remove (info .id ());
@@ -257,7 +237,6 @@ public void onOperationEnd(OperationEndInfo info) {
257237
258238 @ Override
259239 public void onUserFunctionStart (UserFunctionStartInfo info ) {
260- if (!sampled ) return ;
261240 var key = attemptKey (info .id (), info .attempt ());
262241
263242 // Use the operation span as parent for the attempt span
@@ -295,7 +274,6 @@ public void onUserFunctionStart(UserFunctionStartInfo info) {
295274
296275 @ Override
297276 public void onUserFunctionEnd (UserFunctionEndInfo info ) {
298- if (!sampled ) return ;
299277 var key = attemptKey (info .id (), info .attempt ());
300278
301279 // Close scope first (must happen on same thread as makeCurrent)
0 commit comments