Skip to content

Commit c0156a2

Browse files
committed
consolidate log group and log stream config
1 parent 6ce90df commit c0156a2

File tree

2 files changed

+171
-2
lines changed

2 files changed

+171
-2
lines changed

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

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ def _initialize_components():
149149
# This is done before calling _import_exporters which would try to load exporters
150150
is_emf_enabled = _check_emf_exporter_enabled()
151151

152+
# Set up inferred headers before importing exporters
153+
_setup_inferred_headers()
154+
152155
trace_exporters, metric_exporters, log_exporters = _import_exporters(
153156
_get_exporter_names("traces"),
154157
_get_exporter_names("metrics"),
@@ -407,11 +410,17 @@ def _customize_logs_exporter(log_exporter: LogExporter, resource: Resource) -> L
407410
if _is_aws_otlp_endpoint(logs_endpoint, "logs"):
408411
_logger.info("Detected using AWS OTLP Logs Endpoint.")
409412

410-
if isinstance(log_exporter, OTLPLogExporter) and _validate_logs_headers().is_valid:
413+
headers_result = _validate_logs_headers()
414+
if isinstance(log_exporter, OTLPLogExporter) and headers_result.is_valid:
415+
# Get the headers from the OTLPLogExporter
416+
headers = {}
417+
if hasattr(log_exporter, "_headers") and log_exporter._headers:
418+
headers.update(log_exporter._headers)
419+
411420
# Setting default compression mode to Gzip as this is the behavior in upstream's
412421
# collector otlp http exporter:
413422
# https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/otlphttpexporter
414-
return OTLPAwsLogExporter(endpoint=logs_endpoint)
423+
return OTLPAwsLogExporter(endpoint=logs_endpoint, headers=headers)
415424

416425
_logger.warning(
417426
"Improper configuration see: please export/set "
@@ -568,14 +577,65 @@ def _is_aws_otlp_endpoint(otlp_endpoint: str = None, service: str = "xray") -> b
568577
return bool(re.match(pattern, otlp_endpoint.lower()))
569578

570579

580+
def _setup_inferred_headers() -> None:
581+
"""
582+
Set up inferred OTLP logs headers from resource attributes if needed.
583+
This must be called before exporters are imported.
584+
"""
585+
# Only infer if headers are not already set and agent observability is enabled
586+
if not os.environ.get(OTEL_EXPORTER_OTLP_LOGS_HEADERS) and is_agent_observability_enabled():
587+
resource_attrs = _parse_resource_attributes()
588+
589+
# Extract log group and stream from resource attributes
590+
log_group_names = resource_attrs.get("aws.log.group.names", "")
591+
log_stream_names = resource_attrs.get("aws.log.stream.names", "")
592+
593+
if log_group_names and log_stream_names:
594+
# Construct and set the headers environment variable
595+
inferred_headers = []
596+
inferred_headers.append(f"{AWS_OTLP_LOGS_GROUP_HEADER}={log_group_names}")
597+
inferred_headers.append(f"{AWS_OTLP_LOGS_STREAM_HEADER}={log_stream_names}")
598+
599+
os.environ[OTEL_EXPORTER_OTLP_LOGS_HEADERS] = ",".join(inferred_headers)
600+
_logger.info(
601+
"Set OTEL_EXPORTER_OTLP_LOGS_HEADERS from OTEL_RESOURCE_ATTRIBUTES: %s",
602+
os.environ[OTEL_EXPORTER_OTLP_LOGS_HEADERS],
603+
)
604+
605+
606+
def _parse_resource_attributes() -> Dict[str, str]:
607+
"""
608+
Parse OTEL_RESOURCE_ATTRIBUTES environment variable into a dictionary.
609+
610+
Returns:
611+
Dictionary of resource attributes
612+
"""
613+
resource_attributes = {}
614+
resource_attrs_str = os.environ.get("OTEL_RESOURCE_ATTRIBUTES", "")
615+
616+
if not resource_attrs_str:
617+
return resource_attributes
618+
619+
for pair in resource_attrs_str.split(","):
620+
if "=" in pair:
621+
key, value = pair.split("=", 1)
622+
resource_attributes[key.strip()] = value.strip()
623+
624+
return resource_attributes
625+
626+
571627
def _validate_logs_headers() -> OtlpLogHeaderSetting:
572628
"""
573629
Checks if x-aws-log-group and x-aws-log-stream are present in the headers in order to send logs to
574630
AWS OTLP Logs endpoint.
575631
632+
Note: Header inference from OTEL_RESOURCE_ATTRIBUTES is now handled by _setup_inferred_headers()
633+
which runs before exporters are created.
634+
576635
Returns:
577636
LogHeadersResult with log_group, log_stream, namespace and is_valid flag
578637
"""
638+
# Headers should already be set by _setup_inferred_headers if inference was needed
579639
logs_headers = os.environ.get(OTEL_EXPORTER_OTLP_LOGS_HEADERS)
580640

581641
log_group = None

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_opentelementry_configurator.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,115 @@ def customize_exporter_test(
703703
for key in config.keys():
704704
os.environ.pop(key, None)
705705

706+
def test_validate_logs_headers_infers_from_resource_attributes(self):
707+
"""Test that _setup_inferred_headers can infer headers from OTEL_RESOURCE_ATTRIBUTES"""
708+
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import (
709+
_validate_logs_headers,
710+
_parse_resource_attributes,
711+
_setup_inferred_headers,
712+
)
713+
714+
# Test _parse_resource_attributes function
715+
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = (
716+
"service.name=TestService,aws.log.group.names=/aws/genesis/TestAgent,aws.log.stream.names=test-stream"
717+
)
718+
attrs = _parse_resource_attributes()
719+
self.assertEqual(attrs["service.name"], "TestService")
720+
self.assertEqual(attrs["aws.log.group.names"], "/aws/genesis/TestAgent")
721+
self.assertEqual(attrs["aws.log.stream.names"], "test-stream")
722+
723+
# Test inference when AGENT_OBSERVABILITY_ENABLED is true and OTEL_EXPORTER_OTLP_LOGS_HEADERS is not set
724+
os.environ["AGENT_OBSERVABILITY_ENABLED"] = "true"
725+
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = (
726+
"service.name=TestService,aws.log.group.names=/aws/genesis/TestAgent,aws.log.stream.names=test-stream"
727+
)
728+
os.environ.pop("OTEL_EXPORTER_OTLP_LOGS_HEADERS", None)
729+
730+
# Call _setup_inferred_headers to set the environment variable
731+
_setup_inferred_headers()
732+
733+
# Verify the environment variable was set correctly
734+
self.assertEqual(
735+
os.environ.get("OTEL_EXPORTER_OTLP_LOGS_HEADERS"),
736+
"x-aws-log-group=/aws/genesis/TestAgent,x-aws-log-stream=test-stream",
737+
)
738+
739+
# Now validate headers should find the inferred values
740+
result = _validate_logs_headers()
741+
self.assertTrue(result.is_valid)
742+
self.assertEqual(result.log_group, "/aws/genesis/TestAgent")
743+
self.assertEqual(result.log_stream, "test-stream")
744+
745+
# Clean up
746+
os.environ.pop("AGENT_OBSERVABILITY_ENABLED", None)
747+
os.environ.pop("OTEL_RESOURCE_ATTRIBUTES", None)
748+
os.environ.pop("OTEL_EXPORTER_OTLP_LOGS_HEADERS", None)
749+
750+
def test_validate_logs_headers_explicit_takes_priority(self):
751+
"""Test that explicit OTEL_EXPORTER_OTLP_LOGS_HEADERS takes priority over inference"""
752+
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import (
753+
_validate_logs_headers,
754+
_setup_inferred_headers,
755+
)
756+
757+
# Set both explicit headers and resource attributes
758+
os.environ["AGENT_OBSERVABILITY_ENABLED"] = "true"
759+
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = (
760+
"aws.log.group.names=/aws/genesis/InferredAgent,aws.log.stream.names=inferred-stream"
761+
)
762+
os.environ["OTEL_EXPORTER_OTLP_LOGS_HEADERS"] = (
763+
"x-aws-log-group=/aws/genesis/ExplicitAgent,x-aws-log-stream=explicit-stream"
764+
)
765+
766+
# Call _setup_inferred_headers - it should not override explicit headers
767+
_setup_inferred_headers()
768+
769+
# Verify explicit headers were not overridden
770+
self.assertEqual(
771+
os.environ.get("OTEL_EXPORTER_OTLP_LOGS_HEADERS"),
772+
"x-aws-log-group=/aws/genesis/ExplicitAgent,x-aws-log-stream=explicit-stream",
773+
)
774+
775+
result = _validate_logs_headers()
776+
self.assertTrue(result.is_valid)
777+
# Should use explicit headers, not inferred ones
778+
self.assertEqual(result.log_group, "/aws/genesis/ExplicitAgent")
779+
self.assertEqual(result.log_stream, "explicit-stream")
780+
781+
# Clean up
782+
os.environ.pop("AGENT_OBSERVABILITY_ENABLED", None)
783+
os.environ.pop("OTEL_RESOURCE_ATTRIBUTES", None)
784+
os.environ.pop("OTEL_EXPORTER_OTLP_LOGS_HEADERS", None)
785+
786+
def test_validate_logs_headers_no_inference_when_agent_observability_disabled(self):
787+
"""Test that no inference happens when AGENT_OBSERVABILITY_ENABLED is false"""
788+
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import (
789+
_validate_logs_headers,
790+
_setup_inferred_headers,
791+
)
792+
793+
# Set resource attributes but keep agent observability disabled
794+
os.environ["AGENT_OBSERVABILITY_ENABLED"] = "false"
795+
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = (
796+
"aws.log.group.names=/aws/genesis/TestAgent,aws.log.stream.names=test-stream"
797+
)
798+
os.environ.pop("OTEL_EXPORTER_OTLP_LOGS_HEADERS", None)
799+
800+
# Call _setup_inferred_headers - it should not set headers when agent observability is disabled
801+
_setup_inferred_headers()
802+
803+
# Verify no headers were set
804+
self.assertIsNone(os.environ.get("OTEL_EXPORTER_OTLP_LOGS_HEADERS"))
805+
806+
result = _validate_logs_headers()
807+
self.assertFalse(result.is_valid)
808+
self.assertIsNone(result.log_group)
809+
self.assertIsNone(result.log_stream)
810+
811+
# Clean up
812+
os.environ.pop("AGENT_OBSERVABILITY_ENABLED", None)
813+
os.environ.pop("OTEL_RESOURCE_ATTRIBUTES", None)
814+
706815

707816
def validate_distro_environ():
708817
tc: TestCase = TestCase()

0 commit comments

Comments
 (0)