12
12
13
13
from amazon .opentelemetry .distro ._aws_attribute_keys import AWS_LOCAL_SERVICE , AWS_SERVICE_TYPE
14
14
from amazon .opentelemetry .distro ._aws_resource_attribute_configurator import get_service_attribute
15
- from amazon .opentelemetry .distro ._utils import is_agent_observability_enabled , is_installed
15
+ from amazon .opentelemetry .distro ._utils import get_aws_session , is_agent_observability_enabled
16
16
from amazon .opentelemetry .distro .always_record_sampler import AlwaysRecordSampler
17
17
from amazon .opentelemetry .distro .attribute_propagating_span_processor_builder import (
18
18
AttributePropagatingSpanProcessorBuilder ,
23
23
AwsMetricAttributesSpanExporterBuilder ,
24
24
)
25
25
from amazon .opentelemetry .distro .aws_span_metrics_processor_builder import AwsSpanMetricsProcessorBuilder
26
-
27
- # pylint: disable=line-too-long
28
- from amazon .opentelemetry .distro .exporter .otlp .aws .logs ._aws_cw_otlp_batch_log_record_processor import (
29
- AwsCloudWatchOtlpBatchLogRecordProcessor ,
30
- )
31
- from amazon .opentelemetry .distro .exporter .otlp .aws .logs .otlp_aws_logs_exporter import OTLPAwsLogExporter
32
- from amazon .opentelemetry .distro .exporter .otlp .aws .traces .otlp_aws_span_exporter import OTLPAwsSpanExporter
33
26
from amazon .opentelemetry .distro .otlp_udp_exporter import OTLPUdpSpanExporter
34
27
from amazon .opentelemetry .distro .sampler .aws_xray_remote_sampler import AwsXRayRemoteSampler
35
28
from amazon .opentelemetry .distro .scope_based_exporter import ScopeBasedPeriodicExportingMetricReader
102
95
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
103
96
OTEL_EXPORTER_OTLP_LOGS_HEADERS = "OTEL_EXPORTER_OTLP_LOGS_HEADERS"
104
97
98
+ XRAY_SERVICE = "xray"
99
+ LOGS_SERIVCE = "logs"
105
100
AWS_TRACES_OTLP_ENDPOINT_PATTERN = r"https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$"
106
101
AWS_LOGS_OTLP_ENDPOINT_PATTERN = r"https://logs\.([a-z0-9-]+)\.amazonaws\.com/v1/logs$"
107
102
@@ -215,9 +210,9 @@ def _init_logging(
215
210
216
211
for _ , exporter_class in exporters .items ():
217
212
exporter_args = {}
218
- log_exporter : LogExporter = _customize_logs_exporter ( exporter_class ( ** exporter_args ))
219
- log_processor = _customize_log_record_processor ( log_exporter )
220
- provider . add_log_record_processor ( log_processor )
213
+ _customize_log_record_processor (
214
+ logger_provider = provider , log_exporter = _customize_logs_exporter ( exporter_class ( ** exporter_args ) )
215
+ )
221
216
222
217
event_logger_provider = EventLoggerProvider (logger_provider = provider )
223
218
set_event_logger_provider (event_logger_provider )
@@ -304,10 +299,11 @@ def _export_unsampled_span_for_agent_observability(trace_provider: TracerProvide
304
299
return
305
300
306
301
traces_endpoint = os .environ .get (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT )
302
+ if traces_endpoint and _is_aws_otlp_endpoint (traces_endpoint , XRAY_SERVICE ):
303
+ endpoint , region = _extract_endpoint_and_region_from_otlp_endpoint (traces_endpoint )
304
+ span_exporter = _create_aws_otlp_exporter (endpoint = endpoint , service = XRAY_SERVICE , region = region )
307
305
308
- span_exporter = OTLPAwsSpanExporter (endpoint = traces_endpoint , logger_provider = get_logger_provider ())
309
-
310
- trace_provider .add_span_processor (BatchUnsampledSpanProcessor (span_exporter = span_exporter ))
306
+ trace_provider .add_span_processor (BatchUnsampledSpanProcessor (span_exporter = span_exporter ))
311
307
312
308
313
309
def _is_defer_to_workers_enabled ():
@@ -356,7 +352,7 @@ def _custom_import_sampler(sampler_name: str, resource: Resource) -> Sampler:
356
352
if sampler_name is None :
357
353
sampler_name = "parentbased_always_on"
358
354
359
- if sampler_name == "xray" :
355
+ if sampler_name == XRAY_SERVICE :
360
356
# Example env var value
361
357
# OTEL_TRACES_SAMPLER_ARG=endpoint=http://localhost:2000,polling_interval=360
362
358
sampler_argument_env : str = os .getenv (OTEL_TRACES_SAMPLER_ARG , None )
@@ -402,50 +398,52 @@ def _customize_span_exporter(span_exporter: SpanExporter, resource: Resource) ->
402
398
traces_endpoint = os .environ .get (AWS_XRAY_DAEMON_ADDRESS_CONFIG , "127.0.0.1:2000" )
403
399
span_exporter = OTLPUdpSpanExporter (endpoint = traces_endpoint )
404
400
405
- if _is_aws_otlp_endpoint (traces_endpoint , "xray" ):
401
+ if traces_endpoint and _is_aws_otlp_endpoint (traces_endpoint , XRAY_SERVICE ):
406
402
_logger .info ("Detected using AWS OTLP Traces Endpoint." )
407
403
408
404
if isinstance (span_exporter , OTLPSpanExporter ):
409
- if is_agent_observability_enabled ():
410
- # Span exporter needs an instance of logger provider in ai agent
411
- # observability case because we need to split input/output prompts
412
- # from span attributes and send them to the logs pipeline per
413
- # the new Gen AI semantic convention from OTel
414
- # ref: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/
415
- span_exporter = OTLPAwsSpanExporter (endpoint = traces_endpoint , logger_provider = get_logger_provider ())
416
- else :
417
- span_exporter = OTLPAwsSpanExporter (endpoint = traces_endpoint )
405
+ endpoint , region = _extract_endpoint_and_region_from_otlp_endpoint (traces_endpoint )
406
+ return _create_aws_otlp_exporter (endpoint = endpoint , service = XRAY_SERVICE , region = region )
418
407
419
- else :
420
- _logger .warning (
421
- "Improper configuration see: please export/set "
422
- "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf and OTEL_TRACES_EXPORTER=otlp"
423
- )
408
+ _logger .warning (
409
+ "Improper configuration see: please export/set "
410
+ "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf and OTEL_TRACES_EXPORTER=otlp"
411
+ )
424
412
425
413
if not _is_application_signals_enabled ():
426
414
return span_exporter
427
415
428
416
return AwsMetricAttributesSpanExporterBuilder (span_exporter , resource ).build ()
429
417
430
418
431
- def _customize_log_record_processor (log_exporter : LogExporter ):
432
- if isinstance (log_exporter , OTLPAwsLogExporter ) and is_agent_observability_enabled ():
433
- return AwsCloudWatchOtlpBatchLogRecordProcessor (exporter = log_exporter )
419
+ def _customize_log_record_processor (logger_provider : LoggerProvider , log_exporter : Optional [LogExporter ]) -> None :
420
+ if not log_exporter :
421
+ return
422
+
423
+ if is_agent_observability_enabled ():
424
+ # pylint: disable=import-outside-toplevel
425
+ from amazon .opentelemetry .distro .exporter .otlp .aws .logs ._aws_cw_otlp_batch_log_record_processor import (
426
+ AwsCloudWatchOtlpBatchLogRecordProcessor ,
427
+ )
434
428
435
- return BatchLogRecordProcessor (exporter = log_exporter )
429
+ logger_provider .add_log_record_processor (AwsCloudWatchOtlpBatchLogRecordProcessor (exporter = log_exporter ))
430
+ else :
431
+ logger_provider .add_log_record_processor (BatchLogRecordProcessor (exporter = log_exporter ))
436
432
437
433
438
434
def _customize_logs_exporter (log_exporter : LogExporter ) -> LogExporter :
439
435
logs_endpoint = os .environ .get (OTEL_EXPORTER_OTLP_LOGS_ENDPOINT )
440
436
441
- if _is_aws_otlp_endpoint (logs_endpoint , "logs" ):
437
+ if logs_endpoint and _is_aws_otlp_endpoint (logs_endpoint , LOGS_SERIVCE ):
438
+
442
439
_logger .info ("Detected using AWS OTLP Logs Endpoint." )
443
440
444
441
if isinstance (log_exporter , OTLPLogExporter ) and _validate_and_fetch_logs_header ().is_valid :
442
+ endpoint , region = _extract_endpoint_and_region_from_otlp_endpoint (logs_endpoint )
445
443
# Setting default compression mode to Gzip as this is the behavior in upstream's
446
444
# collector otlp http exporter:
447
445
# https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/otlphttpexporter
448
- return OTLPAwsLogExporter (endpoint = logs_endpoint )
446
+ return _create_aws_otlp_exporter (endpoint = endpoint , service = LOGS_SERIVCE , region = region )
449
447
450
448
_logger .warning (
451
449
"Improper configuration see: please export/set "
@@ -514,7 +512,7 @@ def _customize_metric_exporters(
514
512
metric_readers .append (scope_based_periodic_exporting_metric_reader )
515
513
516
514
if is_emf_enabled :
517
- emf_exporter = create_emf_exporter ()
515
+ emf_exporter = _create_emf_exporter ()
518
516
if emf_exporter :
519
517
metric_readers .append (PeriodicExportingMetricReader (emf_exporter ))
520
518
@@ -604,17 +602,24 @@ def _is_lambda_environment():
604
602
return AWS_LAMBDA_FUNCTION_NAME_CONFIG in os .environ
605
603
606
604
607
- def _is_aws_otlp_endpoint (otlp_endpoint : Optional [str ] = None , service : str = "xray" ) -> bool :
605
+ def _is_aws_otlp_endpoint (otlp_endpoint : Optional [str ], service : str ) -> bool :
608
606
"""Is the given endpoint an AWS OTLP endpoint?"""
609
607
610
- pattern = AWS_TRACES_OTLP_ENDPOINT_PATTERN if service == "xray" else AWS_LOGS_OTLP_ENDPOINT_PATTERN
611
-
612
608
if not otlp_endpoint :
613
609
return False
614
610
611
+ pattern = AWS_TRACES_OTLP_ENDPOINT_PATTERN if service == XRAY_SERVICE else AWS_LOGS_OTLP_ENDPOINT_PATTERN
612
+
615
613
return bool (re .match (pattern , otlp_endpoint .lower ()))
616
614
617
615
616
+ def _extract_endpoint_and_region_from_otlp_endpoint (endpoint : str ):
617
+ endpoint = endpoint .lower ()
618
+ region = endpoint .split ("." )[1 ]
619
+
620
+ return endpoint , region
621
+
622
+
618
623
def _validate_and_fetch_logs_header () -> OtlpLogHeaderSetting :
619
624
"""Checks if x-aws-log-group and x-aws-log-stream are present in the headers in order to send logs to
620
625
AWS OTLP Logs endpoint."""
@@ -631,7 +636,6 @@ def _validate_and_fetch_logs_header() -> OtlpLogHeaderSetting:
631
636
log_group = None
632
637
log_stream = None
633
638
namespace = None
634
- filtered_log_headers_count = 0
635
639
636
640
for pair in logs_headers .split ("," ):
637
641
if "=" in pair :
@@ -640,14 +644,12 @@ def _validate_and_fetch_logs_header() -> OtlpLogHeaderSetting:
640
644
value = split [1 ]
641
645
if key == AWS_OTLP_LOGS_GROUP_HEADER and value :
642
646
log_group = value
643
- filtered_log_headers_count += 1
644
647
elif key == AWS_OTLP_LOGS_STREAM_HEADER and value :
645
648
log_stream = value
646
- filtered_log_headers_count += 1
647
649
elif key == AWS_EMF_METRICS_NAMESPACE and value :
648
650
namespace = value
649
651
650
- is_valid = filtered_log_headers_count == 2 and log_group is not None and log_stream is not None
652
+ is_valid = log_group is not None and log_stream is not None
651
653
652
654
if not is_valid :
653
655
_logger .warning (
@@ -769,11 +771,12 @@ def _check_emf_exporter_enabled() -> bool:
769
771
return True
770
772
771
773
772
- def create_emf_exporter ():
774
+ def _create_emf_exporter ():
773
775
"""Create and configure the CloudWatch EMF exporter."""
774
776
try :
777
+ session = get_aws_session ()
775
778
# Check if botocore is available before importing the EMF exporter
776
- if not is_installed ( "botocore" ) :
779
+ if not session :
777
780
_logger .warning ("botocore is not installed. EMF exporter requires botocore" )
778
781
return None
779
782
@@ -788,6 +791,7 @@ def create_emf_exporter():
788
791
return None
789
792
790
793
return AwsCloudWatchEmfExporter (
794
+ session = session ,
791
795
namespace = log_header_setting .namespace ,
792
796
log_group_name = log_header_setting .log_group ,
793
797
log_stream_name = log_header_setting .log_stream ,
@@ -796,3 +800,39 @@ def create_emf_exporter():
796
800
except Exception as errors :
797
801
_logger .error ("Failed to create EMF exporter: %s" , errors )
798
802
return None
803
+
804
+
805
+ def _create_aws_otlp_exporter (endpoint : str , service : str , region : str ):
806
+ """Create and configure the AWS OTLP exporters."""
807
+ try :
808
+ session = get_aws_session ()
809
+ # Check if botocore is available before importing the AWS exporter
810
+ if not session :
811
+ _logger .warning ("Sigv4 Auth requires botocore to be enabled" )
812
+ return None
813
+
814
+ # pylint: disable=import-outside-toplevel
815
+ from amazon .opentelemetry .distro .exporter .otlp .aws .logs .otlp_aws_logs_exporter import OTLPAwsLogExporter
816
+ from amazon .opentelemetry .distro .exporter .otlp .aws .traces .otlp_aws_span_exporter import OTLPAwsSpanExporter
817
+
818
+ if service == XRAY_SERVICE :
819
+ if is_agent_observability_enabled ():
820
+ # Span exporter needs an instance of logger provider in ai agent
821
+ # observability case because we need to split input/output prompts
822
+ # from span attributes and send them to the logs pipeline per
823
+ # the new Gen AI semantic convention from OTel
824
+ # ref: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/
825
+ return OTLPAwsSpanExporter (
826
+ session = session , endpoint = endpoint , aws_region = region , logger_provider = get_logger_provider ()
827
+ )
828
+
829
+ return OTLPAwsSpanExporter (session = session , endpoint = endpoint , aws_region = region )
830
+
831
+ if service == LOGS_SERIVCE :
832
+ return OTLPAwsLogExporter (session = session , aws_region = region )
833
+
834
+ return None
835
+ # pylint: disable=broad-exception-caught
836
+ except Exception as errors :
837
+ _logger .error ("Failed to create AWS OTLP exporter: %s" , errors )
838
+ return None
0 commit comments