Skip to content

Commit d3b512f

Browse files
authored
feat: [BREAKING CHANGE] introduce AWS_AGENTIC_OBSERVABILITY_OPT_IN and refactor agent observability config (#691)
*Description of changes:* - add note for `AGENT_OBSERVABILITY_VERSION` with a new env var `AWS_AGENTIC_OBSERVABILITY_OPT_IN` that controls ADOT-native agentic instrumentation. When enabled, ADOT disables third-party agentic instrumentations. - `AGENT_OBSERVABILITY_ENABLED` is maintained for backwards compatibility, disabling native ADOT instrumentations. - **BREAKING CHANGE**: enable ALL HTTP library instrumentations for `AGENT_OBSERVABILITY_ENABLED` and `AWS_AGENTIC_OBSERVABILITY_OPT_IN` path Other changes: - Add `OTEL_PYTHON_LOG_CORRELATION` to common agent observability config - Remove redundant `OTEL_TRACES_SAMPLER` env variable - Remove hardcoded `localhost` OTLP endpoints from opt-in path 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 c0f08aa commit d3b512f

16 files changed

Lines changed: 306 additions & 104 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t
1212

1313
## Unreleased
1414

15+
- feat: [BREAKING CHANGE] introduce AWS_AGENTIC_OBSERVABILITY_OPT_IN and refactor agent observability config
16+
([#691](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/691))
1517
- feat: propagate HTTP context for MCP requests and prefix all span names with mcp
1618
([#683](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/683))
1719
- feat: add threading instrumentation dependency

aws-opentelemetry-distro/pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ aws_distro = "amazon.opentelemetry.distro.aws_opentelemetry_distro:AwsOpenTeleme
108108
aws_crewai = "amazon.opentelemetry.distro.instrumentation.crewai:CrewAIInstrumentor"
109109
aws_langchain = "amazon.opentelemetry.distro.instrumentation.langchain:LangChainInstrumentor"
110110
aws_mcp = "amazon.opentelemetry.distro.instrumentation.mcp:McpInstrumentor"
111+
# Wraps the upstream OTel OpenAI Agents instrumentor under a unique entry point name.
112+
# This allows us to disable 3rd party "openai_agents" entry points in the opt-in path
113+
# while keeping OpenAI Agents instrumentation active via this alias.
114+
aws_openai_agents = "opentelemetry.instrumentation.openai_agents:OpenAIAgentsInstrumentor"
111115

112116
[project.urls]
113117
Homepage = "https://github.com/aws-observability/aws-otel-python-instrumentation/tree/main/aws-opentelemetry-distro"

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
_logger: Logger = getLogger(__name__)
1212

13+
# Maintained for backwards compatibility. New users should use AWS_AGENTIC_OBSERVABILITY_OPT_IN instead.
1314
AGENT_OBSERVABILITY_ENABLED = "AGENT_OBSERVABILITY_ENABLED"
15+
AWS_AGENTIC_OBSERVABILITY_OPT_IN = "AWS_AGENTIC_OBSERVABILITY_OPT_IN"
1416
OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS = "OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS"
1517

1618

@@ -35,10 +37,20 @@ def is_installed(req: str) -> bool:
3537

3638

3739
def is_agent_observability_enabled() -> bool:
38-
"""Is the Agentic AI monitoring flag set to true?"""
40+
# Maintained for backwards compatibility. New users should use AWS_AGENTIC_OBSERVABILITY_OPT_IN instead.
3941
return os.environ.get(AGENT_OBSERVABILITY_ENABLED, "false").lower() == "true"
4042

4143

44+
def is_aws_agentic_observability_opt_in() -> bool:
45+
"""Is the AI observability opt-in flag set to true?"""
46+
return os.environ.get(AWS_AGENTIC_OBSERVABILITY_OPT_IN, "false").lower() == "true"
47+
48+
49+
def is_agentic_observability_enabled() -> bool:
50+
"""Returns True if either AGENT_OBSERVABILITY_ENABLED or AWS_AGENTIC_OBSERVABILITY_OPT_IN is set to true."""
51+
return is_agent_observability_enabled() or is_aws_agentic_observability_opt_in()
52+
53+
4254
def should_add_application_signals_dimensions() -> bool:
4355
"""Should Service and Environment Application Signals dimensions be added to EMF logs?"""
4456
return os.environ.get(OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS, "true").lower() == "true"

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from amazon.opentelemetry.distro._aws_attribute_keys import AWS_LOCAL_SERVICE, AWS_SERVICE_TYPE
1919
from amazon.opentelemetry.distro._aws_resource_attribute_configurator import get_service_attribute
20-
from amazon.opentelemetry.distro._utils import get_aws_session, is_agent_observability_enabled
20+
from amazon.opentelemetry.distro._utils import get_aws_session, is_agentic_observability_enabled
2121
from amazon.opentelemetry.distro.always_record_sampler import AlwaysRecordSampler
2222
from amazon.opentelemetry.distro.attribute_propagating_span_processor_builder import (
2323
AttributePropagatingSpanProcessorBuilder,
@@ -204,7 +204,7 @@ def _initialize_components():
204204
AwsEksResourceDetector(),
205205
AwsEcsResourceDetector(),
206206
]
207-
if not (_is_lambda_environment() or is_agent_observability_enabled())
207+
if not (_is_lambda_environment() or is_agentic_observability_enabled())
208208
else []
209209
)
210210

@@ -326,7 +326,7 @@ def _export_unsampled_span_for_lambda(trace_provider: TracerProvider, resource:
326326

327327

328328
def _export_unsampled_span_for_agent_observability(trace_provider: TracerProvider, resource: Resource = None):
329-
if not is_agent_observability_enabled():
329+
if not is_agentic_observability_enabled():
330330
return
331331

332332
traces_endpoint = os.environ.get(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)
@@ -467,7 +467,7 @@ def _customize_log_record_processor(logger_provider: LoggerProvider, log_exporte
467467
if not log_exporter:
468468
return
469469

470-
if is_agent_observability_enabled():
470+
if is_agentic_observability_enabled():
471471
# pylint: disable=import-outside-toplevel
472472
from amazon.opentelemetry.distro.exporter.otlp.aws.logs._aws_cw_otlp_batch_log_record_processor import (
473473
AwsCloudWatchOtlpBatchLogRecordProcessor,
@@ -526,7 +526,7 @@ def _customize_span_processors(provider: TracerProvider, resource: Resource, sam
526526
# AI applications typically have low throughput traffic patterns and require
527527
# comprehensive monitoring to catch subtle failure modes like hallucinations
528528
# and quality degradation that sampling could miss.
529-
if is_agent_observability_enabled():
529+
if is_agentic_observability_enabled():
530530
_export_unsampled_span_for_agent_observability(provider, resource)
531531
baggage_keys.add("session.id")
532532

@@ -633,7 +633,7 @@ def _customize_resource(resource: Resource) -> Resource:
633633

634634
custom_attributes = {AWS_LOCAL_SERVICE: service_name}
635635

636-
if is_agent_observability_enabled():
636+
if is_agentic_observability_enabled():
637637
# Add aws.service.type if it doesn't exist in the resource
638638
if resource and resource.attributes.get(AWS_SERVICE_TYPE) is None:
639639
# Set a default agent type for AI agent observability
@@ -919,7 +919,7 @@ def _create_aws_otlp_exporter(endpoint: str, service: str, region: str):
919919
from amazon.opentelemetry.distro.exporter.otlp.aws.traces.otlp_aws_span_exporter import OTLPAwsSpanExporter
920920

921921
if service == XRAY_SERVICE:
922-
if is_agent_observability_enabled():
922+
if is_agentic_observability_enabled():
923923
# Span exporter needs an instance of logger provider in ai agent
924924
# observability case because we need to split input/output prompts
925925
# from span attributes and send them to the logs pipeline per

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

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS,
2121
get_aws_region,
2222
is_agent_observability_enabled,
23+
is_aws_agentic_observability_opt_in,
2324
is_installed,
2425
)
2526
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import APPLICATION_SIGNALS_ENABLED_CONFIG
@@ -36,28 +37,35 @@
3637
from opentelemetry.instrumentation.auto_instrumentation import _load
3738
from opentelemetry.instrumentation.environment_variables import OTEL_PYTHON_DISABLED_INSTRUMENTATIONS
3839
from opentelemetry.instrumentation.logging import LEVELS
39-
from opentelemetry.instrumentation.logging.environment_variables import OTEL_PYTHON_LOG_LEVEL
40+
from opentelemetry.instrumentation.logging.environment_variables import (
41+
OTEL_PYTHON_LOG_CORRELATION,
42+
OTEL_PYTHON_LOG_LEVEL,
43+
)
4044
from opentelemetry.sdk.environment_variables import (
4145
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED as OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
4246
)
4347
from opentelemetry.sdk.environment_variables import (
4448
OTEL_EXPORTER_OTLP_ENDPOINT,
4549
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
4650
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION,
47-
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
4851
OTEL_EXPORTER_OTLP_PROTOCOL,
4952
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
50-
OTEL_TRACES_SAMPLER,
5153
)
54+
from opentelemetry.util._importlib_metadata import EntryPoint
5255

5356
_logger: Logger = getLogger(__name__)
5457
# Suppress configurator warnings from auto-instrumentation
5558
_load._logger.setLevel(LEVELS.get(os.environ.get(OTEL_PYTHON_LOG_LEVEL, "error").lower(), ERROR))
5659

5760

5861
AGENT_OBSERVABILITY_DISABLED_INSTRUMENTATIONS = (
59-
"http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
60-
"urllib3,requests,system_metrics,google-genai,crewai,langchain,mcp"
62+
"sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
63+
"system_metrics,google-genai,aws_crewai,aws_langchain,aws_mcp,aws_openai_agents"
64+
)
65+
66+
AWS_AGENTIC_OBSERVABILITY_DISABLED_INSTRUMENTATIONS = (
67+
"sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
68+
"system_metrics,google-genai,crewai,langchain,mcp,openai_agents"
6169
)
6270

6371

@@ -134,51 +142,66 @@ def _configure(self, **kwargs):
134142
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, "base2_exponential_bucket_histogram"
135143
)
136144

137-
if is_agent_observability_enabled():
138-
os.environ.setdefault("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true")
139-
os.environ.setdefault(OTEL_TRACES_SAMPLER, "parentbased_always_on")
140-
os.environ.setdefault(
141-
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
142-
AGENT_OBSERVABILITY_DISABLED_INSTRUMENTATIONS,
145+
if is_aws_agentic_observability_opt_in():
146+
_logger.info("AWS Agentic Observability enabled.")
147+
self._configure_common_agent_observability(AWS_AGENTIC_OBSERVABILITY_DISABLED_INSTRUMENTATIONS)
148+
os.environ.setdefault(OTEL_METRICS_EXPORTER, "otlp")
149+
os.environ.setdefault("CREWAI_DISABLE_TELEMETRY", "true")
150+
elif is_agent_observability_enabled():
151+
# Maintained for backwards compatibility. New users should use AWS_AGENTIC_OBSERVABILITY_OPT_IN instead.
152+
_logger.info(
153+
"AGENT_OBSERVABILITY_ENABLED is set. Consider using AWS_AGENTIC_OBSERVABILITY_OPT_IN for ADOT Agentic Observability."
143154
)
144-
os.environ.setdefault(OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "true")
145-
os.environ.setdefault(APPLICATION_SIGNALS_ENABLED_CONFIG, "false")
146-
os.environ.setdefault(OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS, "false")
147-
148-
version = os.environ.get("AGENT_OBSERVABILITY_VERSION", "1")
149-
if version == "1":
150-
_configure_agent_observability_v1(get_aws_region())
151-
else:
152-
_configure_agent_observability_v2()
155+
self._configure_common_agent_observability(AGENT_OBSERVABILITY_DISABLED_INSTRUMENTATIONS)
156+
os.environ.setdefault(OTEL_METRICS_EXPORTER, "awsemf")
157+
region = get_aws_region()
158+
if not os.environ.get(OTEL_EXPORTER_OTLP_ENDPOINT):
159+
if region:
160+
os.environ.setdefault(
161+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, f"https://xray.{region}.amazonaws.com/v1/traces"
162+
)
163+
os.environ.setdefault(
164+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, f"https://logs.{region}.amazonaws.com/v1/logs"
165+
)
166+
else:
167+
_logger.warning(
168+
"AWS region could not be determined. OTLP endpoints will not be automatically configured. "
169+
"Please set AWS_REGION environment variable or configure OTLP endpoints manually."
170+
)
153171

154172
super(AwsOpenTelemetryDistro, self)._configure()
155173

156174
if kwargs.get("apply_patches", True):
157175
apply_instrumentation_patches()
158176

159-
160-
def _configure_agent_observability_v1(region: str) -> None:
161-
# "otlp" is already native OTel default, but we set them here to be explicit
162-
# about intended configuration for agent observability
163-
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
164-
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")
165-
os.environ.setdefault(OTEL_METRICS_EXPORTER, "awsemf")
166-
if not os.environ.get(OTEL_EXPORTER_OTLP_ENDPOINT):
167-
if region:
168-
os.environ.setdefault(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, f"https://xray.{region}.amazonaws.com/v1/traces")
169-
os.environ.setdefault(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, f"https://logs.{region}.amazonaws.com/v1/logs")
170-
else:
171-
_logger.warning(
172-
"AWS region could not be determined. OTLP endpoints will not be automatically configured. "
173-
"Please set AWS_REGION environment variable or configure OTLP endpoints manually."
174-
)
175-
176-
177-
def _configure_agent_observability_v2() -> None:
178-
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
179-
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")
180-
os.environ.setdefault(OTEL_METRICS_EXPORTER, "otlp")
181-
if not os.environ.get(OTEL_EXPORTER_OTLP_ENDPOINT):
182-
os.environ.setdefault(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://localhost:4318/v1/traces")
183-
os.environ.setdefault(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, "http://localhost:4318/v1/logs")
184-
os.environ.setdefault(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, "http://localhost:4318/v1/metrics")
177+
def load_instrumentor(self, entry_point: EntryPoint, **kwargs):
178+
if self._should_skip_instrumentor(entry_point):
179+
return
180+
super().load_instrumentor(entry_point, **kwargs)
181+
182+
@staticmethod
183+
def _should_skip_instrumentor(entry_point: EntryPoint) -> bool:
184+
# Some third-party SDKs register the same entry point name as the upstream
185+
# OTel packages that we depend on. For Agentic Observability legacy mode, skip our bundled
186+
# OTel instrumentation so that existing third-party setups are not brokens.
187+
if (
188+
is_agent_observability_enabled()
189+
and not is_aws_agentic_observability_opt_in()
190+
and entry_point.dist
191+
and entry_point.name == "openai_agents"
192+
and entry_point.dist.name == "opentelemetry-instrumentation-openai-agents-v2"
193+
):
194+
return True
195+
# TODO: add additional skip conditions here as needed
196+
return False
197+
198+
@staticmethod
199+
def _configure_common_agent_observability(disabled_instrumentations: str) -> None:
200+
os.environ.setdefault("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true")
201+
os.environ.setdefault(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, disabled_instrumentations)
202+
os.environ.setdefault(OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "true")
203+
os.environ.setdefault(OTEL_PYTHON_LOG_CORRELATION, "true")
204+
os.environ.setdefault(APPLICATION_SIGNALS_ENABLED_CONFIG, "false")
205+
os.environ.setdefault(OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS, "false")
206+
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
207+
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
4+
from amazon.opentelemetry.distro._utils import is_agentic_observability_enabled
55
from amazon.opentelemetry.distro.version import __version__
66

77

88
def build_user_agent() -> str:
99
user_agent = f"ADOT-OTLP-Exporter-Python/{__version__}"
1010

11-
if is_agent_observability_enabled():
11+
if is_agentic_observability_enabled():
1212
user_agent = f"ADOT-GenAI-OTLP-Exporter-Python/{__version__}"
1313
return user_agent
1414

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from botocore.session import Session
88

9-
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
9+
from amazon.opentelemetry.distro._utils import is_agentic_observability_enabled
1010
from amazon.opentelemetry.distro.exporter.otlp.aws.common._aws_http_headers import _OTLP_AWS_HTTP_HEADERS
1111
from amazon.opentelemetry.distro.exporter.otlp.aws.common.aws_auth_session import AwsAuthSession
1212
from amazon.opentelemetry.distro.llo_handler import LLOHandler
@@ -61,7 +61,7 @@ def __init__(
6161

6262
def _ensure_llo_handler(self):
6363
"""Lazily initialize LLO handler when needed to avoid initialization order issues"""
64-
if self._llo_handler is None and is_agent_observability_enabled():
64+
if self._llo_handler is None and is_agentic_observability_enabled():
6565
if self._logger_provider is None:
6666
try:
6767
self._logger_provider = get_logger_provider()
@@ -77,7 +77,7 @@ def _ensure_llo_handler(self):
7777

7878
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
7979
try:
80-
if is_agent_observability_enabled() and self._ensure_llo_handler():
80+
if is_agentic_observability_enabled() and self._ensure_llo_handler():
8181
llo_processed_spans = self._llo_handler.process_spans(spans)
8282
return super().export(llo_processed_spans)
8383
except Exception: # pylint: disable=broad-exception-caught

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_starlette_patches.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
44
from logging import Logger, getLogger
55

6-
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
6+
from amazon.opentelemetry.distro._utils import is_agentic_observability_enabled
77

88
_logger: Logger = getLogger(__name__)
99

@@ -24,7 +24,7 @@ def _apply_starlette_instrumentation_patches() -> None:
2424
#
2525
# Issue for tracking a feature to customize this setting within Starlette:
2626
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3725
27-
if is_agent_observability_enabled():
27+
if is_agentic_observability_enabled():
2828
original_init = OpenTelemetryMiddleware.__init__
2929

3030
def patched_init(self, app, **kwargs):

0 commit comments

Comments
 (0)