@@ -119,6 +120,7 @@
2.3.0
1.5.0
0.11.5
+ 1.65.0
diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java
index 6dc4e9d9f..cc8ea39e9 100644
--- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java
+++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/SystemWrapper.java
@@ -22,6 +22,10 @@ public static String getenv(String name) {
return System.getenv(name);
}
+ public static boolean containsKey(String key) {
+ return System.getenv().containsKey(key);
+ }
+
public static String getProperty(String name) {
return System.getProperty(name);
}
diff --git a/powertools-tracing-opentelemetry/pom.xml b/powertools-tracing-opentelemetry/pom.xml
new file mode 100644
index 000000000..6fe91dbcc
--- /dev/null
+++ b/powertools-tracing-opentelemetry/pom.xml
@@ -0,0 +1,151 @@
+
+
+
+ 4.0.0
+
+ powertools-tracing-opentelemetry
+ jar
+
+
+ software.amazon.lambda
+ powertools-parent
+ 2.10.0
+
+
+ Powertools for AWS Lambda (Java) - Tracing OpenTelemetry
+
+ A suite of utilities for AWS Lambda Functions that makes tracing with OpenTelemetry, structured logging and
+ creating custom metrics asynchronously easier.
+
+
+
+
+ io.opentelemetry
+ opentelemetry-api
+ ${opentelemetry-api.version}
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+ ${opentelemetry-api.version}
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+ ${opentelemetry-api.version}
+
+
+ org.aspectj
+ aspectjrt
+ provided
+
+
+ software.amazon.lambda
+ powertools-common
+
+
+ software.amazon.awssdk
+ aws-core
+
+
+ software.amazon.awssdk
+ sdk-core
+
+
+ com.amazonaws
+ aws-lambda-java-core
+
+
+ com.amazonaws
+ aws-lambda-java-events
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ ${opentelemetry-api.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ software.amazon.lambda
+ powertools-common
+ ${project.version}
+ test-jar
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+ org.junit-pioneer
+ junit-pioneer
+ test
+
+
+ org.apache.commons
+ commons-lang3
+ test
+
+
+ org.aspectj
+ aspectjweaver
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ on-demand
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/CaptureMode.java b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/CaptureMode.java
new file mode 100644
index 000000000..d62c3b1ff
--- /dev/null
+++ b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/CaptureMode.java
@@ -0,0 +1,32 @@
+package software.amazon.lambda.powertools.tracing.opentelemetry;
+
+/**
+ * Defines how method responses and errors are captured by tracing.
+ */
+public enum CaptureMode {
+
+ /**
+ * Capture response and errors according to environment variables.
+ */
+ ENVIRONMENT_VAR,
+
+ /**
+ * Capture the method response.
+ */
+ RESPONSE,
+
+ /**
+ * Capture errors thrown by the method.
+ */
+ ERROR,
+
+ /**
+ * Capture both the method response and errors.
+ */
+ RESPONSE_AND_ERROR,
+
+ /**
+ * Disable response and error capture.
+ */
+ DISABLED
+}
\ No newline at end of file
diff --git a/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/Tracing.java b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/Tracing.java
new file mode 100644
index 000000000..2d4d68608
--- /dev/null
+++ b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/Tracing.java
@@ -0,0 +1,43 @@
+package software.amazon.lambda.powertools.tracing.opentelemetry;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to enable OpenTelemetry tracing for the annotated method.
+ * Automatically creates and manages an OpenTelemetry span for the method invocation.
+ *
+ * This annotation allows configuration of the namespace, span name, and capture mode
+ * for tracing purposes. If no explicit configuration is provided, default values are used.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Tracing {
+ /**
+ * The namespace associated with the span.
+ *
+ *
If empty, the default Powertools service name is used.
+ *
+ * @return the namespace
+ */
+ String namespace() default "";
+
+ /**
+ * The name of the span.
+ *
+ *
If empty, the annotated method name is used.
+ *
+ * @return the span name
+ */
+ String spanName() default "";
+
+ /**
+ * Controls whether the method response and/or errors are captured
+ * as span data.
+ *
+ * @return the capture mode
+ */
+ CaptureMode captureMode() default CaptureMode.ENVIRONMENT_VAR;
+}
diff --git a/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/TracingOpenTelemetry.java b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/TracingOpenTelemetry.java
new file mode 100644
index 000000000..2e41050c8
--- /dev/null
+++ b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/TracingOpenTelemetry.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.tracing.opentelemetry;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.context.propagation.TextMapSetter;
+import java.util.Objects;
+import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor;
+import software.amazon.lambda.powertools.tracing.opentelemetry.context.LambdaEventContextExtractorResolver;
+import software.amazon.lambda.powertools.tracing.opentelemetry.internal.AttributesConstants;
+import software.amazon.lambda.powertools.tracing.opentelemetry.internal.SpanOperation;
+import software.amazon.lambda.powertools.tracing.opentelemetry.internal.SpanScope;
+import software.amazon.lambda.powertools.tracing.opentelemetry.provider.OpenTelemetryProvider;
+
+
+public final class TracingOpenTelemetry {
+
+ private final Tracer tracer;
+ private final TextMapPropagator propagator;
+ private final LambdaEventContextExtractorResolver eventContextExtractorResolver;
+
+ private TracingOpenTelemetry(Builder builder) {
+ this.tracer = Objects.requireNonNull(
+ builder.tracer,
+ "tracer must not be null"
+ );
+ this.propagator = Objects.requireNonNull(
+ builder.propagator,
+ "propagator must not be null"
+ );
+ this.eventContextExtractorResolver = Objects.requireNonNull(
+ builder.eventContextExtractorResolver,
+ "eventContextExtractorResolver must not be null"
+ );
+ }
+
+ public TracingOpenTelemetry() {
+ this(OpenTelemetryProvider.tracer());
+ }
+
+
+ public TracingOpenTelemetry(Tracer tracer) {
+ this(tracer, createDefaultPropagator(), createDefaultEventContextExtractorResolver());
+ }
+
+
+ public TracingOpenTelemetry(
+ Tracer tracer,
+ TextMapPropagator propagator,
+ LambdaEventContextExtractorResolver eventContextExtractorResolver) {
+
+ this.tracer = Objects.requireNonNull(
+ tracer,
+ "tracer must not be null"
+ );
+ this.propagator = Objects.requireNonNull(
+ propagator,
+ "propagator must not be null"
+ );
+ this.eventContextExtractorResolver = Objects.requireNonNull(
+ eventContextExtractorResolver,
+ "eventContextExtractorResolver must not be null"
+ );
+ }
+
+ public TextMapPropagator propagator() {
+ return propagator;
+ }
+
+ public LambdaEventContextExtractorResolver eventContextExtractorResolver() {
+ return eventContextExtractorResolver;
+ }
+
+ public Span currentSpan() {
+ return Span.current();
+ }
+
+
+ public SpanScope addSpan(String name) {
+ return addSpan(name, SpanKind.INTERNAL);
+ }
+
+
+ public SpanScope addSpan(
+ String name,
+ SpanKind kind) {
+
+ return addSpan(name, kind, Attributes.empty());
+ }
+
+
+ public SpanScope addSpan(
+ String name,
+ SpanKind kind,
+ Attributes attributes) {
+
+ Objects.requireNonNull(name, "name must not be null");
+ Objects.requireNonNull(kind, "kind must not be null");
+ Objects.requireNonNull(attributes, "attributes must not be null");
+
+ Span span = tracer.spanBuilder(name)
+ .setSpanKind(kind)
+ .setAllAttributes(attributes)
+ .startSpan();
+
+ return new SpanScope(span);
+ }
+
+ public SpanScope addSpan(
+ String name,
+ SpanKind kind,
+ Attributes attributes,
+ Context parentContext) {
+
+ Objects.requireNonNull(parentContext, "parentContext must not be null");
+
+ Span span = tracer.spanBuilder(name)
+ .setParent(parentContext)
+ .setSpanKind(kind)
+ .setAllAttributes(attributes)
+ .startSpan();
+
+ return new SpanScope(span);
+ }
+
+
+ public T withSpan(
+ String name,
+ SpanOperation operation) throws Exception {
+
+ Objects.requireNonNull(operation, "operation must not be null");
+
+ try (SpanScope scope = addSpan(name)) {
+ try {
+ return operation.execute(scope.span());
+ } catch (Exception exception) {
+ scope.recordException(exception);
+ throw exception;
+ }
+ }
+ }
+
+ public T captureLambdaHandler(
+ String name,
+ com.amazonaws.services.lambda.runtime.Context lambdaContext,
+ io.opentelemetry.context.Context parentContext,
+ SpanOperation operation) throws Exception {
+
+ Objects.requireNonNull(name, "name must not be null");
+ Objects.requireNonNull(parentContext, "parentContext must not be null");
+ Objects.requireNonNull(operation, "operation must not be null");
+
+ Span span = tracer.spanBuilder(name)
+ .setParent(parentContext)
+ .setSpanKind(SpanKind.SERVER)
+ .setAttribute(AttributesConstants.AWS_LAMBDA_FUNCTION_ARN, LambdaHandlerProcessor.isColdStart())
+ .setAttribute(AttributesConstants.FAAS_INVOCATION_ID, lambdaContext.getAwsRequestId())
+ .startSpan();
+
+ try (SpanScope scope = new SpanScope(span)) {
+ try {
+ T result = operation.execute(span);
+
+ LambdaHandlerProcessor.coldStartDone();
+
+ return result;
+ } catch (Exception exception) {
+ scope.recordException(exception);
+ throw exception;
+ }
+ }
+ }
+
+ public Context extractContext(
+ T carrier,
+ TextMapGetter getter) {
+
+ return extractContext(Context.current(), carrier, getter);
+ }
+
+
+ public Context extractContext(
+ Context context,
+ T carrier,
+ TextMapGetter getter) {
+
+ Objects.requireNonNull(context, "context must not be null");
+ Objects.requireNonNull(getter, "getter must not be null");
+
+ return propagator.extract(
+ context,
+ carrier,
+ getter
+ );
+ }
+
+
+ public void injectContext(
+ T carrier,
+ TextMapSetter setter) {
+
+ injectContext(Context.current(), carrier, setter);
+ }
+
+
+ public void injectContext(
+ Context context,
+ T carrier,
+ TextMapSetter setter) {
+
+ Objects.requireNonNull(context, "context must not be null");
+ Objects.requireNonNull(setter, "setter must not be null");
+
+ propagator.inject(
+ context,
+ carrier,
+ setter
+ );
+ }
+
+ private static TextMapPropagator createDefaultPropagator() {
+ return OpenTelemetryProvider.propagator();
+ }
+
+ private static LambdaEventContextExtractorResolver createDefaultEventContextExtractorResolver() {
+ return LambdaEventContextExtractorResolver.create();
+ }
+
+
+ public static TracingOpenTelemetry create() {
+ return new TracingOpenTelemetry();
+ }
+
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+
+ private Tracer tracer;
+ private TextMapPropagator propagator = createDefaultPropagator();
+ private LambdaEventContextExtractorResolver eventContextExtractorResolver =
+ createDefaultEventContextExtractorResolver();
+
+ public Builder tracer(Tracer tracer) {
+ this.tracer = tracer;
+ return this;
+ }
+
+ public Builder propagator(TextMapPropagator propagator) {
+ this.propagator = propagator;
+ return this;
+ }
+
+ public Builder eventContextExtractorResolver(
+ LambdaEventContextExtractorResolver eventContextExtractorResolver) {
+ this.eventContextExtractorResolver = eventContextExtractorResolver;
+ return this;
+ }
+
+ public TracingOpenTelemetry build() {
+ return new TracingOpenTelemetry(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/context/ApiGatewayTraceContextExtractor.java b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/context/ApiGatewayTraceContextExtractor.java
new file mode 100644
index 000000000..5a2381cf1
--- /dev/null
+++ b/powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/context/ApiGatewayTraceContextExtractor.java
@@ -0,0 +1,119 @@
+package software.amazon.lambda.powertools.tracing.opentelemetry.context;
+
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class ApiGatewayTraceContextExtractor implements LambdaEventContextExtractor {
+ private static final TextMapGetter