Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t

- fix(mcp-instrumentation): always inject W3C trace context into outbound HTTP request headers so MCP servers that read context only from HTTP (API Gateways, service meshes, non-Python MCP servers) can join the caller's trace, even with `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION` enabled
([#827](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/827))
- Nightly dependency update: OpenTelemetry 1.44.0/0.65b0
([#799](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/799))
- feat(genai): capture user input and agent output on llama_index invoke_agent spans
([#824](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/824))
- fix(crewai): use native per-call token usage when crewai provides it
Expand Down
109 changes: 54 additions & 55 deletions aws-opentelemetry-distro/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,63 +26,62 @@ classifiers = [
]

dependencies = [
"opentelemetry-api == 1.42.1",
"opentelemetry-sdk == 1.42.1",
"opentelemetry-exporter-otlp-proto-grpc == 1.42.1",
"opentelemetry-exporter-otlp-proto-http == 1.42.1",
"opentelemetry-propagator-b3 == 1.42.1",
"opentelemetry-propagator-jaeger == 1.42.1",
"opentelemetry-exporter-otlp-proto-common == 1.42.1",
"opentelemetry-api == 1.44.0",
"opentelemetry-sdk == 1.44.0",
"opentelemetry-exporter-otlp-proto-grpc == 1.44.0",
"opentelemetry-exporter-otlp-proto-http == 1.44.0",
"opentelemetry-propagator-b3 == 1.44.0",
"opentelemetry-propagator-jaeger == 1.44.0",
"opentelemetry-exporter-otlp-proto-common == 1.44.0",
"opentelemetry-sdk-extension-aws == 2.1.0",
"opentelemetry-propagator-aws-xray == 1.0.2",
"opentelemetry-distro == 0.63b1",
"opentelemetry-processor-baggage == 0.63b1",
"opentelemetry-propagator-ot-trace == 0.63b1",
"opentelemetry-instrumentation == 0.63b1",
"opentelemetry-instrumentation-aws-lambda == 0.63b1",
"opentelemetry-instrumentation-aio-pika == 0.63b1",
"opentelemetry-instrumentation-aiohttp-client == 0.63b1",
"opentelemetry-instrumentation-aiokafka == 0.63b1",
"opentelemetry-instrumentation-aiopg == 0.63b1",
"opentelemetry-instrumentation-asgi == 0.63b1",
"opentelemetry-instrumentation-asyncpg == 0.63b1",
"opentelemetry-instrumentation-boto3sqs == 0.63b1",
"opentelemetry-instrumentation-botocore == 0.63b1",
"opentelemetry-instrumentation-celery == 0.63b1",
"opentelemetry-instrumentation-confluent-kafka == 0.63b1",
"opentelemetry-instrumentation-dbapi == 0.63b1",
"opentelemetry-instrumentation-django == 0.63b1",
"opentelemetry-instrumentation-elasticsearch == 0.63b1",
"opentelemetry-instrumentation-falcon == 0.63b1",
"opentelemetry-instrumentation-fastapi == 0.63b1",
"opentelemetry-instrumentation-flask == 0.63b1",
"opentelemetry-instrumentation-grpc == 0.63b1",
"opentelemetry-instrumentation-httpx == 0.63b1",
"opentelemetry-instrumentation-jinja2 == 0.63b1",
"opentelemetry-instrumentation-kafka-python == 0.63b1",
"opentelemetry-instrumentation-logging == 0.63b1",
"opentelemetry-instrumentation-mysql == 0.63b1",
"opentelemetry-instrumentation-mysqlclient == 0.63b1",
"opentelemetry-instrumentation-pika == 0.63b1",
"opentelemetry-instrumentation-psycopg2 == 0.63b1",
"opentelemetry-instrumentation-pymemcache == 0.63b1",
"opentelemetry-instrumentation-pymongo == 0.63b1",
"opentelemetry-instrumentation-pymysql == 0.63b1",
"opentelemetry-instrumentation-pyramid == 0.63b1",
"opentelemetry-instrumentation-redis == 0.63b1",
"opentelemetry-instrumentation-remoulade == 0.63b1",
"opentelemetry-instrumentation-requests == 0.63b1",
"opentelemetry-instrumentation-sqlalchemy == 0.63b1",
"opentelemetry-instrumentation-sqlite3 == 0.63b1",
"opentelemetry-instrumentation-starlette == 0.63b1",
"opentelemetry-instrumentation-system-metrics == 0.63b1",
"opentelemetry-instrumentation-threading == 0.63b1",
"opentelemetry-instrumentation-tornado == 0.63b1",
"opentelemetry-instrumentation-tortoiseorm == 0.63b1",
"opentelemetry-instrumentation-urllib == 0.63b1",
"opentelemetry-instrumentation-urllib3 == 0.63b1",
"opentelemetry-instrumentation-wsgi == 0.63b1",
"opentelemetry-instrumentation-cassandra == 0.63b1",
"opentelemetry-distro == 0.65b0",
"opentelemetry-processor-baggage == 0.65b0",
"opentelemetry-propagator-ot-trace == 0.65b0",
"opentelemetry-instrumentation == 0.65b0",
"opentelemetry-instrumentation-aws-lambda == 0.65b0",
"opentelemetry-instrumentation-aio-pika == 0.65b0",
"opentelemetry-instrumentation-aiohttp-client == 0.65b0",
"opentelemetry-instrumentation-aiokafka == 0.65b0",
"opentelemetry-instrumentation-aiopg == 0.65b0",
"opentelemetry-instrumentation-asgi == 0.65b0",
"opentelemetry-instrumentation-asyncpg == 0.65b0",
"opentelemetry-instrumentation-boto3sqs == 0.65b0",
"opentelemetry-instrumentation-botocore == 0.65b0",
"opentelemetry-instrumentation-celery == 0.65b0",
"opentelemetry-instrumentation-confluent-kafka == 0.65b0",
"opentelemetry-instrumentation-dbapi == 0.65b0",
"opentelemetry-instrumentation-django == 0.65b0",
"opentelemetry-instrumentation-falcon == 0.65b0",
"opentelemetry-instrumentation-fastapi == 0.65b0",
"opentelemetry-instrumentation-flask == 0.65b0",
"opentelemetry-instrumentation-grpc == 0.65b0",
"opentelemetry-instrumentation-httpx == 0.65b0",
"opentelemetry-instrumentation-jinja2 == 0.65b0",
"opentelemetry-instrumentation-kafka-python == 0.65b0",
"opentelemetry-instrumentation-logging == 0.65b0",
"opentelemetry-instrumentation-mysql == 0.65b0",
"opentelemetry-instrumentation-mysqlclient == 0.65b0",
"opentelemetry-instrumentation-pika == 0.65b0",
"opentelemetry-instrumentation-psycopg2 == 0.65b0",
"opentelemetry-instrumentation-pymemcache == 0.65b0",
"opentelemetry-instrumentation-pymongo == 0.65b0",
"opentelemetry-instrumentation-pymysql == 0.65b0",
"opentelemetry-instrumentation-pyramid == 0.65b0",
"opentelemetry-instrumentation-redis == 0.65b0",
"opentelemetry-instrumentation-remoulade == 0.65b0",
"opentelemetry-instrumentation-requests == 0.65b0",
"opentelemetry-instrumentation-sqlalchemy == 0.65b0",
"opentelemetry-instrumentation-sqlite3 == 0.65b0",
"opentelemetry-instrumentation-starlette == 0.65b0",
"opentelemetry-instrumentation-system-metrics == 0.65b0",
"opentelemetry-instrumentation-threading == 0.65b0",
"opentelemetry-instrumentation-tornado == 0.65b0",
"opentelemetry-instrumentation-tortoiseorm == 0.65b0",
"opentelemetry-instrumentation-urllib == 0.65b0",
"opentelemetry-instrumentation-urllib3 == 0.65b0",
"opentelemetry-instrumentation-wsgi == 0.65b0",
"opentelemetry-instrumentation-cassandra == 0.65b0",
"opentelemetry-instrumentation-openai-agents-v2 == 0.1.0",
"cachetools == 6.2.4",
"urllib3 >= 2.7.0; python_version >= '3.10'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
from amazon.opentelemetry.distro.sampler.aws_xray_remote_sampler import AwsXRayRemoteSampler
from amazon.opentelemetry.distro.scope_based_exporter import ScopeBasedPeriodicExportingMetricReader
from amazon.opentelemetry.distro.scope_based_filtering_view import ScopeBasedRetainingView
from opentelemetry._events import set_event_logger_provider
from opentelemetry._logs import get_logger_provider, set_logger_provider
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as OTLPHttpOTLPMetricExporter
Expand All @@ -59,7 +58,6 @@
_OTelSDKConfigurator,
_overwrite_logging_config_fns,
)
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, ConsoleLogRecordExporter, LogRecordExporter
from opentelemetry.sdk.environment_variables import (
Expand Down Expand Up @@ -319,9 +317,6 @@ def _init_logging(
logger_provider=provider, log_exporter=_customize_logs_exporter(exporter_class(**exporter_args))
)

event_logger_provider = EventLoggerProvider(logger_provider=provider)
set_event_logger_provider(event_logger_provider)

if setup_logging_handler:
# Add OTel handler
handler = LoggingHandler(level=logging.NOTSET, logger_provider=provider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
import os
import threading

from opentelemetry._events import Event
from opentelemetry._logs import LogRecord, SeverityNumber
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.resources import Resource
Expand Down Expand Up @@ -53,23 +52,23 @@ def __init__(self, resource=None, logs_endpoint=None):
self._resource = resource
endpoint = logs_endpoint or os.environ.get(_LOGS_ENDPOINT_ENV_VAR, "")
self._logs_endpoint = endpoint.strip() if endpoint.strip() else _DEFAULT_LOGS_ENDPOINT
self._event_logger = None
self._logger = None
self._logger_provider = None
self._init_failed = False
self._lock = threading.Lock()

def _ensure_initialized(self):
"""Lazily initialize the LoggerProvider and EventLogger on first use.
"""Lazily initialize the LoggerProvider and Logger on first use.

Uses double-checked locking to be thread-safe without contention on the
common path (already initialized).
"""
if self._event_logger is not None:
if self._logger is not None:
return True
if self._init_failed:
return False
with self._lock:
if self._event_logger is not None:
if self._logger is not None:
return True
if self._init_failed:
return False
Expand All @@ -81,10 +80,7 @@ def _ensure_initialized(self):
self._logger_provider = LoggerProvider(resource=resource)
self._logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))

event_logger_provider = EventLoggerProvider(logger_provider=self._logger_provider)
self._event_logger = event_logger_provider.get_event_logger(
_INSTRUMENTATION_SCOPE, _INSTRUMENTATION_VERSION
)
self._logger = self._logger_provider.get_logger(_INSTRUMENTATION_SCOPE, _INSTRUMENTATION_VERSION)

logger.debug("DI OTLP emitter initialized (endpoint: %s)", self._logs_endpoint)
return True
Expand Down Expand Up @@ -165,18 +161,18 @@ def _emit_snapshot_internal(self, snapshot, config):
if trace_id and span_id:
trace_flags = TraceFlags(0x01) # SAMPLED

# Emit as Event
event = Event(
name=_EVENT_NAME,
log_record = LogRecord(
event_name=_EVENT_NAME,
timestamp=snapshot.timestamp * 1_000_000, # ms to ns
severity_number=SeverityNumber.INFO,
body=body if body else None,
attributes=attributes,
trace_id=trace_id,
span_id=span_id,
trace_flags=trace_flags,
)

self._event_logger.emit(event)
self._logger.emit(log_record)

def shutdown(self):
"""Flush and shutdown the owned LoggerProvider."""
Expand All @@ -188,10 +184,10 @@ def shutdown(self):
except Exception: # pylint: disable=broad-exception-caught
logger.warning("Error shutting down DI OTLP LoggerProvider", exc_info=True)
self._logger_provider = None
self._event_logger = None
self._logger = None

def reset(self):
"""Reset state for post-fork cleanup. Does not flush — parent's threads are dead."""
self._logger_provider = None
self._event_logger = None
self._logger = None
self._init_failed = False
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ def shutdown(self):
self._out.flush()
self._shutdown = True

def force_flush(self, timeout_millis: int = 10000) -> bool:
self._out.flush()
return True

@staticmethod
def _to_compact_json(data) -> str:
# Support both ReadableLogRecord (1.39+) and LogData (older) APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
from enum import Enum
from typing import Any, Dict, List, Optional, Sequence, TypedDict

from opentelemetry._events import Event
from opentelemetry._logs import LogRecord, SeverityNumber
from opentelemetry.attributes import BoundedAttributes
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk.trace import Event as SpanEvent
from opentelemetry.sdk.trace import ReadableSpan
Expand Down Expand Up @@ -209,7 +208,6 @@ def __init__(self, logger_provider: LoggerProvider):
Global LoggerProvider instance injected from our AwsOpenTelemetryConfigurator
"""
self._logger_provider = logger_provider
self._event_logger_provider = EventLoggerProvider(logger_provider=self._logger_provider)

self._build_pattern_matchers()

Expand Down Expand Up @@ -537,23 +535,24 @@ def _emit_llo_attributes(
return

timestamp = event_timestamp if event_timestamp is not None else span.end_time
event_logger = self._event_logger_provider.get_event_logger(span.instrumentation_scope.name)
logger = self._logger_provider.get_logger(span.instrumentation_scope.name)

event_attributes = {}
event_attributes = {"event.name": span.instrumentation_scope.name}
if span.attributes and "session.id" in span.attributes:
event_attributes["session.id"] = span.attributes["session.id"]

event = Event(
name=span.instrumentation_scope.name,
log_record = LogRecord(
event_name=span.instrumentation_scope.name,
timestamp=timestamp,
severity_number=SeverityNumber.INFO,
body=event_body,
attributes=event_attributes if event_attributes else None,
attributes=event_attributes,
trace_id=span.context.trace_id,
span_id=span.context.span_id,
trace_flags=span.context.trace_flags,
)

event_logger.emit(event)
logger.emit(log_record)
_logger.debug("Emitted Gen AI Event with input/output message format")

def _filter_attributes(self, attributes: types.Attributes) -> types.Attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,6 @@ def configure_lite_mode():
"confluent_kafka": "opentelemetry.instrumentation.confluent_kafka:ConfluentKafkaInstrumentor",
"dbapi": "opentelemetry.instrumentation.dbapi:DatabaseApiIntegration",
"django": "opentelemetry.instrumentation.django:DjangoInstrumentor",
"elasticsearch": "opentelemetry.instrumentation.elasticsearch:ElasticsearchInstrumentor",
"falcon": "opentelemetry.instrumentation.falcon:FalconInstrumentor",
"fastapi": "opentelemetry.instrumentation.fastapi:FastAPIInstrumentor",
"flask": "opentelemetry.instrumentation.flask:FlaskInstrumentor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ def setUp(self):
self.emitter = SnapshotOtlpEmitter()
# Directly set the internals for testing
self.emitter._logger_provider = self.logger_provider
from opentelemetry.sdk._events import EventLoggerProvider

event_logger_provider = EventLoggerProvider(logger_provider=self.logger_provider)
self.emitter._event_logger = event_logger_provider.get_event_logger("aws.dynamic_instrumentation", "1.0")
self.emitter._logger = self.logger_provider.get_logger("aws.dynamic_instrumentation", "1.0")

def tearDown(self):
self.logger_provider.shutdown()
Expand Down Expand Up @@ -230,7 +227,7 @@ def test_reset_clears_state(self):
self.emitter.reset()

self.assertIsNone(self.emitter._logger_provider)
self.assertIsNone(self.emitter._event_logger)
self.assertIsNone(self.emitter._logger)
self.assertFalse(self.emitter._init_failed)

def test_emit_after_init_failure_is_silent(self):
Expand Down
Loading
Loading