Skip to content

Commit 4613dbf

Browse files
committed
add otlp logs exporter sigv4 support
1 parent dd37193 commit 4613dbf

File tree

4 files changed

+55
-16
lines changed

4 files changed

+55
-16
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/aws_opentelemetry_configurator.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from amazon.opentelemetry.distro.scope_based_exporter import ScopeBasedPeriodicExportingMetricReader
2929
from amazon.opentelemetry.distro.scope_based_filtering_view import ScopeBasedRetainingView
3030
from opentelemetry._logs import set_logger_provider
31+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
3132
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as OTLPHttpOTLPMetricExporter
3233
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
3334
from opentelemetry.metrics import set_meter_provider
@@ -89,7 +90,9 @@
8990
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"
9091
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
9192

92-
XRAY_OTLP_ENDPOINT_PATTERN = r"https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$"
93+
AWS_TRACES_OTLP_ENDPOINT_PATTERN = r"https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$"
94+
AWS_LOGS_OTLP_ENDPOINT_PATTERN = r"https://logs\.([a-z0-9-]+)\.amazonaws\.com/v1/logs$"
95+
9396
# UDP package size is not larger than 64KB
9497
LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10
9598

@@ -174,7 +177,7 @@ def _init_logging(
174177
set_logger_provider(provider)
175178

176179
for _, exporter_class in exporters.items():
177-
exporter_args = {}
180+
exporter_args: Dict[str, any] = {}
178181
log_exporter = _customize_logs_exporter(exporter_class(**exporter_args), resource)
179182
provider.add_log_record_processor(BatchLogRecordProcessor(exporter=log_exporter))
180183

@@ -200,7 +203,7 @@ def _init_tracing(
200203
for _, exporter_class in exporters.items():
201204
exporter_args: Dict[str, any] = {}
202205
span_exporter: SpanExporter = exporter_class(**exporter_args)
203-
span_exporter = _customize_exporter(span_exporter, resource)
206+
span_exporter = _customize_span_exporter(span_exporter, resource)
204207
trace_provider.add_span_processor(
205208
BatchSpanProcessor(span_exporter=span_exporter, max_export_batch_size=_span_export_batch_size())
206209
)
@@ -335,15 +338,14 @@ def _customize_sampler(sampler: Sampler) -> Sampler:
335338
return AlwaysRecordSampler(sampler)
336339

337340

338-
def _customize_exporter(span_exporter: SpanExporter, resource: Resource) -> SpanExporter:
341+
def _customize_span_exporter(span_exporter: SpanExporter, resource: Resource) -> SpanExporter:
339342
if _is_lambda_environment():
340343
# Override OTLP http default endpoint to UDP
341344
if isinstance(span_exporter, OTLPSpanExporter) and os.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) is None:
342345
traces_endpoint = os.environ.get(AWS_XRAY_DAEMON_ADDRESS_CONFIG, "127.0.0.1:2000")
343346
span_exporter = OTLPUdpSpanExporter(endpoint=traces_endpoint)
344347

345-
if is_xray_otlp_endpoint(os.environ.get(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)):
346-
# TODO: Change this url once doc writer has added a section for using SigV4 without collector
348+
if is_aws_otlp_endpoint(os.environ.get(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT), "xray"):
347349
_logger.info("Detected using AWS OTLP XRay Endpoint.")
348350

349351
if isinstance(span_exporter, OTLPSpanExporter):
@@ -362,7 +364,13 @@ def _customize_exporter(span_exporter: SpanExporter, resource: Resource) -> Span
362364

363365

364366
def _customize_logs_exporter(log_exporter: LogExporter, resource: Resource) -> LogExporter:
365-
return OTLPAwsLogExporter(endpoint=os.getenv(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT))
367+
if is_aws_otlp_endpoint(os.environ.get(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT), "logs"):
368+
_logger.info("Detected using AWS OTLP Logs Endpoint.")
369+
370+
if isinstance(log_exporter, OTLPLogExporter):
371+
return OTLPAwsLogExporter(endpoint=os.getenv(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT))
372+
373+
return log_exporter
366374

367375

368376
def _customize_span_processors(provider: TracerProvider, resource: Resource) -> None:
@@ -485,12 +493,15 @@ def _is_lambda_environment():
485493
return AWS_LAMBDA_FUNCTION_NAME_CONFIG in os.environ
486494

487495

488-
def is_xray_otlp_endpoint(otlp_endpoint: str = None) -> bool:
489-
"""Is the given endpoint the XRay OTLP endpoint?"""
496+
def is_aws_otlp_endpoint(otlp_endpoint: str = None, service: str = "xray") -> bool:
497+
"""Is the given endpoint an AWS OTLP endpoint?"""
498+
499+
pattern = AWS_TRACES_OTLP_ENDPOINT_PATTERN if service == "xray" else AWS_LOGS_OTLP_ENDPOINT_PATTERN
500+
490501
if not otlp_endpoint:
491502
return False
492503

493-
return bool(re.match(XRAY_OTLP_ENDPOINT_PATTERN, otlp_endpoint.lower()))
504+
return bool(re.match(pattern, otlp_endpoint.lower()))
494505

495506

496507
def _get_metric_export_interval():

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/exporter/otlp/aws/common/otlp_aws_exporter.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
14
import logging
25
from abc import ABC, abstractmethod
36
from typing import Optional
@@ -10,6 +13,11 @@
1013

1114

1215
class OTLPBaseAwsExporter(ABC):
16+
"""
17+
Abstract base class providing shared functionality for AWS (OTLP) exporters authenticated with
18+
Sigv4.
19+
"""
20+
1321
def __init__(
1422
self,
1523
endpoint: Optional[str] = None,
@@ -49,7 +57,12 @@ def __init__(
4957
def get_service(self):
5058
pass
5159

52-
def sigv4_auth(self, serialized_data):
60+
def inject_sigv4_auth(self, serialized_data):
61+
"""
62+
Injects Sigv4 authentication headers to this exporter's session object.
63+
Does nothing if obtaining or signing the credentials fails.
64+
"""
65+
5366
if self._has_required_dependencies:
5467
request = self._boto_aws_request.AWSRequest(
5568
method="POST",

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/exporter/otlp/aws/logs/otlp_aws_logs_exporter.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44
from typing import Dict, Optional
55

66
import requests
7-
from opentelemetry.exporter.otlp.proto.http import Compression
87

98
from amazon.opentelemetry.distro.exporter.otlp.aws.common.otlp_aws_exporter import OTLPBaseAwsExporter
9+
from opentelemetry.exporter.otlp.proto.http import Compression
1010
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
1111

1212

1313
class OTLPAwsLogExporter(OTLPLogExporter, OTLPBaseAwsExporter):
14+
"""
15+
This exporter extends the functionality of the OTLPLogExporter to allow spans to be exported to the
16+
CloudWatch Logs OTLP endpoint https://logs.[AWSRegion].amazonaws.com/v1/logs. Utilizes the botocore
17+
library to sign and directly inject SigV4 Authentication to the exported request's headers.
18+
19+
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html
20+
"""
21+
22+
# pylint: disable=too-many-arguments
1423
def __init__(
1524
self,
1625
endpoint: Optional[str] = None,
@@ -35,9 +44,13 @@ def __init__(
3544
session,
3645
)
3746

47+
# pylint: disable=no-self-use
3848
def get_service(self):
3949
return "logs"
4050

51+
# Overrides upstream's private implementation of _export. All behaviors are
52+
# the same except if the endpoint is an CloudWatch Logs OTLP endpoint, we will sign the request
53+
# with SigV4 in headers before sending it to the endpoint.
4154
def _export(self, serialized_data: bytes):
42-
self.sigv4_auth(serialized_data)
55+
self.inject_sigv4_auth(serialized_data)
4356
return OTLPLogExporter._export(self, serialized_data)

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/exporter/otlp/aws/traces/otlp_aws_span_exporter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
class OTLPAwsSpanExporter(OTLPSpanExporter, OTLPBaseAwsExporter):
1313
"""
1414
This exporter extends the functionality of the OTLPSpanExporter to allow spans to be exported to the
15-
XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the botocore
15+
XRay Traces OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the botocore
1616
library to sign and directly inject SigV4 Authentication to the exported request's headers.
1717
1818
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html
1919
"""
2020

21+
# pylint: disable=too-many-arguments
2122
def __init__(
2223
self,
2324
endpoint: Optional[str] = None,
@@ -43,12 +44,13 @@ def __init__(
4344
rsession,
4445
)
4546

47+
# pylint: disable=no-self-use
4648
def get_service(self):
4749
return "xray"
4850

4951
# Overrides upstream's private implementation of _export. All behaviors are
5052
# the same except if the endpoint is an XRay OTLP endpoint, we will sign the request
51-
# with SigV4 in headers before sending it to the endpoint. Otherwise, we will skip signing.
53+
# with SigV4 in headers before sending it to the endpoint.
5254
def _export(self, serialized_data: bytes):
53-
self.sigv4_auth(serialized_data)
55+
self.inject_sigv4_auth(serialized_data)
5456
return OTLPSpanExporter._export(self, serialized_data)

0 commit comments

Comments
 (0)