diff --git a/otel-plugin/README.md b/otel-plugin/README.md index 8dfaa8d5d..76cd2f96e 100644 --- a/otel-plugin/README.md +++ b/otel-plugin/README.md @@ -215,21 +215,9 @@ With Lambda's `LoggingConfig: JSON` (required for durable functions), CloudWatch ## Configuration -### Constructor Options - -```java -// Default: ADOT Java agent global provider, X-Ray context extraction, MDC enabled -new InvocationOtelPlugin(); - -// Custom tracer provider pipeline -new InvocationOtelPlugin(tracerProviderBuilder); - -// Custom context extractor, MDC enabled -new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor); - -// Full configuration -new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc); -``` +Both plugins take a required `SdkTracerProviderBuilder` (your exporter/processor pipeline) plus an optional +`OtelPluginConfig` built with a named-field builder. This replaces the older telescoping constructors, giving readable, +type-safe call sites, and matches the `OtelPluginConfig` object in the JavaScript and Python SDKs. ### InvocationOtelPlugin @@ -237,46 +225,53 @@ new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc); // Default: ADOT Java agent global provider, X-Ray context extraction, MDC enabled new InvocationOtelPlugin(); -// Custom tracer provider pipeline +// Custom tracer provider pipeline, all other options defaulted new InvocationOtelPlugin(tracerProviderBuilder); -// Custom context extractor, MDC enabled -new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor); - -// Full configuration -new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc); +// Full configuration via the builder +new InvocationOtelPlugin( + tracerProviderBuilder, + OtelPluginConfig.builder() + .contextExtractor(new XRayContextExtractor()) + .enableMdc(true) + .workflowSpanName("Workflow") + .instrumentationName("aws-durable-execution-sdk-java") + .build()); ``` -| Parameter | Description | Default | -|-----------|-------------|---------| -| `tracerProviderBuilder` | `SdkTracerProviderBuilder` with your exporter/processor configured | Not used by `new InvocationOtelPlugin()`; the default constructor uses the ADOT Java agent provider | -| `contextExtractor` | Extracts parent trace context from the Lambda environment | `XRayContextExtractor` | -| `enableMdc` | If true, injects `trace_id`/`span_id`/`traceSampled` into SLF4J MDC | `true` | - ### ExecutionOtelPlugin -The `ExecutionOtelPlugin` renders the Workflow span as the trace root with operations as siblings of the invocation span. It supports the same constructor options: +The `ExecutionOtelPlugin` renders the Workflow span as the trace root with operations as siblings of the invocation +span. It takes the same `(SdkTracerProviderBuilder, OtelPluginConfig)` constructor: ```java // Default: ADOT Java agent global provider, X-Ray context extraction, MDC enabled new ExecutionOtelPlugin(); -// Custom tracer provider pipeline +// Custom tracer provider pipeline, all other options defaulted new ExecutionOtelPlugin(tracerProviderBuilder); -// Custom context extractor, MDC enabled -new ExecutionOtelPlugin(tracerProviderBuilder, contextExtractor); - -// Full configuration -new ExecutionOtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc, workflowSpanName); +// Full configuration via the builder +new ExecutionOtelPlugin( + tracerProviderBuilder, + OtelPluginConfig.builder() + .enableMdc(false) + .workflowSpanName("Workflow") + .build()); ``` -| Parameter | Description | Default | +### OtelPluginConfig options + +| Builder method | Description | Default | |-----------|-------------|---------| -| `tracerProviderBuilder` | `SdkTracerProviderBuilder` with your exporter/processor configured | Not used by `new ExecutionOtelPlugin()`; the default constructor uses the ADOT Java agent provider | -| `contextExtractor` | Extracts parent trace context from the Lambda environment | `XRayContextExtractor` | -| `enableMdc` | If true, injects `trace_id`/`span_id`/`traceSampled` into SLF4J MDC | `true` | -| `workflowSpanName` | Name for the Workflow root span | `"Workflow"` | +| `contextExtractor(...)` | Extracts parent trace context from the Lambda environment | `new XRayContextExtractor()` | +| `enableMdc(...)` | If true, injects `trace_id`/`span_id`/`traceSampled` into SLF4J MDC | `true` | +| `workflowSpanName(...)` | Name for the Workflow span | `"Workflow"` | +| `instrumentationName(...)` | Instrumentation scope name registered with the tracer | `"aws-durable-execution-sdk-java"` | + +> The `tracerProviderBuilder` argument is not used by the no-arg `new InvocationOtelPlugin()` / +> `new ExecutionOtelPlugin()` constructors; those use the ADOT Java agent's global provider. A `null` passed to any +> `OtelPluginConfig` builder setter falls back to that option's default. ## Known Limitations diff --git a/otel-plugin/pom.xml b/otel-plugin/pom.xml index c3391934b..2a709f7f4 100644 --- a/otel-plugin/pom.xml +++ b/otel-plugin/pom.xml @@ -33,12 +33,18 @@ ${opentelemetry.version} - + io.opentelemetry opentelemetry-sdk ${opentelemetry.version} - provided + + + + io.opentelemetry + opentelemetry-exporter-otlp + ${opentelemetry.version} diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ExecutionOtelPlugin.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ExecutionOtelPlugin.java index 92236d4f0..ecffc4ba5 100644 --- a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ExecutionOtelPlugin.java +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ExecutionOtelPlugin.java @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -82,8 +81,6 @@ public class ExecutionOtelPlugin implements DurableExecutionPlugin { private static final Logger logger = LoggerFactory.getLogger(ExecutionOtelPlugin.class); - private static final String INSTRUMENTATION_NAME = "aws-durable-execution-sdk-java"; - private static final String DEFAULT_WORKFLOW_SPAN_NAME = "Workflow"; private final SdkTracerProvider sdkTracerProvider; private final Tracer tracer; @@ -91,6 +88,7 @@ public class ExecutionOtelPlugin implements DurableExecutionPlugin { private final ContextExtractor contextExtractor; private final boolean enableMdc; private final String workflowSpanName; + private final ProviderSource providerSource; // Per-invocation state private volatile Span workflowSpan; @@ -117,7 +115,7 @@ public class ExecutionOtelPlugin implements DurableExecutionPlugin { * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) */ public ExecutionOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder) { - this(tracerProviderBuilder, new XRayContextExtractor(), true, DEFAULT_WORKFLOW_SPAN_NAME); + this(tracerProviderBuilder, OtelPluginConfig.defaults()); } /** @@ -127,51 +125,63 @@ public ExecutionOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder) { * {@code OtelPluginAutoConfigurationCustomizerProvider}. */ public ExecutionOtelPlugin() { - this(getDefaultTracerProvider(), createDefaultIdGenerator()); + this(OtelPluginConfig.defaults()); } /** - * Creates a Workflow-rooted OTel plugin with a custom context extractor, MDC enabled, root span named - * {@code "Workflow"}. + * Creates a Workflow-rooted OTel plugin from the given tracer provider builder and configuration. * - * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) - * @param contextExtractor extracts parent trace context from the Lambda environment - */ - public ExecutionOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder, ContextExtractor contextExtractor) { - this(tracerProviderBuilder, contextExtractor, true, DEFAULT_WORKFLOW_SPAN_NAME); - } - - /** - * Creates a Workflow-rooted OTel plugin with full configuration. + *

Customers configure exporters and span processors on the builder; all other tunables (context extractor, MDC + * toggle, Workflow span name, instrumentation scope name) come from {@link OtelPluginConfig}. Use + * {@link OtelPluginConfig#builder()} for readable, named configuration: + * + *

{@code
+     * var plugin = new ExecutionOtelPlugin(
+     *     SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)),
+     *     OtelPluginConfig.builder().enableMdc(false).workflowSpanName("Workflow").build());
+     * }
* * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) - * @param contextExtractor extracts parent trace context from the Lambda environment - * @param enableMdc if true, injects traceId/spanId/otelTraceSampled into SLF4J MDC for log correlation - * @param workflowSpanName the name for the Workflow root span + * @param config the plugin configuration */ - public ExecutionOtelPlugin( - SdkTracerProviderBuilder tracerProviderBuilder, - ContextExtractor contextExtractor, - boolean enableMdc, - String workflowSpanName) { + public ExecutionOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder, OtelPluginConfig config) { this.idGenerator = new DeterministicIdGenerator(); this.sdkTracerProvider = tracerProviderBuilder.setIdGenerator(idGenerator).build(); - this.tracer = sdkTracerProvider.get(INSTRUMENTATION_NAME); - this.contextExtractor = contextExtractor; - this.enableMdc = enableMdc; - this.workflowSpanName = workflowSpanName != null ? workflowSpanName : DEFAULT_WORKFLOW_SPAN_NAME; + this.tracer = sdkTracerProvider.get(config.instrumentationName()); + this.contextExtractor = config.contextExtractor(); + this.enableMdc = config.enableMdc(); + this.workflowSpanName = config.workflowSpanName(); + this.providerSource = ProviderSource.EXPLICIT; } - private ExecutionOtelPlugin(TracerProvider tracerProvider, DeterministicIdGenerator idGenerator) { - this.idGenerator = idGenerator; - this.sdkTracerProvider = OtelPluginSupport.getSdkTracerProviderForFlush(tracerProvider, "ExecutionOtelPlugin"); - this.tracer = tracerProvider.get(INSTRUMENTATION_NAME); + /** + * Creates a Workflow-rooted OTel plugin from configuration alone (no caller-supplied tracer provider builder). + * + *

The provider is taken from {@link OtelPluginConfig#providerSource()}: {@link ProviderSource#GLOBAL} uses the + * ADOT/global provider, otherwise the default {@link ProviderSource#AUTO_OTLP} builds a plugin-owned OTLP/HTTP + * provider (matching the JavaScript and Python SDK plugins). {@link ProviderSource#EXPLICIT} is rejected here — + * supply a {@code SdkTracerProviderBuilder} via the two-arg constructor for that. + * + * @param config the plugin configuration + * @throws IllegalArgumentException if {@code config.providerSource()} is {@link ProviderSource#EXPLICIT} + */ + public ExecutionOtelPlugin(OtelPluginConfig config) { + this.contextExtractor = config.contextExtractor(); + this.enableMdc = config.enableMdc(); + this.workflowSpanName = config.workflowSpanName(); + + var setup = OtelPluginSupport.resolveConfiguredProvider(config, "ExecutionOtelPlugin"); + this.providerSource = setup.source(); + this.idGenerator = setup.idGenerator(); + this.sdkTracerProvider = setup.sdkTracerProvider(); + this.tracer = setup.tracer(); + } - this.contextExtractor = new XRayContextExtractor(); - this.enableMdc = true; - this.workflowSpanName = DEFAULT_WORKFLOW_SPAN_NAME; + /** The tier that produced this plugin's tracer provider. */ + public ProviderSource providerSource() { + return providerSource; } // ─── Invocation hooks ──────────────────────────────────────────────── @@ -581,12 +591,4 @@ private static String attemptKey(String operationId, Integer attempt) { private static ExtractedContext extractCurrentSpanContext() { return OtelPluginSupport.extractCurrentSpanContext(); } - - private static TracerProvider getDefaultTracerProvider() { - return OtelPluginSupport.getDefaultTracerProvider("ExecutionOtelPlugin"); - } - - private static DeterministicIdGenerator createDefaultIdGenerator() { - return OtelPluginSupport.createDefaultIdGenerator(); - } } diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java index 227542d8a..56f6c4881 100644 --- a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -94,8 +93,6 @@ public class InvocationOtelPlugin implements DurableExecutionPlugin { private static final Logger logger = LoggerFactory.getLogger(InvocationOtelPlugin.class); - private static final String INSTRUMENTATION_NAME = "aws-durable-execution-sdk-java"; - private static final String DEFAULT_WORKFLOW_SPAN_NAME = "Workflow"; private final SdkTracerProvider sdkTracerProvider; private final Tracer tracer; @@ -103,6 +100,7 @@ public class InvocationOtelPlugin implements DurableExecutionPlugin { private final ContextExtractor contextExtractor; private final boolean enableMdc; private final String workflowSpanName; + private final ProviderSource providerSource; // Per-invocation state private volatile Span workflowSpan; @@ -137,7 +135,7 @@ public class InvocationOtelPlugin implements DurableExecutionPlugin { * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) */ public InvocationOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder) { - this(tracerProviderBuilder, new XRayContextExtractor(), true); + this(tracerProviderBuilder, OtelPluginConfig.defaults()); } /** @@ -147,62 +145,63 @@ public InvocationOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder) { * {@code OtelPluginAutoConfigurationCustomizerProvider}. */ public InvocationOtelPlugin() { - this(getDefaultTracerProvider(), createDefaultIdGenerator()); + this(OtelPluginConfig.defaults()); } /** - * Creates an OTel plugin with a custom context extractor, MDC enabled. + * Creates an OTel plugin from the given tracer provider builder and configuration. * - * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) - * @param contextExtractor extracts parent trace context from the Lambda environment - */ - public InvocationOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder, ContextExtractor contextExtractor) { - this(tracerProviderBuilder, contextExtractor, true); - } - - /** - * Creates an OTel plugin with the given context extractor and MDC setting, using the default Workflow span name. + *

Customers configure exporters and span processors on the builder; all other tunables (context extractor, MDC + * toggle, Workflow span name, instrumentation scope name) come from {@link OtelPluginConfig}. Use + * {@link OtelPluginConfig#builder()} for readable, named configuration: * - * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) - * @param contextExtractor extracts parent trace context from the Lambda environment - * @param enableMdc if true, injects traceId/spanId/otelTraceSampled into SLF4J MDC for log correlation - */ - public InvocationOtelPlugin( - SdkTracerProviderBuilder tracerProviderBuilder, ContextExtractor contextExtractor, boolean enableMdc) { - this(tracerProviderBuilder, contextExtractor, enableMdc, DEFAULT_WORKFLOW_SPAN_NAME); - } - - /** - * Creates an OTel plugin with full configuration. + *

{@code
+     * var plugin = new InvocationOtelPlugin(
+     *     SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)),
+     *     OtelPluginConfig.builder().enableMdc(false).workflowSpanName("Workflow").build());
+     * }
* * @param tracerProviderBuilder the tracer provider builder (ID generator will be overridden) - * @param contextExtractor extracts parent trace context from the Lambda environment - * @param enableMdc if true, injects traceId/spanId/otelTraceSampled into SLF4J MDC for log correlation - * @param workflowSpanName the name for the Workflow span + * @param config the plugin configuration */ - public InvocationOtelPlugin( - SdkTracerProviderBuilder tracerProviderBuilder, - ContextExtractor contextExtractor, - boolean enableMdc, - String workflowSpanName) { + public InvocationOtelPlugin(SdkTracerProviderBuilder tracerProviderBuilder, OtelPluginConfig config) { this.idGenerator = new DeterministicIdGenerator(); this.sdkTracerProvider = tracerProviderBuilder.setIdGenerator(idGenerator).build(); - this.tracer = sdkTracerProvider.get(INSTRUMENTATION_NAME); - this.contextExtractor = contextExtractor; - this.enableMdc = enableMdc; - this.workflowSpanName = workflowSpanName != null ? workflowSpanName : DEFAULT_WORKFLOW_SPAN_NAME; + this.tracer = sdkTracerProvider.get(config.instrumentationName()); + this.contextExtractor = config.contextExtractor(); + this.enableMdc = config.enableMdc(); + this.workflowSpanName = config.workflowSpanName(); + this.providerSource = ProviderSource.EXPLICIT; } - private InvocationOtelPlugin(TracerProvider tracerProvider, DeterministicIdGenerator idGenerator) { - this.idGenerator = idGenerator; - this.sdkTracerProvider = OtelPluginSupport.getSdkTracerProviderForFlush(tracerProvider, "InvocationOtelPlugin"); - this.tracer = tracerProvider.get(INSTRUMENTATION_NAME); + /** + * Creates an OTel plugin from configuration alone (no caller-supplied tracer provider builder). + * + *

The provider is taken from {@link OtelPluginConfig#providerSource()}: {@link ProviderSource#GLOBAL} uses the + * ADOT/global provider, otherwise the default {@link ProviderSource#AUTO_OTLP} builds a plugin-owned OTLP/HTTP + * provider (matching the JavaScript and Python SDK plugins). {@link ProviderSource#EXPLICIT} is rejected here — + * supply a {@code SdkTracerProviderBuilder} via the two-arg constructor for that. + * + * @param config the plugin configuration + * @throws IllegalArgumentException if {@code config.providerSource()} is {@link ProviderSource#EXPLICIT} + */ + public InvocationOtelPlugin(OtelPluginConfig config) { + this.contextExtractor = config.contextExtractor(); + this.enableMdc = config.enableMdc(); + this.workflowSpanName = config.workflowSpanName(); + + var setup = OtelPluginSupport.resolveConfiguredProvider(config, "InvocationOtelPlugin"); + this.providerSource = setup.source(); + this.idGenerator = setup.idGenerator(); + this.sdkTracerProvider = setup.sdkTracerProvider(); + this.tracer = setup.tracer(); + } - this.contextExtractor = new XRayContextExtractor(); - this.enableMdc = true; - this.workflowSpanName = DEFAULT_WORKFLOW_SPAN_NAME; + /** The tier that produced this plugin's tracer provider. */ + public ProviderSource providerSource() { + return providerSource; } // ─── Invocation hooks ──────────────────────────────────────────────── @@ -641,12 +640,4 @@ private static String attemptKey(String operationId, Integer attempt) { private static ExtractedContext extractCurrentSpanContext() { return OtelPluginSupport.extractCurrentSpanContext(); } - - private static TracerProvider getDefaultTracerProvider() { - return OtelPluginSupport.getDefaultTracerProvider("InvocationOtelPlugin"); - } - - private static DeterministicIdGenerator createDefaultIdGenerator() { - return OtelPluginSupport.createDefaultIdGenerator(); - } } diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginConfig.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginConfig.java new file mode 100644 index 000000000..61d40f7bf --- /dev/null +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginConfig.java @@ -0,0 +1,221 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.otel; + +import java.util.Map; + +/** + * Immutable configuration for {@link InvocationOtelPlugin} and {@link ExecutionOtelPlugin}. + * + *

Replaces the previous telescoping constructor overloads with a single named-field builder, giving readable, + * type-safe call sites and forward compatibility (new options are added as builder methods, not new constructors). This + * mirrors the {@code OtelPluginConfig} object in the JavaScript SDK and the {@code OtelPluginConfig} dataclass in the + * Python SDK for cross-SDK parity. + * + *

Construct via {@link #builder()} and pass to a plugin's {@code (SdkTracerProviderBuilder, OtelPluginConfig)} + * constructor: + * + *

{@code
+ * var config = OtelPluginConfig.builder()
+ *     .contextExtractor(new XRayContextExtractor())
+ *     .enableMdc(true)
+ *     .workflowSpanName("Workflow")
+ *     .instrumentationName("my-scope")
+ *     .build();
+ * var plugin = new InvocationOtelPlugin(tracerProviderBuilder, config);
+ * }
+ * + *

Defaults: {@code contextExtractor = new XRayContextExtractor()}, {@code enableMdc = true}, {@code workflowSpanName + * = "Workflow"}, {@code instrumentationName = "aws-durable-execution-sdk-java"}, {@code providerSource = + * ProviderSource.GLOBAL}. A {@code null} passed to any builder setter falls back to the corresponding default. + * + * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. + */ +@Deprecated +public final class OtelPluginConfig { + + static final String DEFAULT_INSTRUMENTATION_NAME = "aws-durable-execution-sdk-java"; + static final String DEFAULT_WORKFLOW_SPAN_NAME = "Workflow"; + + private final ContextExtractor contextExtractor; + private final boolean enableMdc; + private final String workflowSpanName; + private final String instrumentationName; + private final ProviderSource providerSource; + private final String otlpEndpoint; + private final Map otlpHeaders; + + private OtelPluginConfig(Builder builder) { + this.contextExtractor = + builder.contextExtractor != null ? builder.contextExtractor : new XRayContextExtractor(); + this.enableMdc = builder.enableMdc; + this.workflowSpanName = + builder.workflowSpanName != null ? builder.workflowSpanName : DEFAULT_WORKFLOW_SPAN_NAME; + this.instrumentationName = + builder.instrumentationName != null ? builder.instrumentationName : DEFAULT_INSTRUMENTATION_NAME; + this.providerSource = builder.providerSource != null ? builder.providerSource : ProviderSource.GLOBAL; + this.otlpEndpoint = builder.otlpEndpoint; + this.otlpHeaders = builder.otlpHeaders != null ? Map.copyOf(builder.otlpHeaders) : Map.of(); + } + + /** Returns a new builder with all fields defaulted. */ + public static Builder builder() { + return new Builder(); + } + + /** Returns a config with all default values. */ + public static OtelPluginConfig defaults() { + return new Builder().build(); + } + + /** The context extractor used to read parent trace context from the Lambda environment. */ + public ContextExtractor contextExtractor() { + return contextExtractor; + } + + /** Whether traceId/spanId/otelTraceSampled are injected into the SLF4J MDC for log correlation. */ + public boolean enableMdc() { + return enableMdc; + } + + /** The name used for the Workflow span. */ + public String workflowSpanName() { + return workflowSpanName; + } + + /** The instrumentation scope name registered with the tracer. */ + public String instrumentationName() { + return instrumentationName; + } + + /** + * The tracer-provider source to use when no {@code SdkTracerProviderBuilder} is supplied (the config-only + * constructors). {@link ProviderSource#GLOBAL} (the default) uses the globally configured (ADOT) provider; + * {@link ProviderSource#AUTO_OTLP} makes the plugin build and own an OTLP/HTTP provider. + * + *

{@link ProviderSource#EXPLICIT} is not valid here — it is implied by using a {@code (SdkTracerProviderBuilder, + * OtelPluginConfig)} constructor and is rejected by the config-only constructors. + */ + public ProviderSource providerSource() { + return providerSource; + } + + /** OTLP/HTTP endpoint for the auto-configured provider, or {@code null} to use the OTel default / env var. */ + public String otlpEndpoint() { + return otlpEndpoint; + } + + /** Extra headers sent by the auto-configured OTLP exporter (never {@code null}). */ + public Map otlpHeaders() { + return otlpHeaders; + } + + /** + * Builder for {@link OtelPluginConfig}. + * + * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. + */ + @Deprecated + public static final class Builder { + + private ContextExtractor contextExtractor; + private boolean enableMdc = true; + private String workflowSpanName; + private String instrumentationName; + private ProviderSource providerSource = ProviderSource.GLOBAL; + private String otlpEndpoint; + private Map otlpHeaders; + + private Builder() {} + + /** + * Sets the context extractor. Defaults to {@link XRayContextExtractor} when null. + * + * @param contextExtractor extracts parent trace context from the Lambda environment + * @return this builder + */ + public Builder contextExtractor(ContextExtractor contextExtractor) { + this.contextExtractor = contextExtractor; + return this; + } + + /** + * Sets whether to inject traceId/spanId/otelTraceSampled into the SLF4J MDC. Defaults to {@code true}. + * + * @param enableMdc if true, enables MDC log correlation + * @return this builder + */ + public Builder enableMdc(boolean enableMdc) { + this.enableMdc = enableMdc; + return this; + } + + /** + * Sets the Workflow span name. Defaults to {@code "Workflow"} when null. + * + * @param workflowSpanName the name for the Workflow span + * @return this builder + */ + public Builder workflowSpanName(String workflowSpanName) { + this.workflowSpanName = workflowSpanName; + return this; + } + + /** + * Sets the instrumentation scope name registered with the tracer. Defaults to + * {@code "aws-durable-execution-sdk-java"} when null. + * + * @param instrumentationName the instrumentation scope name + * @return this builder + */ + public Builder instrumentationName(String instrumentationName) { + this.instrumentationName = instrumentationName; + return this; + } + + /** + * Sets the tracer-provider source used when no {@code SdkTracerProviderBuilder} is supplied. Defaults to + * {@link ProviderSource#GLOBAL} (the globally configured ADOT provider); pass {@link ProviderSource#AUTO_OTLP} + * to make the plugin build and own an OTLP/HTTP provider. A {@code null} falls back to + * {@link ProviderSource#GLOBAL}. + * + *

{@link ProviderSource#EXPLICIT} is not accepted through the config-only constructors — supply a + * {@code SdkTracerProviderBuilder} via the two-arg constructor instead. + * + * @param providerSource the provider source, {@link ProviderSource#GLOBAL} or {@link ProviderSource#AUTO_OTLP} + * @return this builder + */ + public Builder providerSource(ProviderSource providerSource) { + this.providerSource = providerSource != null ? providerSource : ProviderSource.GLOBAL; + return this; + } + + /** + * Sets the OTLP/HTTP endpoint for the auto-configured provider. When null, the OTel default (or + * {@code OTEL_EXPORTER_OTLP_ENDPOINT}) is used. + * + * @param otlpEndpoint the OTLP/HTTP traces endpoint + * @return this builder + */ + public Builder otlpEndpoint(String otlpEndpoint) { + this.otlpEndpoint = otlpEndpoint; + return this; + } + + /** + * Sets extra headers for the auto-configured OTLP exporter (e.g. auth headers for a third-party endpoint). + * + * @param otlpHeaders header name/value pairs; null is treated as empty + * @return this builder + */ + public Builder otlpHeaders(Map otlpHeaders) { + this.otlpHeaders = otlpHeaders; + return this; + } + + /** Builds an immutable {@link OtelPluginConfig}. */ + public OtelPluginConfig build() { + return new OtelPluginConfig(this); + } + } +} diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginSupport.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginSupport.java index 1408b28cf..2f602a8c8 100644 --- a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginSupport.java +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/OtelPluginSupport.java @@ -3,9 +3,16 @@ package software.amazon.lambda.durable.otel; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.ServiceAttributes; import java.nio.file.Files; import java.nio.file.Path; import org.slf4j.Logger; @@ -45,6 +52,151 @@ static DeterministicIdGenerator createDefaultIdGenerator() { return new DeterministicIdGenerator(); } + /** + * Builds a plugin-owned {@link SdkTracerProvider} that exports over OTLP/HTTP (the {@link ProviderSource#AUTO_OTLP} + * default). Mirrors the auto-configured provider in the JavaScript and Python SDK plugins: an OTLP/HTTP exporter, a + * batch span processor, an env-driven sampler, Lambda resource attributes, and the deterministic ID generator. + * + * @param config the plugin configuration (endpoint + headers) + * @param idGenerator the deterministic ID generator to install + * @param additionalResource extra resource attributes to merge (e.g. ExecutionOtelPlugin's service.name), or null + */ + static SdkTracerProvider buildAutoOtlpProvider( + OtelPluginConfig config, DeterministicIdGenerator idGenerator, Resource additionalResource) { + var exporterBuilder = OtlpHttpSpanExporter.builder(); + var endpoint = resolveOtlpEndpoint(config); + if (endpoint != null) { + exporterBuilder.setEndpoint(endpoint); + } + for (var header : config.otlpHeaders().entrySet()) { + exporterBuilder.addHeader(header.getKey(), header.getValue()); + } + + var resource = buildLambdaResource(); + if (additionalResource != null) { + resource = resource.merge(additionalResource); + } + + return SdkTracerProvider.builder() + .setIdGenerator(idGenerator) + .setSampler(resolveSampler()) + .setResource(resource) + .addSpanProcessor( + BatchSpanProcessor.builder(exporterBuilder.build()).build()) + .build(); + } + + /** + * The tracer provider, tracer, and ID generator resolved for a config-only plugin constructor, plus the + * {@link ProviderSource} that produced them. + * + * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. + */ + @Deprecated + record ProviderSetup( + ProviderSource source, + SdkTracerProvider sdkTracerProvider, + Tracer tracer, + DeterministicIdGenerator idGenerator) {} + + /** + * Resolves the tracer provider for the config-only plugin constructors from + * {@link OtelPluginConfig#providerSource()}, centralizing the {@link ProviderSource} branching shared by + * {@link InvocationOtelPlugin} and {@link ExecutionOtelPlugin}: + * + *

+ * + * @param config the plugin configuration + * @param pluginName the plugin name used in diagnostics/flush logging + * @return the resolved provider, tracer, ID generator, and source + * @throws IllegalArgumentException if {@code config.providerSource()} is {@link ProviderSource#EXPLICIT} + */ + static ProviderSetup resolveConfiguredProvider(OtelPluginConfig config, String pluginName) { + return switch (config.providerSource()) { + case GLOBAL -> { + var idGenerator = createDefaultIdGenerator(); + var tracerProvider = getDefaultTracerProvider(pluginName); + yield new ProviderSetup( + ProviderSource.GLOBAL, + getSdkTracerProviderForFlush(tracerProvider, pluginName), + tracerProvider.get(config.instrumentationName()), + idGenerator); + } + case AUTO_OTLP -> { + var idGenerator = new DeterministicIdGenerator(); + var sdkTracerProvider = buildAutoOtlpProvider(config, idGenerator, null); + yield new ProviderSetup( + ProviderSource.AUTO_OTLP, + sdkTracerProvider, + sdkTracerProvider.get(config.instrumentationName()), + idGenerator); + } + case EXPLICIT -> + throw new IllegalArgumentException( + "OtelPluginConfig.providerSource(EXPLICIT) requires a caller-supplied SdkTracerProviderBuilder; " + + "use the (SdkTracerProviderBuilder, OtelPluginConfig) constructor."); + }; + } + + /** Resolves the OTLP/HTTP traces endpoint (config -> env -> exporter default), appending the signal path. */ + private static String resolveOtlpEndpoint(OtelPluginConfig config) { + if (config.otlpEndpoint() != null && !config.otlpEndpoint().isBlank()) { + return config.otlpEndpoint(); + } + var envEndpoint = System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"); + if (envEndpoint != null && !envEndpoint.isBlank()) { + var base = envEndpoint.endsWith("/") ? envEndpoint.substring(0, envEndpoint.length() - 1) : envEndpoint; + return base.endsWith("/v1/traces") ? base : base + "/v1/traces"; + } + // null -> the OTLP/HTTP exporter's own default (http://localhost:4318/v1/traces) + return null; + } + + /** Builds the sampler from {@code OTEL_DURABLE_SAMPLING_RATIO}, falling back to always-on. */ + private static Sampler resolveSampler() { + var raw = System.getenv("OTEL_DURABLE_SAMPLING_RATIO"); + if (raw != null) { + try { + var ratio = Double.parseDouble(raw); + if (ratio >= 0.0 && ratio <= 1.0) { + return Sampler.traceIdRatioBased(ratio); + } + } catch (NumberFormatException ignored) { + // fall through to always-on + } + } + return Sampler.alwaysOn(); + } + + /** Builds Lambda resource attributes from AWS_* env vars, merged onto the default resource. */ + private static Resource buildLambdaResource() { + var functionName = System.getenv("AWS_LAMBDA_FUNCTION_NAME"); + if (functionName == null || functionName.isBlank()) { + return Resource.getDefault(); + } + var attributes = Attributes.builder() + .put(ServiceAttributes.SERVICE_NAME, functionName) + .put("faas.name", functionName) + .put("cloud.provider", "aws") + .put("cloud.platform", "aws_lambda"); + var region = System.getenv("AWS_REGION"); + if (region != null && !region.isBlank()) { + attributes.put("cloud.region", region); + } + var version = System.getenv("AWS_LAMBDA_FUNCTION_VERSION"); + if (version != null && !version.isBlank()) { + attributes.put("faas.version", version); + } + return Resource.getDefault().merge(Resource.create(attributes.build())); + } + /** Extracts trace context from the current OTel span (fallback when X-Ray header is unavailable). */ static ExtractedContext extractCurrentSpanContext() { var spanContext = Span.current().getSpanContext(); diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ProviderSource.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ProviderSource.java new file mode 100644 index 000000000..4fb164050 --- /dev/null +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/ProviderSource.java @@ -0,0 +1,34 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.otel; + +/** + * Which of the three resolution tiers produced a plugin's tracer provider. + * + *

Mirrors the {@code ProviderSource} used by the JavaScript and Python SDK OTel plugins for cross-SDK parity: + * + *

+ * + *

This is the single knob that selects a plugin's tracer provider. {@link OtelPluginConfig#providerSource()} carries + * it for the config-only constructors; the {@code (SdkTracerProviderBuilder, OtelPluginConfig)} constructors always + * report {@link #EXPLICIT}. + * + * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. + */ +@Deprecated +public enum ProviderSource { + /** Caller-supplied {@code SdkTracerProviderBuilder}; plugin-owned. */ + EXPLICIT, + /** Globally configured provider (ADOT Java agent); not plugin-owned. */ + GLOBAL, + /** Auto-configured OTLP/HTTP provider; plugin-owned. */ + AUTO_OTLP +} diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java index a693be2e4..605bbaff8 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java @@ -39,9 +39,11 @@ void setUp() { SdkTracerProvider.builder() .setResource(resource) .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - false, - "Workflow"); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("Workflow") + .build()); } @AfterEach @@ -53,6 +55,73 @@ void tearDown() { // ─── Default constructor ───────────────────────────────────────────── + @Test + void customInstrumentationName_isUsedForTracerScope() { + var exporter = InMemorySpanExporter.create(); + var customPlugin = new ExecutionOtelPlugin( + SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)), + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("Workflow") + .instrumentationName("my-custom-scope") + .build()); + customPlugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); + customPlugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); + + var spans = exporter.getFinishedSpanItems(); + assertFalse(spans.isEmpty()); + for (var span : spans) { + assertEquals("my-custom-scope", span.getInstrumentationScopeInfo().getName()); + } + } + + @Test + void configOnlyConstructor_defaultsToGlobalProvider() { + OtelPluginAutoConfigurationState.markInstalled(); + GlobalOpenTelemetry.resetForTest(); + OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .buildAndRegisterGlobal(); + + var plugin = new ExecutionOtelPlugin(OtelPluginConfig.defaults()); + assertEquals(ProviderSource.GLOBAL, plugin.providerSource()); + } + + @Test + void configWithAutoOtlp_buildsPluginOwnedProvider() { + var plugin = new ExecutionOtelPlugin(OtelPluginConfig.builder() + .providerSource(ProviderSource.AUTO_OTLP) + .build()); + assertEquals(ProviderSource.AUTO_OTLP, plugin.providerSource()); + } + + @Test + void builderConstructor_isExplicitSource() { + var plugin = new ExecutionOtelPlugin(SdkTracerProvider.builder(), OtelPluginConfig.defaults()); + assertEquals(ProviderSource.EXPLICIT, plugin.providerSource()); + } + + @Test + void configProviderSource_defaultsToGlobalAndHonorsAutoOtlp() { + assertEquals(ProviderSource.GLOBAL, OtelPluginConfig.defaults().providerSource()); + assertEquals( + ProviderSource.AUTO_OTLP, + OtelPluginConfig.builder() + .providerSource(ProviderSource.AUTO_OTLP) + .build() + .providerSource()); + } + + @Test + void configOnlyConstructor_rejectsExplicitProviderSource() { + var config = OtelPluginConfig.builder() + .providerSource(ProviderSource.EXPLICIT) + .build(); + var error = assertThrows(IllegalArgumentException.class, () -> new ExecutionOtelPlugin(config)); + assertTrue(error.getMessage().contains("SdkTracerProviderBuilder")); + } + @Test void defaultConstructor_throwsWhenAutoConfigurationCustomizerProviderIsNotInstalled() { GlobalOpenTelemetry.resetForTest(); @@ -623,9 +692,11 @@ void deterministicWorkflowSpanId_stableAcrossInvocations() { var exporter2 = InMemorySpanExporter.create(); var plugin2 = new ExecutionOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter2)), - () -> null, - false, - "Workflow"); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("Workflow") + .build()); plugin2.onInvocationStart(new InvocationInfo("req-9", ARN, true, Instant.now())); plugin2.onInvocationEnd(new InvocationEndInfo("req-9", ARN, true, InvocationStatus.SUCCEEDED, null)); var secondWorkflowSpanId = @@ -646,9 +717,11 @@ void sampling_disabled_producesNoSpans() { SdkTracerProvider.builder() .setSampler(io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOff()) .addSpanProcessor(SimpleSpanProcessor.create(exporter)), - () -> null, - false, - "Workflow"); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("Workflow") + .build()); sampledPlugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); sampledPlugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); assertTrue(exporter.getFinishedSpanItems().isEmpty(), "No spans should be exported with 0% sampling"); @@ -662,9 +735,11 @@ void xrayExtraction_allSpansShareExtractedTraceId() { var exporter = InMemorySpanExporter.create(); var xrayPlugin = new ExecutionOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)), - () -> new ExtractedContext(xrayTraceId, null), - false, - "Workflow"); + OtelPluginConfig.builder() + .contextExtractor(() -> new ExtractedContext(xrayTraceId, null)) + .enableMdc(false) + .workflowSpanName("Workflow") + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); xrayPlugin.onOperationStart( new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginIntegrationTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginIntegrationTest.java index d4e85c241..2266b5a94 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginIntegrationTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginIntegrationTest.java @@ -46,8 +46,10 @@ void setUp() { var plugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .build()); otelConfig = DurableConfig.builder().withPlugins(plugin).build(); } @@ -326,8 +328,10 @@ void sampling_off_producesNoSpans() { SdkTracerProvider.builder() .setSampler(Sampler.alwaysOff()) .addSpanProcessor(SimpleSpanProcessor.create(sampledExporter)), - () -> null, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .build()); var noSampleConfig = DurableConfig.builder().withPlugins(noSamplePlugin).build(); diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java index 020e3cbbe..d14d8140c 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java @@ -51,8 +51,10 @@ void setUp() { plugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .build()); } @AfterEach @@ -235,6 +237,74 @@ void invocationStart_and_end_createsSpan() { assertEquals(StatusCode.OK, span.getStatus().getStatusCode()); } + @Test + void customInstrumentationName_isUsedForTracerScope() { + var exporter = InMemorySpanExporter.create(); + var customPlugin = new InvocationOtelPlugin( + SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)), + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("Workflow") + .instrumentationName("my-custom-scope") + .build()); + customPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); + customPlugin.onInvocationEnd( + new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); + + var spans = exporter.getFinishedSpanItems(); + assertFalse(spans.isEmpty()); + for (var span : spans) { + assertEquals("my-custom-scope", span.getInstrumentationScopeInfo().getName()); + } + } + + @Test + void configOnlyConstructor_defaultsToGlobalProvider() { + OtelPluginAutoConfigurationState.markInstalled(); + GlobalOpenTelemetry.resetForTest(); + OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .buildAndRegisterGlobal(); + + var plugin = new InvocationOtelPlugin(OtelPluginConfig.defaults()); + assertEquals(ProviderSource.GLOBAL, plugin.providerSource()); + } + + @Test + void configWithAutoOtlp_buildsPluginOwnedProvider() { + var plugin = new InvocationOtelPlugin(OtelPluginConfig.builder() + .providerSource(ProviderSource.AUTO_OTLP) + .build()); + assertEquals(ProviderSource.AUTO_OTLP, plugin.providerSource()); + } + + @Test + void builderConstructor_isExplicitSource() { + var plugin = new InvocationOtelPlugin(SdkTracerProvider.builder(), OtelPluginConfig.defaults()); + assertEquals(ProviderSource.EXPLICIT, plugin.providerSource()); + } + + @Test + void configProviderSource_defaultsToGlobalAndHonorsAutoOtlp() { + assertEquals(ProviderSource.GLOBAL, OtelPluginConfig.defaults().providerSource()); + assertEquals( + ProviderSource.AUTO_OTLP, + OtelPluginConfig.builder() + .providerSource(ProviderSource.AUTO_OTLP) + .build() + .providerSource()); + } + + @Test + void configOnlyConstructor_rejectsExplicitProviderSource() { + var config = OtelPluginConfig.builder() + .providerSource(ProviderSource.EXPLICIT) + .build(); + var error = assertThrows(IllegalArgumentException.class, () -> new InvocationOtelPlugin(config)); + assertTrue(error.getMessage().contains("SdkTracerProviderBuilder")); + } + @Test void invocationSpan_hasInternalKind() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); @@ -675,8 +745,10 @@ void sampling_disabled_producesNoSpans() { SdkTracerProvider.builder() .setSampler(io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOff()) .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .build()); sampledPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); sampledPlugin.onUserFunctionStart( @@ -701,8 +773,10 @@ void xrayExtraction_usesExtractedTraceId_overArnDerived() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); @@ -720,8 +794,10 @@ void xrayExtraction_allSpansShareExtractedTraceId() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onOperationStart( @@ -750,8 +826,10 @@ void xrayExtraction_withParentSpanId_invocationSpanHasCorrectParent() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); @@ -775,8 +853,10 @@ void xrayExtraction_withoutParentSpanId_invocationSpanIsRoot() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); @@ -805,8 +885,10 @@ void xrayExtraction_multipleInvocations_sameTraceId_unifiedTrace() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); // First invocation xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); @@ -839,8 +921,10 @@ void xrayExtraction_nullExtractor_fallsBackToArnDerived() { spanExporter = InMemorySpanExporter.create(); var noXrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .build()); var arn = "arn:aws:lambda:us-east-1:123:function:test:$LATEST/durable/exec1"; noXrayPlugin.onInvocationStart(new InvocationInfo("req-1", arn, true, Instant.now())); @@ -870,8 +954,10 @@ void xrayExtraction_extractedTraceIdMatchesXrayConversion() { spanExporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> extractedContext, - false); + OtelPluginConfig.builder() + .contextExtractor(() -> extractedContext) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); @@ -1313,8 +1399,11 @@ void operationLinksToWorkflow_withXRayContext() { var exporter = InMemorySpanExporter.create(); var xrayPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)), - () -> new ExtractedContext("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8"), - false); + OtelPluginConfig.builder() + .contextExtractor( + () -> new ExtractedContext("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8")) + .enableMdc(false) + .build()); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onOperationStart( new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); @@ -1342,9 +1431,11 @@ void workflowSpanName_isConfigurable() { var exporter = InMemorySpanExporter.create(); var customPlugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)), - () -> null, - false, - "MyWorkflow"); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(false) + .workflowSpanName("MyWorkflow") + .build()); customPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); customPlugin.onInvocationEnd( new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/MdcSpanEnricherTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/MdcSpanEnricherTest.java index 747aee695..2318e4075 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/MdcSpanEnricherTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/MdcSpanEnricherTest.java @@ -58,8 +58,10 @@ void plugin_withMdcEnabled_setsFieldsInMdc() { var plugin = new InvocationOtelPlugin( SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spanExporter)), - () -> null, - true); + OtelPluginConfig.builder() + .contextExtractor(() -> null) + .enableMdc(true) + .build()); plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec-mdc-test", true, Instant.now()));