diff --git a/awsagentprovider/build.gradle.kts b/awsagentprovider/build.gradle.kts
index 7e9211052e..6b9b75e3d5 100644
--- a/awsagentprovider/build.gradle.kts
+++ b/awsagentprovider/build.gradle.kts
@@ -41,9 +41,12 @@ dependencies {
// Import AWS SDK v1 core for ARN parsing utilities
implementation("com.amazonaws:aws-java-sdk-core:1.12.773")
// Export configuration
- compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
+ implementation("io.opentelemetry:opentelemetry-exporter-otlp")
// For Udp emitter
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp-common")
+ // For HTTP SigV4 emitter
+ implementation("software.amazon.awssdk:auth:2.30.14")
+ implementation("software.amazon.awssdk:http-auth-aws:2.30.14")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
index b3d04a7a8c..9f023c119f 100644
--- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
@@ -51,6 +51,7 @@
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
/**
* This customizer performs the following customizations:
@@ -70,6 +71,8 @@
public class AwsApplicationSignalsCustomizerProvider
implements AutoConfigurationCustomizerProvider {
static final String AWS_LAMBDA_FUNCTION_NAME_CONFIG = "AWS_LAMBDA_FUNCTION_NAME";
+ private static final String XRAY_OTLP_ENDPOINT_PATTERN =
+ "^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$";
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
private static final Logger logger =
@@ -121,6 +124,16 @@ static boolean isLambdaEnvironment() {
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) != null;
}
+ static boolean isXrayOtlpEndpoint(String otlpEndpoint) {
+ if (otlpEndpoint == null) {
+ return false;
+ }
+
+ return Pattern.compile(XRAY_OTLP_ENDPOINT_PATTERN)
+ .matcher(otlpEndpoint.toLowerCase())
+ .matches();
+ }
+
private boolean isApplicationSignalsEnabled(ConfigProperties configProps) {
return configProps.getBoolean(
APPLICATION_SIGNALS_ENABLED_CONFIG,
@@ -221,6 +234,10 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
return tracerProviderBuilder;
}
+ if (isXrayOtlpEndpoint(System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG))) {
+ return tracerProviderBuilder;
+ }
+
// Construct meterProvider
MetricExporter metricsExporter =
ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps);
@@ -286,6 +303,14 @@ private SpanExporter customizeSpanExporter(
.build();
}
}
+ // When running OTLP endpoint for X-Ray backend, use custom exporter for SigV4 authentication
+ else if (spanExporter instanceof OtlpHttpSpanExporter
+ && isXrayOtlpEndpoint(System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG))) {
+ spanExporter =
+ new OtlpAwsSpanExporter(
+ (OtlpHttpSpanExporter) spanExporter,
+ System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG));
+ }
if (isApplicationSignalsEnabled(configProps)) {
return AwsMetricAttributesSpanExporterBuilder.create(
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java
new file mode 100644
index 0000000000..c4a777dfe5
--- /dev/null
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.opentelemetry.javaagent.providers;
+
+import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler;
+import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import javax.annotation.concurrent.Immutable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;
+import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
+
+/**
+ * This exporter extends the functionality of the OtlpHttpSpanExporter to allow spans to be exported
+ * to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the AWSSDK
+ * library to sign and directly inject SigV4 Authentication to the exported request's headers. ...
+ */
+@Immutable
+public class OtlpAwsSpanExporter implements SpanExporter {
+ private static final String SERVICE_NAME = "xray";
+ private static final Logger logger = LoggerFactory.getLogger(OtlpAwsSpanExporter.class);
+
+ private final OtlpHttpSpanExporter parentExporter;
+ private final String awsRegion;
+ private final String endpoint;
+ private Collection spanData;
+
+ public OtlpAwsSpanExporter(String endpoint) {
+ this.parentExporter =
+ OtlpHttpSpanExporter.builder()
+ .setEndpoint(endpoint)
+ .setHeaders(new SigV4AuthHeaderSupplier())
+ .build();
+
+ this.awsRegion = endpoint.split("\\.")[1];
+ this.endpoint = endpoint;
+ this.spanData = new ArrayList<>();
+ }
+
+ public OtlpAwsSpanExporter(OtlpHttpSpanExporter parentExporter, String endpoint) {
+ this.parentExporter =
+ parentExporter.toBuilder()
+ .setEndpoint(endpoint)
+ .setHeaders(new SigV4AuthHeaderSupplier())
+ .build();
+
+ this.awsRegion = endpoint.split("\\.")[1];
+ this.endpoint = endpoint;
+ this.spanData = new ArrayList<>();
+ }
+
+ /**
+ * Overrides the upstream implementation of export. All behaviors are the same except if the
+ * endpoint is an XRay OTLP endpoint, we will sign the request with SigV4 in headers before
+ * sending it to the endpoint. Otherwise, we will skip signing.
+ */
+ @Override
+ public CompletableResultCode export(Collection spans) {
+ this.spanData = spans;
+ return this.parentExporter.export(spans);
+ }
+
+ @Override
+ public CompletableResultCode flush() {
+ return this.parentExporter.flush();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ return this.parentExporter.shutdown();
+ }
+
+ @Override
+ public String toString() {
+ return this.parentExporter.toString();
+ }
+
+ private final class SigV4AuthHeaderSupplier implements Supplier