Skip to content

Commit aed584f

Browse files
authored
Auto-Configure ADOT SDK Defaults for Genesis (#392)
## What does this pull request do? Defaults many environment variables when `AGENT_OBSERVABILITY_ENABLED=true` to streamline enablement process for customers. Before: ``` docker run -p 8000:8000 \ -e "OTEL_METRICS_EXPORTER=awsemf" \ -e "OTEL_TRACES_EXPORTER=otlp" \ -e "OTEL_LOGS_EXPORTER=otlp" \ -e "OTEL_PYTHON_DISTRO=aws_distro" \ -e "OTEL_PYTHON_CONFIGURATOR=aws_configurator" \ -e "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf" \ -e "OTEL_RESOURCE_ATTRIBUTES=service.name=ticketing-agent" \ -e "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true" \ -e "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true" \ -e "OTEL_AWS_APPLICATION_SIGNALS_ENABLED=false" \ -e "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://xray.us-west-2.amazonaws.com/v1/traces" \ -e "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs.us-west-2.amazonaws.com/v1/logs" \ -e "OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-aws-log-group=test/genesis,x-aws-log-stream=default,x-aws-metric-namespace=genesis-test" \ -e "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,botocore,boto3,urllib3,requests,starlette" \ -e "AGENT_OBSERVABILITY_ENABLED=true" \ genesis-poc ``` After: ``` docker run -p 8000:8000 \ -e "OTEL_PYTHON_DISTRO=aws_distro" \ -e "OTEL_PYTHON_CONFIGURATOR=aws_configurator" \ -e "OTEL_RESOURCE_ATTRIBUTES=service.name=ticketing-agent,aws.log.group.names=test/genesis,cloud.resource_id=agent_arn" \ -e "OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-aws-log-group=test/genesis,x-aws-log-stream=default,x-aws-metric-namespace=genesis" \ -e "AGENT_OBSERVABILITY_ENABLED=true" \ genesis-poc ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 69b7ea0 commit aed584f

File tree

5 files changed

+382
-5
lines changed

5 files changed

+382
-5
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,32 @@ def is_installed(req: str) -> bool:
3535
def is_agent_observability_enabled() -> bool:
3636
"""Is the Agentic AI monitoring flag set to true?"""
3737
return os.environ.get(AGENT_OBSERVABILITY_ENABLED, "false").lower() == "true"
38+
39+
40+
def get_aws_region() -> str:
41+
"""Get AWS region using botocore session.
42+
43+
botocore automatically checks in the following priority order:
44+
1. AWS_REGION environment variable
45+
2. AWS_DEFAULT_REGION environment variable
46+
3. AWS CLI config file (~/.aws/config)
47+
4. EC2 instance metadata service
48+
49+
Returns:
50+
The AWS region if found, None otherwise.
51+
"""
52+
if is_installed("botocore"):
53+
try:
54+
from botocore import session # pylint: disable=import-outside-toplevel
55+
56+
botocore_session = session.Session()
57+
if botocore_session.region_name:
58+
return botocore_session.region_name
59+
except (ImportError, AttributeError):
60+
# botocore failed to determine region
61+
pass
62+
63+
_logger.warning(
64+
"AWS region not found. Please set AWS_REGION environment variable or configure AWS CLI with 'aws configure'."
65+
)
66+
return None

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@
102102
# UDP package size is not larger than 64KB
103103
LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10
104104

105+
OTEL_TRACES_EXPORTER = "OTEL_TRACES_EXPORTER"
106+
OTEL_LOGS_EXPORTER = "OTEL_LOGS_EXPORTER"
107+
OTEL_METRICS_EXPORTER = "OTEL_METRICS_EXPORTER"
108+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
109+
OTEL_TRACES_SAMPLER = "OTEL_TRACES_SAMPLER"
110+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"
111+
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED"
112+
105113
_logger: Logger = getLogger(__name__)
106114

107115

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
import sys
55
from logging import Logger, getLogger
66

7+
from amazon.opentelemetry.distro._utils import get_aws_region, is_agent_observability_enabled
8+
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import (
9+
APPLICATION_SIGNALS_ENABLED_CONFIG,
10+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
11+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
12+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
13+
OTEL_LOGS_EXPORTER,
14+
OTEL_METRICS_EXPORTER,
15+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
16+
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
17+
OTEL_TRACES_EXPORTER,
18+
OTEL_TRACES_SAMPLER,
19+
)
720
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
821
from opentelemetry.distro import OpenTelemetryDistro
922
from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_PYTHON_ID_GENERATOR
@@ -65,5 +78,44 @@ def _configure(self, **kwargs):
6578
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, "base2_exponential_bucket_histogram"
6679
)
6780

81+
if is_agent_observability_enabled():
82+
# "otlp" is already native OTel default, but we set them here to be explicit
83+
# about intended configuration for agent observability
84+
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
85+
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")
86+
os.environ.setdefault(OTEL_METRICS_EXPORTER, "awsemf")
87+
88+
# Set GenAI capture content default
89+
os.environ.setdefault(OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, "true")
90+
91+
# Set OTLP endpoints with AWS region if not already set
92+
region = get_aws_region()
93+
if region:
94+
os.environ.setdefault(
95+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, f"https://xray.{region}.amazonaws.com/v1/traces"
96+
)
97+
os.environ.setdefault(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, f"https://logs.{region}.amazonaws.com/v1/logs")
98+
else:
99+
_logger.warning(
100+
"AWS region could not be determined. OTLP endpoints will not be automatically configured. "
101+
"Please set AWS_REGION environment variable or configure OTLP endpoints manually."
102+
)
103+
104+
# Set sampler default
105+
os.environ.setdefault(OTEL_TRACES_SAMPLER, "parentbased_always_on")
106+
107+
# Set disabled instrumentations default
108+
os.environ.setdefault(
109+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
110+
"http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
111+
"botocore,boto3,urllib3,requests,starlette",
112+
)
113+
114+
# Set logging auto instrumentation default
115+
os.environ.setdefault(OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "true")
116+
117+
# Disable AWS Application Signals by default
118+
os.environ.setdefault(APPLICATION_SIGNALS_ENABLED_CONFIG, "false")
119+
68120
if kwargs.get("apply_patches", True):
69121
apply_instrumentation_patches()
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,226 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import os
34
from importlib.metadata import PackageNotFoundError, version
45
from unittest import TestCase
6+
from unittest.mock import patch
7+
8+
from amazon.opentelemetry.distro.aws_opentelemetry_distro import AwsOpenTelemetryDistro
59

610

711
class TestAwsOpenTelemetryDistro(TestCase):
12+
def setUp(self):
13+
# Store original env vars if they exist
14+
self.env_vars_to_restore = {}
15+
self.env_vars_to_check = [
16+
"OTEL_EXPORTER_OTLP_PROTOCOL",
17+
"OTEL_PROPAGATORS",
18+
"OTEL_PYTHON_ID_GENERATOR",
19+
"OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION",
20+
"AGENT_OBSERVABILITY_ENABLED",
21+
"OTEL_TRACES_EXPORTER",
22+
"OTEL_LOGS_EXPORTER",
23+
"OTEL_METRICS_EXPORTER",
24+
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT",
25+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
26+
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
27+
"OTEL_TRACES_SAMPLER",
28+
"OTEL_PYTHON_DISABLED_INSTRUMENTATIONS",
29+
"OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED",
30+
"OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
31+
]
32+
33+
# First, save all current values
34+
for var in self.env_vars_to_check:
35+
if var in os.environ:
36+
self.env_vars_to_restore[var] = os.environ[var]
37+
38+
# Then clear ALL of them to ensure clean state
39+
for var in self.env_vars_to_check:
40+
if var in os.environ:
41+
del os.environ[var]
42+
43+
def tearDown(self):
44+
# Clear all env vars first
45+
for var in self.env_vars_to_check:
46+
if var in os.environ:
47+
del os.environ[var]
48+
49+
# Then restore original values
50+
for var, value in self.env_vars_to_restore.items():
51+
os.environ[var] = value
52+
853
def test_package_available(self):
954
try:
1055
version("aws-opentelemetry-distro")
1156
except PackageNotFoundError:
1257
self.fail("aws-opentelemetry-distro not installed")
58+
59+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
60+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
61+
def test_configure_sets_default_values(self, mock_super_configure, mock_apply_patches):
62+
"""Test that _configure sets default environment variables"""
63+
distro = AwsOpenTelemetryDistro()
64+
distro._configure(apply_patches=True)
65+
66+
# Check that default values are set
67+
self.assertEqual(os.environ.get("OTEL_EXPORTER_OTLP_PROTOCOL"), "http/protobuf")
68+
self.assertEqual(os.environ.get("OTEL_PROPAGATORS"), "xray,tracecontext,b3,b3multi")
69+
self.assertEqual(os.environ.get("OTEL_PYTHON_ID_GENERATOR"), "xray")
70+
self.assertEqual(
71+
os.environ.get("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION"),
72+
"base2_exponential_bucket_histogram",
73+
)
74+
75+
# Verify super()._configure() was called
76+
mock_super_configure.assert_called_once()
77+
78+
# Verify patches were applied
79+
mock_apply_patches.assert_called_once()
80+
81+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
82+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
83+
def test_configure_without_patches(self, mock_super_configure, mock_apply_patches): # pylint: disable=no-self-use
84+
"""Test that _configure can skip applying patches"""
85+
distro = AwsOpenTelemetryDistro()
86+
distro._configure(apply_patches=False)
87+
88+
# Verify patches were NOT applied
89+
mock_apply_patches.assert_not_called()
90+
91+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region")
92+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
93+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
94+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
95+
def test_configure_with_agent_observability_enabled(
96+
self, mock_super_configure, mock_apply_patches, mock_is_agent_observability, mock_get_aws_region
97+
):
98+
"""Test that _configure sets agent observability defaults when enabled"""
99+
mock_is_agent_observability.return_value = True
100+
mock_get_aws_region.return_value = "us-west-2"
101+
102+
distro = AwsOpenTelemetryDistro()
103+
distro._configure()
104+
105+
# Check agent observability defaults
106+
self.assertEqual(os.environ.get("OTEL_TRACES_EXPORTER"), "otlp")
107+
self.assertEqual(os.environ.get("OTEL_LOGS_EXPORTER"), "otlp")
108+
self.assertEqual(os.environ.get("OTEL_METRICS_EXPORTER"), "awsemf")
109+
self.assertEqual(os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"), "true")
110+
self.assertEqual(
111+
os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"), "https://xray.us-west-2.amazonaws.com/v1/traces"
112+
)
113+
self.assertEqual(
114+
os.environ.get("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"), "https://logs.us-west-2.amazonaws.com/v1/logs"
115+
)
116+
self.assertEqual(os.environ.get("OTEL_TRACES_SAMPLER"), "parentbased_always_on")
117+
self.assertEqual(
118+
os.environ.get("OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"),
119+
"http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
120+
"botocore,boto3,urllib3,requests,starlette",
121+
)
122+
self.assertEqual(os.environ.get("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED"), "true")
123+
self.assertEqual(os.environ.get("OTEL_AWS_APPLICATION_SIGNALS_ENABLED"), "false")
124+
125+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region")
126+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
127+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
128+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
129+
def test_configure_with_agent_observability_no_region(
130+
self, mock_super_configure, mock_apply_patches, mock_is_agent_observability, mock_get_aws_region
131+
):
132+
"""Test that _configure handles missing AWS region gracefully"""
133+
mock_is_agent_observability.return_value = True
134+
mock_get_aws_region.return_value = None # No region found
135+
136+
distro = AwsOpenTelemetryDistro()
137+
distro._configure()
138+
139+
# Check that OTLP endpoints are not set when region is not available
140+
self.assertNotIn("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", os.environ)
141+
self.assertNotIn("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", os.environ)
142+
143+
# But verify that the exporters are still set to otlp (will use default endpoints)
144+
self.assertEqual(os.environ.get("OTEL_TRACES_EXPORTER"), "otlp")
145+
self.assertEqual(os.environ.get("OTEL_LOGS_EXPORTER"), "otlp")
146+
self.assertEqual(os.environ.get("OTEL_METRICS_EXPORTER"), "awsemf")
147+
148+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
149+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
150+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
151+
def test_configure_with_agent_observability_disabled(
152+
self, mock_super_configure, mock_apply_patches, mock_is_agent_observability
153+
):
154+
"""Test that _configure doesn't set agent observability defaults when disabled"""
155+
mock_is_agent_observability.return_value = False
156+
157+
distro = AwsOpenTelemetryDistro()
158+
distro._configure()
159+
160+
# Check that agent observability defaults are not set
161+
self.assertNotIn("OTEL_TRACES_EXPORTER", os.environ)
162+
self.assertNotIn("OTEL_LOGS_EXPORTER", os.environ)
163+
self.assertNotIn("OTEL_METRICS_EXPORTER", os.environ)
164+
self.assertNotIn("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", os.environ)
165+
166+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region")
167+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
168+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
169+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
170+
def test_configure_preserves_existing_env_vars(
171+
self, mock_super_configure, mock_apply_patches, mock_is_agent_observability, mock_get_aws_region
172+
):
173+
"""Test that _configure doesn't override existing environment variables"""
174+
mock_is_agent_observability.return_value = True
175+
mock_get_aws_region.return_value = "us-east-1"
176+
177+
# Set existing values
178+
os.environ["OTEL_TRACES_EXPORTER"] = "custom_exporter"
179+
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = "https://custom.endpoint.com"
180+
181+
distro = AwsOpenTelemetryDistro()
182+
distro._configure()
183+
184+
# Check that existing values are preserved
185+
self.assertEqual(os.environ.get("OTEL_TRACES_EXPORTER"), "custom_exporter")
186+
self.assertEqual(os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"), "https://custom.endpoint.com")
187+
188+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
189+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
190+
@patch("os.getcwd")
191+
@patch("sys.path", new_callable=list)
192+
def test_configure_adds_cwd_to_sys_path(self, mock_sys_path, mock_getcwd, mock_super_configure, mock_apply_patches):
193+
"""Test that _configure adds current working directory to sys.path"""
194+
mock_getcwd.return_value = "/test/working/directory"
195+
196+
distro = AwsOpenTelemetryDistro()
197+
distro._configure()
198+
199+
# Check that cwd was added to sys.path
200+
self.assertIn("/test/working/directory", mock_sys_path)
201+
202+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region")
203+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
204+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
205+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
206+
def test_configure_with_agent_observability_endpoints_already_set(
207+
self, mock_super_configure, mock_apply_patches, mock_is_agent_observability, mock_get_aws_region
208+
):
209+
"""Test that user-provided OTLP endpoints are preserved even when region detection fails"""
210+
mock_is_agent_observability.return_value = True
211+
mock_get_aws_region.return_value = None # No region found
212+
213+
# User has already set custom endpoints
214+
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = "https://my-custom-traces.example.com"
215+
os.environ["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"] = "https://my-custom-logs.example.com"
216+
217+
distro = AwsOpenTelemetryDistro()
218+
distro._configure()
219+
220+
# Verify that user-provided endpoints are preserved
221+
self.assertEqual(os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"), "https://my-custom-traces.example.com")
222+
self.assertEqual(os.environ.get("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"), "https://my-custom-logs.example.com")
223+
224+
# And exporters are still set to otlp
225+
self.assertEqual(os.environ.get("OTEL_TRACES_EXPORTER"), "otlp")
226+
self.assertEqual(os.environ.get("OTEL_LOGS_EXPORTER"), "otlp")

0 commit comments

Comments
 (0)