Skip to content

Auto-Configure ADOT SDK Defaults for Genesis #392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,32 @@ def is_installed(req: str) -> bool:
def is_agent_observability_enabled() -> bool:
"""Is the Agentic AI monitoring flag set to true?"""
return os.environ.get(AGENT_OBSERVABILITY_ENABLED, "false").lower() == "true"


def get_aws_region() -> str:
"""Get AWS region using botocore session.

botocore automatically checks in the following priority order:
1. AWS_REGION environment variable
2. AWS_DEFAULT_REGION environment variable
3. AWS CLI config file (~/.aws/config)
4. EC2 instance metadata service

Returns:
The AWS region if found, None otherwise.
"""
if is_installed("botocore"):
try:
from botocore import session # pylint: disable=import-outside-toplevel

botocore_session = session.Session()
if botocore_session.region_name:
return botocore_session.region_name
except (ImportError, AttributeError):
# botocore failed to determine region
pass

_logger.warning(
"AWS region not found. Please set AWS_REGION environment variable or configure AWS CLI with 'aws configure'."
)
return None
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@
# UDP package size is not larger than 64KB
LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10

# Agent observability defaults
OTEL_TRACES_EXPORTER = "OTEL_TRACES_EXPORTER"
OTEL_LOGS_EXPORTER = "OTEL_LOGS_EXPORTER"
OTEL_METRICS_EXPORTER = "OTEL_METRICS_EXPORTER"
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
OTEL_TRACES_SAMPLER = "OTEL_TRACES_SAMPLER"
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED"

_logger: Logger = getLogger(__name__)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
import sys
from logging import Logger, getLogger

from amazon.opentelemetry.distro._utils import get_aws_region, is_agent_observability_enabled
from amazon.opentelemetry.distro.aws_opentelemetry_configurator import (
APPLICATION_SIGNALS_ENABLED_CONFIG,
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
OTEL_LOGS_EXPORTER,
OTEL_METRICS_EXPORTER,
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
OTEL_TRACES_EXPORTER,
OTEL_TRACES_SAMPLER,
)
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
from opentelemetry.distro import OpenTelemetryDistro
from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_PYTHON_ID_GENERATOR
Expand Down Expand Up @@ -65,5 +78,44 @@ def _configure(self, **kwargs):
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, "base2_exponential_bucket_histogram"
)

if is_agent_observability_enabled():
# "otlp" is already native OTel default, but we set them here to be explicit
# about intended configuration for agent observability
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")
os.environ.setdefault(OTEL_METRICS_EXPORTER, "awsemf")

# Set GenAI capture content default
os.environ.setdefault(OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, "true")

# Set OTLP endpoints with AWS region if not already set
region = get_aws_region()
if region:
os.environ.setdefault(
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, f"https://xray.{region}.amazonaws.com/v1/traces"
)
os.environ.setdefault(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, f"https://logs.{region}.amazonaws.com/v1/logs")
else:
_logger.warning(
"AWS region could not be determined. OTLP endpoints will not be automatically configured. "
"Please set AWS_REGION environment variable or configure OTLP endpoints manually."
)
Comment on lines +99 to +102
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If region and aws otlp endpoints are not set, ADOT will pick the default otlp exporter with default endpoints, correct?


# Set sampler default
os.environ.setdefault(OTEL_TRACES_SAMPLER, "parentbased_always_on")

# Set disabled instrumentations default
os.environ.setdefault(
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
"http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
"botocore,boto3,urllib3,requests,starlette",
)

# Set logging auto instrumentation default
os.environ.setdefault(OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "true")

# Disable AWS Application Signals by default
os.environ.setdefault(APPLICATION_SIGNALS_ENABLED_CONFIG, "false")

if kwargs.get("apply_patches", True):
apply_instrumentation_patches()
Original file line number Diff line number Diff line change
@@ -1,13 +1,76 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
import os
from unittest import TestCase
from unittest.mock import patch

from pkg_resources import DistributionNotFound, require

from amazon.opentelemetry.distro.aws_opentelemetry_distro import AwsOpenTelemetryDistro


class TestAwsOpenTelemetryDistro(TestCase):
def test_package_available(self):
try:
require(["aws-opentelemetry-distro"])
except DistributionNotFound:
self.fail("aws-opentelemetry-distro not installed")

def setUp(self):
# Store original env vars for agent observability tests
self.original_env = {}
env_vars = [
"AGENT_OBSERVABILITY_ENABLED",
"OTEL_TRACES_SAMPLER",
"OTEL_PYTHON_DISABLED_INSTRUMENTATIONS",
"OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED",
"OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
]
for var in env_vars:
self.original_env[var] = os.environ.get(var)
os.environ.pop(var, None)

def tearDown(self):
# Restore original env vars
for var, value in self.original_env.items():
if value is None:
os.environ.pop(var, None)
else:
os.environ[var] = value

def test_agent_observability_sets_new_defaults(self):
# Set up the environment to trigger agent observability
os.environ["AGENT_OBSERVABILITY_ENABLED"] = "true"

# Import and configure
with patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches"):
with patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region", return_value="us-west-2"):
# We need to mock the parent class to avoid its side effects
with patch("opentelemetry.distro.OpenTelemetryDistro._configure"):
distro = AwsOpenTelemetryDistro()
distro._configure()

# Check the new defaults are set
self.assertEqual(os.environ.get("OTEL_TRACES_SAMPLER"), "parentbased_always_on")
self.assertEqual(
os.environ.get("OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"),
"http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,"
"botocore,boto3,urllib3,requests,starlette",
)
self.assertEqual(os.environ.get("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED"), "true")
self.assertEqual(os.environ.get("OTEL_AWS_APPLICATION_SIGNALS_ENABLED"), "false")

def test_new_defaults_not_set_when_agent_observability_disabled(self):
# Don't set AGENT_OBSERVABILITY_ENABLED or set it to false
os.environ.pop("AGENT_OBSERVABILITY_ENABLED", None)

with patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches"):
with patch("opentelemetry.distro.OpenTelemetryDistro._configure"):
distro = AwsOpenTelemetryDistro()
distro._configure()

# These should not be set when agent observability is disabled
self.assertNotIn("OTEL_TRACES_SAMPLER", os.environ)
self.assertNotIn("OTEL_PYTHON_DISABLED_INSTRUMENTATIONS", os.environ)
self.assertNotIn("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED", os.environ)
self.assertNotIn("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", os.environ)
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import os
import sys
from unittest import TestCase
from unittest.mock import MagicMock, patch

from amazon.opentelemetry.distro._utils import get_aws_region


class TestGetAwsRegion(TestCase):
def setUp(self):
# Clear environment variables before each test
os.environ.pop("AWS_REGION", None)
os.environ.pop("AWS_DEFAULT_REGION", None)

def tearDown(self):
# Clean up environment variables after each test
os.environ.pop("AWS_REGION", None)
os.environ.pop("AWS_DEFAULT_REGION", None)

@patch("amazon.opentelemetry.distro._utils.is_installed")
def test_get_aws_region_from_aws_region_env(self, mock_is_installed):
mock_is_installed.return_value = True

# Mock botocore module and session
mock_botocore = MagicMock()
mock_session_instance = MagicMock()
mock_session_instance.region_name = "us-west-2"
mock_botocore.session.Session.return_value = mock_session_instance
sys.modules["botocore"] = mock_botocore
sys.modules["botocore.session"] = mock_botocore.session

os.environ["AWS_REGION"] = "us-west-2"

try:
self.assertEqual(get_aws_region(), "us-west-2")
finally:
# Clean up mock
if "botocore" in sys.modules:
del sys.modules["botocore"]
if "botocore.session" in sys.modules:
del sys.modules["botocore.session"]

@patch("amazon.opentelemetry.distro._utils.is_installed")
def test_get_aws_region_from_aws_default_region_env(self, mock_is_installed):
mock_is_installed.return_value = True

# Mock botocore module and session
mock_botocore = MagicMock()
mock_session_instance = MagicMock()
mock_session_instance.region_name = "eu-central-1"
mock_botocore.session.Session.return_value = mock_session_instance
sys.modules["botocore"] = mock_botocore
sys.modules["botocore.session"] = mock_botocore.session

os.environ["AWS_DEFAULT_REGION"] = "eu-central-1"

try:
self.assertEqual(get_aws_region(), "eu-central-1")
finally:
# Clean up mock
if "botocore" in sys.modules:
del sys.modules["botocore"]
if "botocore.session" in sys.modules:
del sys.modules["botocore.session"]

@patch("amazon.opentelemetry.distro._utils.is_installed")
def test_get_aws_region_prefers_aws_region_over_default(self, mock_is_installed):
mock_is_installed.return_value = True

# Mock botocore module and session
mock_botocore = MagicMock()
mock_session_instance = MagicMock()
mock_session_instance.region_name = "us-east-1"
mock_botocore.session.Session.return_value = mock_session_instance
sys.modules["botocore"] = mock_botocore
sys.modules["botocore.session"] = mock_botocore.session

os.environ["AWS_REGION"] = "us-east-1"
os.environ["AWS_DEFAULT_REGION"] = "eu-west-1"

try:
self.assertEqual(get_aws_region(), "us-east-1")
finally:
# Clean up mock
if "botocore" in sys.modules:
del sys.modules["botocore"]
if "botocore.session" in sys.modules:
del sys.modules["botocore.session"]

@patch("amazon.opentelemetry.distro._utils.is_installed")
def test_get_aws_region_from_botocore_session(self, mock_is_installed):
mock_is_installed.return_value = True

# Mock botocore module and session
mock_botocore = MagicMock()
mock_session_instance = MagicMock()
mock_session_instance.region_name = "ap-southeast-1"
mock_botocore.session.Session.return_value = mock_session_instance
sys.modules["botocore"] = mock_botocore
sys.modules["botocore.session"] = mock_botocore.session

try:
result = get_aws_region()
self.assertEqual(result, "ap-southeast-1")
finally:
# Clean up mock
if "botocore" in sys.modules:
del sys.modules["botocore"]
if "botocore.session" in sys.modules:
del sys.modules["botocore.session"]

@patch("amazon.opentelemetry.distro._utils.is_installed")
@patch("amazon.opentelemetry.distro._utils._logger")
def test_get_aws_region_returns_none_when_no_region_found(self, mock_logger, mock_is_installed):
mock_is_installed.return_value = False

result = get_aws_region()

self.assertIsNone(result)
mock_logger.warning.assert_called_once()

@patch("amazon.opentelemetry.distro._utils.is_installed")
@patch("amazon.opentelemetry.distro._utils._logger")
def test_get_aws_region_returns_none_when_botocore_has_no_region(self, mock_logger, mock_is_installed):
mock_is_installed.return_value = True

# Mock botocore module with no region
mock_botocore = MagicMock()
mock_session_instance = MagicMock()
mock_session_instance.region_name = None
mock_botocore.session.Session.return_value = mock_session_instance
sys.modules["botocore"] = mock_botocore
sys.modules["botocore.session"] = mock_botocore.session

try:
result = get_aws_region()
self.assertIsNone(result)
mock_logger.warning.assert_called_once()
finally:
# Clean up mock
if "botocore" in sys.modules:
del sys.modules["botocore"]
if "botocore.session" in sys.modules:
del sys.modules["botocore.session"]
Loading