Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b629da3
added sigv4 authentication to otlp exporter
liustve Feb 5, 2025
bd4e1d6
added unit tests
liustve Feb 6, 2025
7187839
removed logging
liustve Feb 6, 2025
6422607
more testing
liustve Feb 7, 2025
6462b66
Merge branch 'aws-observability:main' into sigv4_support
liustve Feb 10, 2025
a34e899
added extra test
liustve Feb 11, 2025
5fc6cfb
fixing sanitation issue
liustve Feb 11, 2025
db6d384
formatting
liustve Feb 11, 2025
579efb3
fix arbitrary url error
liustve Feb 11, 2025
f97fd24
linting imports
liustve Feb 11, 2025
451f194
linting fix
liustve Feb 11, 2025
6ce4d68
linting fix
liustve Feb 11, 2025
364f9de
linting fix
liustve Feb 11, 2025
f217ed1
lint fix
liustve Feb 11, 2025
5637278
linting fix
liustve Feb 11, 2025
8e1d0eb
lint fix
liustve Feb 11, 2025
bb591a2
linting fix
liustve Feb 11, 2025
ad4c0a0
linting fix
liustve Feb 11, 2025
f943b07
made botocore an optional dependency if not using otlp cw endpoint
liustve Feb 11, 2025
c796162
comments + linting fix
liustve Feb 11, 2025
0b65642
linting fix
liustve Feb 11, 2025
561fa01
linting fix
liustve Feb 11, 2025
4a46f99
Merge branch 'main' into sigv4_support
liustve Feb 12, 2025
bba778d
addressing comments
liustve Feb 12, 2025
1c8004c
linting fix
liustve Feb 12, 2025
de0e89f
linting fix
liustve Feb 12, 2025
c207167
tests + linting fix
liustve Feb 12, 2025
556b037
renaming
liustve Feb 12, 2025
b7749f8
lint
liustve Feb 12, 2025
e9bf1f1
linting + test fix
liustve Feb 12, 2025
56d7470
linting fix
liustve Feb 12, 2025
532ab25
linting fix
liustve Feb 12, 2025
6cb0f55
fixed test
liustve Feb 13, 2025
9a47ab3
lint fix + test fix
liustve Feb 13, 2025
407bcdc
linting fix
liustve Feb 13, 2025
46d7586
changed to broader exception
liustve Feb 13, 2025
01e74f8
linting fix
liustve Feb 13, 2025
667ac52
removed is xray otlp endpoint validation in the span exporter
liustve Feb 13, 2025
1b33012
linting fix
liustve Feb 13, 2025
c7d8410
removed unused import
liustve Feb 13, 2025
5aa970e
Merge branch 'main' into sigv4_support
liustve Feb 13, 2025
6cf44bd
removed validation for aws region
liustve Feb 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import re


def is_otlp_endpoint_cloudwatch(otlp_endpoint=None):
# Detects if it's the OTLP endpoint in CloudWatchs
if not otlp_endpoint:
return False

pattern = r"https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$"

return bool(re.match(pattern, otlp_endpoint.lower()))
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from amazon.opentelemetry.distro._aws_attribute_keys import AWS_LOCAL_SERVICE
from amazon.opentelemetry.distro._aws_resource_attribute_configurator import get_service_attribute
from amazon.opentelemetry.distro._utils import is_otlp_endpoint_cloudwatch
from amazon.opentelemetry.distro.always_record_sampler import AlwaysRecordSampler
from amazon.opentelemetry.distro.attribute_propagating_span_processor_builder import (
AttributePropagatingSpanProcessorBuilder,
Expand All @@ -19,6 +20,7 @@
AwsMetricAttributesSpanExporterBuilder,
)
from amazon.opentelemetry.distro.aws_span_metrics_processor_builder import AwsSpanMetricsProcessorBuilder
from amazon.opentelemetry.distro.otlp_sigv4_exporter import OTLPAwsSigV4Exporter
from amazon.opentelemetry.distro.otlp_udp_exporter import OTLPUdpSpanExporter
from amazon.opentelemetry.distro.sampler.aws_xray_remote_sampler import AwsXRayRemoteSampler
from amazon.opentelemetry.distro.scope_based_exporter import ScopeBasedPeriodicExportingMetricReader
Expand Down Expand Up @@ -315,6 +317,9 @@ def _customize_exporter(span_exporter: SpanExporter, resource: Resource) -> Span
traces_endpoint = os.environ.get(AWS_XRAY_DAEMON_ADDRESS_CONFIG, "127.0.0.1:2000")
span_exporter = OTLPUdpSpanExporter(endpoint=traces_endpoint)

if is_otlp_endpoint_cloudwatch(os.environ.get(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)):
span_exporter = OTLPAwsSigV4Exporter(endpoint=os.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT))

if not _is_application_signals_enabled():
return span_exporter

Expand All @@ -328,6 +333,10 @@ def _customize_span_processors(provider: TracerProvider, resource: Resource) ->
# Construct and set local and remote attributes span processor
provider.add_span_processor(AttributePropagatingSpanProcessorBuilder().build())

# Do not export metrics if it's CloudWatch OTLP endpoint
if is_otlp_endpoint_cloudwatch():
return

# Export 100% spans and not export Application-Signals metrics if on Lambda.
if _is_lambda_environment():
_export_unsampled_span_for_lambda(provider, resource)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
import logging
from typing import Dict, Optional

import requests
from grpc import Compression

from amazon.opentelemetry.distro._utils import is_otlp_endpoint_cloudwatch
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

AWS_SERVICE = "xray"
_logger = logging.getLogger(__name__)

"""The OTLPAwsSigV4Exporter extends the functionality of the OTLPSpanExporter to allow SigV4 authentication if the
configured traces endpoint is a CloudWatch OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces"""


class OTLPAwsSigV4Exporter(OTLPSpanExporter):

def __init__(
self,
endpoint: Optional[str] = None,
certificate_file: Optional[str] = None,
client_key_file: Optional[str] = None,
client_certificate_file: Optional[str] = None,
headers: Optional[Dict[str, str]] = None,
timeout: Optional[int] = None,
compression: Optional[Compression] = None,
rsession: Optional[requests.Session] = None,
):

# Represents the region of the CloudWatch OTLP endpoint to send the traces to.
# If the endpoint has been verified to be valid, this should not be None

self._aws_region = None

if endpoint and is_otlp_endpoint_cloudwatch(endpoint):

# Defensive check to verify that the application being auto instrumented has
# botocore installed.
try:
# pylint: disable=import-outside-toplevel
from botocore import auth, awsrequest, session

self.boto_auth = auth
self.boto_aws_request = awsrequest
self.boto_session = session.Session()
self._aws_region = self._validate_exporter_endpoint(endpoint)

except ImportError:
_logger.error(
"botocore is required to export traces to %s. Please install it using `pip install botocore`",
endpoint,
)

else:
_logger.error(
"Invalid XRay traces endpoint: %s. Resolving to OTLPSpanExporter to handle exporting. "
"The traces endpoint follows the pattern https://xray.[AWSRegion].amazonaws.com/v1/traces. "
"For example, for the US West (Oregon) (us-west-2) Region, the endpoint will be "
"https://xray.us-west-2.amazonaws.com/v1/traces.",
endpoint,
)

super().__init__(
endpoint=endpoint,
certificate_file=certificate_file,
client_key_file=client_key_file,
client_certificate_file=client_certificate_file,
headers=headers,
timeout=timeout,
compression=compression,
session=rsession,
)

def _export(self, serialized_data: bytes):
if self._aws_region:
request = self.boto_aws_request.AWSRequest(
method="POST",
url=self._endpoint,
data=serialized_data,
headers={"Content-Type": "application/x-protobuf"},
)

credentials = self.boto_session.get_credentials()

if credentials is not None:
signer = self.boto_auth.SigV4Auth(credentials, AWS_SERVICE, self._aws_region)

try:
signer.add_auth(request)
self._session.headers.update(dict(request.headers))

except self.boto_auth.NoCredentialsError as signing_error:
_logger.error("Failed to sign request: %s", signing_error)

else:
_logger.error("Failed to get credentials to export span to OTLP CloudWatch endpoint")

return super()._export(serialized_data)

def _validate_exporter_endpoint(self, endpoint: str) -> Optional[str]:
if not endpoint:
return None

region = endpoint.split(".")[1]
xray_regions = self.boto_session.get_available_regions(AWS_SERVICE)

if region not in xray_regions:

_logger.error(
"Invalid AWS region: %s. Valid regions are %s. Resolving to default endpoint.", region, xray_regions
)

return None

return region
Loading
Loading