Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 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,35 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import re
import sys
from logging import Logger, getLogger

import pkg_resources

_logger: Logger = getLogger(__name__)

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


def is_xray_otlp_endpoint(otlp_endpoint: str = None) -> bool:
"""Is the given endpoint the XRay OTLP endpoint?"""

if not otlp_endpoint:
return False

return bool(re.match(XRAY_OTLP_ENDPOINT_PATTERN, otlp_endpoint.lower()))


def is_installed(req: str) -> bool:
"""Is the given required package installed?"""

if req in sys.modules and sys.modules[req] is not None:
return True

try:
pkg_resources.get_distribution(req)
except Exception as exc: # pylint: disable=broad-except
_logger.debug("Skipping instrumentation patch: package %s, exception: %s", req, exc)
return False
return True
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_xray_otlp_endpoint
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_aws_span_exporter import OTLPAwsSpanExporter
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,11 @@ 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 isinstance(span_exporter, OTLPSpanExporter) and is_xray_otlp_endpoint(
os.environ.get(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)
):
span_exporter = OTLPAwsSpanExporter(endpoint=os.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT))

if not _is_application_signals_enabled():
return span_exporter

Expand All @@ -328,6 +335,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 Application-Signals metrics if it's XRay OTLP endpoint
if is_xray_otlp_endpoint():
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,94 @@
# 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 amazon.opentelemetry.distro._utils import is_installed, is_xray_otlp_endpoint
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

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


class OTLPAwsSpanExporter(OTLPSpanExporter):
"""
This exporter extends the functionality of the OTLPSpanExporter to allow spans to be exported to the
XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the botocore
library to sign and directly inject SigV4 Authentication to the exported request's headers.
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html
"""

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_xray_otlp_endpoint(endpoint):

if is_installed("botocore"):
# 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 = endpoint.split(".")[1]

else:
_logger.error(
"botocore is required to export traces to %s. Please install it using `pip install botocore`",
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)
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
# SPDX-License-Identifier: Apache-2.0
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
import os
import sys
from logging import Logger, getLogger

# pylint: disable=unused-import
# flake8: noqa: F401
import pkg_resources

from amazon.opentelemetry.distro._utils import is_installed
from amazon.opentelemetry.distro.patches._resource_detector_patches import _apply_resource_detector_patches

# Env variable for determining whether we want to monkey patch gevent modules. Possible values are 'all', 'none', and
Expand All @@ -25,7 +27,7 @@ def apply_instrumentation_patches() -> None:

Where possible, automated testing should be run to catch upstream changes resulting in broken patches
"""
if _is_installed("gevent"):
if is_installed("gevent"):
try:
gevent_patch_module = os.environ.get(AWS_GEVENT_PATCH_MODULES, "all")

Expand Down Expand Up @@ -56,7 +58,7 @@ def apply_instrumentation_patches() -> None:
except Exception as exc: # pylint: disable=broad-except
_logger.info("Failed to monkey patch gevent, exception: %s", exc)

if _is_installed("botocore ~= 1.0"):
if is_installed("botocore ~= 1.0"):
# pylint: disable=import-outside-toplevel
# Delay import to only occur if patches is safe to apply (e.g. the instrumented library is installed).
from amazon.opentelemetry.distro.patches._botocore_patches import _apply_botocore_instrumentation_patches
Expand All @@ -66,15 +68,3 @@ def apply_instrumentation_patches() -> None:
# No need to check if library is installed as this patches opentelemetry.sdk,
# which must be installed for the distro to work at all.
_apply_resource_detector_patches()


def _is_installed(req: str) -> bool:
if req in sys.modules:
return True

try:
pkg_resources.get_distribution(req)
except Exception as exc: # pylint: disable=broad-except
_logger.debug("Skipping instrumentation patch: package %s, exception: %s", req, exc)
return False
return True
Loading
Loading