Skip to content

Commit 409cb6a

Browse files
authored
Genesis LLO Support in ADOT SDK (#361)
## What does this pull request do? Adds support to handle LLO from third-party instrumentation SDKs in ADOT SDK. The following SDKs are supported: - Traceloop/Openllmetry - OpenInference - OpenLit Note: OTel dependencies in ADOT SDK have been loosened as a short-term workaround to support the various conflicting dependency requirements of third-party instrumentation SDKs. ## Test plan Built this custom ADOT SDK into various sample apps and exported the span and logs data to the OTLP X-Ray and Logs endpoint, respectively, to validate the LLO extraction and transformation to Gen AI Events. Configurations tested: - LangChain + Traceloop/Openllmetry - LangChan + OpenInference - LangChain + OpenLit - CrewAI + Traceloop/Openllmetry - CrewAI + OpenInference - CrewAI + OpenLit Environment variable configuration: ``` λ env OTEL_METRICS_EXPORTER=none \ OTEL_TRACES_EXPORTER=otlp \ OTEL_LOGS_EXPORTER=otlp \ OTEL_PYTHON_DISTRO=aws_distro \ OTEL_PYTHON_CONFIGURATOR=aws_configurator \ OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ OTEL_EXPORTER_OTLP_LOGS_HEADERS="x-aws-log-group=test,x-aws-log-stream=default" \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://xray.us-east-1.amazonaws.com/v1/traces \ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs.us-east-1.amazonaws.com/v1/logs \ OTEL_RESOURCE_ATTRIBUTES="service.name=langchain-app" \ OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED="true" \ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT="true" \ OTEL_PYTHON_DISABLED_INSTRUMENTATIONS="http,sqlalchemy,psycopg2,pymysql,sqlite3,aiopg,asyncpg,mysql_connector,botocore,boto3,urllib3,requests,starlette" \ AGENT_OBSERVABILITY_ENABLED="true" \ python app.py ``` 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 b6b7cf8 commit 409cb6a

File tree

9 files changed

+1443
-71
lines changed

9 files changed

+1443
-71
lines changed

aws-opentelemetry-distro/pyproject.toml

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,61 +25,61 @@ classifiers = [
2525
]
2626

2727
dependencies = [
28-
"opentelemetry-api == 1.27.0",
29-
"opentelemetry-sdk == 1.27.0",
30-
"opentelemetry-exporter-otlp-proto-grpc == 1.27.0",
31-
"opentelemetry-exporter-otlp-proto-http == 1.27.0",
32-
"opentelemetry-propagator-b3 == 1.27.0",
33-
"opentelemetry-propagator-jaeger == 1.27.0",
34-
"opentelemetry-exporter-otlp-proto-common == 1.27.0",
35-
"opentelemetry-sdk-extension-aws == 2.0.2",
36-
"opentelemetry-propagator-aws-xray == 1.0.1",
37-
"opentelemetry-distro == 0.48b0",
38-
"opentelemetry-propagator-ot-trace == 0.48b0",
39-
"opentelemetry-instrumentation == 0.48b0",
40-
"opentelemetry-instrumentation-aws-lambda == 0.48b0",
41-
"opentelemetry-instrumentation-aio-pika == 0.48b0",
42-
"opentelemetry-instrumentation-aiohttp-client == 0.48b0",
43-
"opentelemetry-instrumentation-aiopg == 0.48b0",
44-
"opentelemetry-instrumentation-asgi == 0.48b0",
45-
"opentelemetry-instrumentation-asyncpg == 0.48b0",
46-
"opentelemetry-instrumentation-boto == 0.48b0",
47-
"opentelemetry-instrumentation-boto3sqs == 0.48b0",
48-
"opentelemetry-instrumentation-botocore == 0.48b0",
49-
"opentelemetry-instrumentation-celery == 0.48b0",
50-
"opentelemetry-instrumentation-confluent-kafka == 0.48b0",
51-
"opentelemetry-instrumentation-dbapi == 0.48b0",
52-
"opentelemetry-instrumentation-django == 0.48b0",
53-
"opentelemetry-instrumentation-elasticsearch == 0.48b0",
54-
"opentelemetry-instrumentation-falcon == 0.48b0",
55-
"opentelemetry-instrumentation-fastapi == 0.48b0",
56-
"opentelemetry-instrumentation-flask == 0.48b0",
57-
"opentelemetry-instrumentation-grpc == 0.48b0",
58-
"opentelemetry-instrumentation-httpx == 0.48b0",
59-
"opentelemetry-instrumentation-jinja2 == 0.48b0",
60-
"opentelemetry-instrumentation-kafka-python == 0.48b0",
61-
"opentelemetry-instrumentation-logging == 0.48b0",
62-
"opentelemetry-instrumentation-mysql == 0.48b0",
63-
"opentelemetry-instrumentation-mysqlclient == 0.48b0",
64-
"opentelemetry-instrumentation-pika == 0.48b0",
65-
"opentelemetry-instrumentation-psycopg2 == 0.48b0",
66-
"opentelemetry-instrumentation-pymemcache == 0.48b0",
67-
"opentelemetry-instrumentation-pymongo == 0.48b0",
68-
"opentelemetry-instrumentation-pymysql == 0.48b0",
69-
"opentelemetry-instrumentation-pyramid == 0.48b0",
70-
"opentelemetry-instrumentation-redis == 0.48b0",
71-
"opentelemetry-instrumentation-remoulade == 0.48b0",
72-
"opentelemetry-instrumentation-requests == 0.48b0",
73-
"opentelemetry-instrumentation-sqlalchemy == 0.48b0",
74-
"opentelemetry-instrumentation-sqlite3 == 0.48b0",
75-
"opentelemetry-instrumentation-starlette == 0.48b0",
76-
"opentelemetry-instrumentation-system-metrics == 0.48b0",
77-
"opentelemetry-instrumentation-tornado == 0.48b0",
78-
"opentelemetry-instrumentation-tortoiseorm == 0.48b0",
79-
"opentelemetry-instrumentation-urllib == 0.48b0",
80-
"opentelemetry-instrumentation-urllib3 == 0.48b0",
81-
"opentelemetry-instrumentation-wsgi == 0.48b0",
82-
"opentelemetry-instrumentation-cassandra == 0.48b0",
28+
"opentelemetry-api >= 1.29.0",
29+
"opentelemetry-sdk >= 1.29.0",
30+
"opentelemetry-exporter-otlp-proto-grpc >= 1.29.0",
31+
"opentelemetry-exporter-otlp-proto-http >= 1.29.0",
32+
"opentelemetry-propagator-b3 >= 1.29.0",
33+
"opentelemetry-propagator-jaeger >= 1.29.0",
34+
"opentelemetry-exporter-otlp-proto-common >= 1.29.0",
35+
"opentelemetry-sdk-extension-aws >= 2.0.2",
36+
"opentelemetry-propagator-aws-xray >= 1.0.1",
37+
"opentelemetry-distro >= 0.50b0",
38+
"opentelemetry-propagator-ot-trace >= 0.50b0",
39+
"opentelemetry-instrumentation >= 0.50b0",
40+
"opentelemetry-instrumentation-aws-lambda >= 0.50b0",
41+
"opentelemetry-instrumentation-aio-pika >= 0.50b0",
42+
"opentelemetry-instrumentation-aiohttp-client >= 0.50b0",
43+
"opentelemetry-instrumentation-aiopg >= 0.50b0",
44+
"opentelemetry-instrumentation-asgi >= 0.50b0",
45+
"opentelemetry-instrumentation-asyncpg >= 0.50b0",
46+
"opentelemetry-instrumentation-boto >= 0.50b0",
47+
"opentelemetry-instrumentation-boto3sqs >= 0.50b0",
48+
"opentelemetry-instrumentation-botocore >= 0.50b0",
49+
"opentelemetry-instrumentation-celery >= 0.50b0",
50+
"opentelemetry-instrumentation-confluent-kafka >= 0.50b0",
51+
"opentelemetry-instrumentation-dbapi >= 0.50b0",
52+
"opentelemetry-instrumentation-django >= 0.50b0",
53+
"opentelemetry-instrumentation-elasticsearch >= 0.50b0",
54+
"opentelemetry-instrumentation-falcon >= 0.50b0",
55+
"opentelemetry-instrumentation-fastapi >= 0.50b0",
56+
"opentelemetry-instrumentation-flask >= 0.50b0",
57+
"opentelemetry-instrumentation-grpc >= 0.50b0",
58+
"opentelemetry-instrumentation-httpx >= 0.50b0",
59+
"opentelemetry-instrumentation-jinja2 >= 0.50b0",
60+
"opentelemetry-instrumentation-kafka-python >= 0.50b0",
61+
"opentelemetry-instrumentation-logging >= 0.50b0",
62+
"opentelemetry-instrumentation-mysql >= 0.50b0",
63+
"opentelemetry-instrumentation-mysqlclient >= 0.50b0",
64+
"opentelemetry-instrumentation-pika >= 0.50b0",
65+
"opentelemetry-instrumentation-psycopg2 >= 0.50b0",
66+
"opentelemetry-instrumentation-pymemcache >= 0.50b0",
67+
"opentelemetry-instrumentation-pymongo >= 0.50b0",
68+
"opentelemetry-instrumentation-pymysql >= 0.50b0",
69+
"opentelemetry-instrumentation-pyramid >= 0.50b0",
70+
"opentelemetry-instrumentation-redis >= 0.50b0",
71+
"opentelemetry-instrumentation-remoulade >= 0.50b0",
72+
"opentelemetry-instrumentation-requests >= 0.50b0",
73+
"opentelemetry-instrumentation-sqlalchemy >= 0.50b0",
74+
"opentelemetry-instrumentation-sqlite3 >= 0.50b0",
75+
"opentelemetry-instrumentation-starlette >= 0.50b0",
76+
"opentelemetry-instrumentation-system-metrics >= 0.50b0",
77+
"opentelemetry-instrumentation-tornado >= 0.50b0",
78+
"opentelemetry-instrumentation-tortoiseorm >= 0.50b0",
79+
"opentelemetry-instrumentation-urllib >= 0.50b0",
80+
"opentelemetry-instrumentation-urllib3 >= 0.50b0",
81+
"opentelemetry-instrumentation-wsgi >= 0.50b0",
82+
"opentelemetry-instrumentation-cassandra >= 0.50b0",
8383
]
8484

8585
[project.optional-dependencies]

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

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

4+
import os
45
import sys
56
from logging import Logger, getLogger
67

78
import pkg_resources
89

910
_logger: Logger = getLogger(__name__)
1011

12+
AGENT_OBSERVABILITY_ENABLED = "AGENT_OBSERVABILITY_ENABLED"
1113

1214
def is_installed(req: str) -> bool:
1315
"""Is the given required package installed?"""
@@ -21,3 +23,6 @@ def is_installed(req: str) -> bool:
2123
_logger.debug("Skipping instrumentation patch: package %s, exception: %s", req, exc)
2224
return False
2325
return True
26+
27+
def is_agent_observability_enabled() -> bool:
28+
return os.environ.get(AGENT_OBSERVABILITY_ENABLED, "false").lower() == "true"

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing_extensions import override
1111

1212
from amazon.opentelemetry.distro._aws_attribute_keys import AWS_LOCAL_SERVICE
13+
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
1314
from amazon.opentelemetry.distro._aws_resource_attribute_configurator import get_service_attribute
1415
from amazon.opentelemetry.distro.always_record_sampler import AlwaysRecordSampler
1516
from amazon.opentelemetry.distro.attribute_propagating_span_processor_builder import (
@@ -27,7 +28,7 @@
2728
from amazon.opentelemetry.distro.sampler.aws_xray_remote_sampler import AwsXRayRemoteSampler
2829
from amazon.opentelemetry.distro.scope_based_exporter import ScopeBasedPeriodicExportingMetricReader
2930
from amazon.opentelemetry.distro.scope_based_filtering_view import ScopeBasedRetainingView
30-
from opentelemetry._logs import set_logger_provider
31+
from opentelemetry._logs import set_logger_provider, get_logger_provider
3132
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
3233
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as OTLPHttpOTLPMetricExporter
3334
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
@@ -91,6 +92,8 @@
9192
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
9293
OTEL_EXPORTER_OTLP_LOGS_HEADERS = "OTEL_EXPORTER_OTLP_LOGS_HEADERS"
9394

95+
AGENT_OBSERVABILITY_ENABLED = "AGENT_OBSERVABILITY_ENABLED"
96+
9497
AWS_TRACES_OTLP_ENDPOINT_PATTERN = r"https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$"
9598
AWS_LOGS_OTLP_ENDPOINT_PATTERN = r"https://logs\.([a-z0-9-]+)\.amazonaws\.com/v1/logs$"
9699

@@ -160,16 +163,17 @@ def _initialize_components():
160163
sampler_name = _get_sampler()
161164
sampler = _custom_import_sampler(sampler_name, resource)
162165

166+
logging_enabled = os.getenv(_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false")
167+
if logging_enabled.strip().lower() == "true":
168+
_init_logging(log_exporters, resource)
169+
163170
_init_tracing(
164171
exporters=trace_exporters,
165172
id_generator=id_generator,
166173
sampler=sampler,
167174
resource=resource,
168175
)
169176
_init_metrics(metric_exporters, resource)
170-
logging_enabled = os.getenv(_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false")
171-
if logging_enabled.strip().lower() == "true":
172-
_init_logging(log_exporters, resource)
173177

174178

175179
def _init_logging(
@@ -359,7 +363,15 @@ def _customize_span_exporter(span_exporter: SpanExporter, resource: Resource) ->
359363
_logger.info("Detected using AWS OTLP Traces Endpoint.")
360364

361365
if isinstance(span_exporter, OTLPSpanExporter):
362-
span_exporter = OTLPAwsSpanExporter(endpoint=traces_endpoint)
366+
if is_agent_observability_enabled():
367+
logs_endpoint = os.getenv(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)
368+
logs_exporter = OTLPAwsLogExporter(endpoint=logs_endpoint)
369+
span_exporter = OTLPAwsSpanExporter(
370+
endpoint=traces_endpoint,
371+
logger_provider=get_logger_provider()
372+
)
373+
else:
374+
span_exporter = OTLPAwsSpanExporter(endpoint=traces_endpoint)
363375

364376
else:
365377
_logger.warning(

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

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

4-
from typing import Dict, Optional
4+
from typing import Dict, Optional, Sequence
55

66
from amazon.opentelemetry.distro.exporter.otlp.aws.common.aws_auth_session import AwsAuthSession
7+
from amazon.opentelemetry.distro.llo_handler import LLOHandler
8+
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
9+
from opentelemetry.sdk._logs import LoggerProvider
710
from opentelemetry.exporter.otlp.proto.http import Compression
811
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
12+
from opentelemetry.sdk.trace import ReadableSpan
13+
from opentelemetry.sdk.trace.export import SpanExportResult
14+
15+
AGENT_OBSERVABILITY_ENABLED = "AGENT_OBSERVABILITY_ENABLED"
916

1017

1118
class OTLPAwsSpanExporter(OTLPSpanExporter):
@@ -18,9 +25,13 @@ def __init__(
1825
headers: Optional[Dict[str, str]] = None,
1926
timeout: Optional[int] = None,
2027
compression: Optional[Compression] = None,
28+
logger_provider: Optional[LoggerProvider] = None
2129
):
2230
self._aws_region = None
2331

32+
if logger_provider:
33+
self._llo_handler = LLOHandler(logger_provider)
34+
2435
if endpoint:
2536
self._aws_region = endpoint.split(".")[1]
2637

@@ -35,3 +46,10 @@ def __init__(
3546
compression,
3647
session=AwsAuthSession(aws_region=self._aws_region, service="xray"),
3748
)
49+
50+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
51+
if is_agent_observability_enabled():
52+
llo_processed_spans = self._llo_handler.process_spans(spans)
53+
return super().export(llo_processed_spans)
54+
55+
return super().export(spans)

0 commit comments

Comments
 (0)