Skip to content

Commit 9f393c7

Browse files
DylanRussellxrmxemdneto
authored
Update the botocore instrumentation to emit events via the logs API instead of the deprecated events API (open-telemetry#3624)
* Initial commit to replace deprecated events API/ SDK with logs. * Use LogRecord in API instead of SDK * Use LogRecord from APi instead of Events * Add changelog, fix lint, fix ruff * Respond to review comments * Require v1.35 of otel python * Specify the .0 * Fix changelog * Update CHANGELOG with recent fixes * respond to comments * Update bedrock.py * Update instrumentation/opentelemetry-instrumentation-botocore/pyproject.toml --------- Co-authored-by: Riccardo Magliocchetti <[email protected]> Co-authored-by: Emídio Neto <[email protected]>
1 parent a4adc93 commit 9f393c7

File tree

9 files changed

+67
-75
lines changed

9 files changed

+67
-75
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
## Unreleased
1313

1414
### Fixed
15+
16+
- `opentelemetry-instrumentation-botocore`: migrate off the deprecated events API to use the logs API
17+
([#3624](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3624))
1518
- `opentelemetry-instrumentation-dbapi`: fix crash retrieving libpq version when enabling commenter with psycopg
1619
([#3796](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3796))
1720

1821
### Added
22+
1923
- `opentelemetry-instrumentation`: botocore: Add support for AWS Secrets Manager semantic convention attribute
2024
([#3765](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3765))
2125
- Add `rstcheck` to pre-commit to stop introducing invalid RST

instrumentation/opentelemetry-instrumentation-botocore/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ classifiers = [
2525
"Programming Language :: Python :: 3.13",
2626
]
2727
dependencies = [
28-
"opentelemetry-api ~= 1.30",
28+
"opentelemetry-api ~= 1.37",
2929
"opentelemetry-instrumentation == 0.59b0.dev",
3030
"opentelemetry-semantic-conventions == 0.59b0.dev",
3131
"opentelemetry-propagator-aws-xray ~= 1.0",

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def response_hook(span, service_name, operation_name, result):
8888
from botocore.exceptions import ClientError
8989
from wrapt import wrap_function_wrapper
9090

91-
from opentelemetry._events import get_event_logger
91+
from opentelemetry._logs import get_logger
9292
from opentelemetry.instrumentation.botocore.extensions import (
9393
_find_extension,
9494
_has_extension,
@@ -139,8 +139,8 @@ def _instrument(self, **kwargs):
139139

140140
# tracers are lazy initialized per-extension in _get_tracer
141141
self._tracers = {}
142-
# event_loggers are lazy initialized per-extension in _get_event_logger
143-
self._event_loggers = {}
142+
# loggers are lazy initialized per-extension in _get_logger
143+
self._loggers = {}
144144
# meters are lazy initialized per-extension in _get_meter
145145
self._meters = {}
146146
# metrics are lazy initialized per-extension in _get_metrics
@@ -154,7 +154,7 @@ def _instrument(self, **kwargs):
154154
self.propagator = propagator
155155

156156
self.tracer_provider = kwargs.get("tracer_provider")
157-
self.event_logger_provider = kwargs.get("event_logger_provider")
157+
self.logger_provider = kwargs.get("logger_provider")
158158
self.meter_provider = kwargs.get("meter_provider")
159159

160160
wrap_function_wrapper(
@@ -195,23 +195,23 @@ def _get_tracer(self, extension: _AwsSdkExtension):
195195
)
196196
return self._tracers[instrumentation_name]
197197

198-
def _get_event_logger(self, extension: _AwsSdkExtension):
199-
"""This is a multiplexer in order to have an event logger per extension"""
198+
def _get_logger(self, extension: _AwsSdkExtension):
199+
"""This is a multiplexer in order to have a logger per extension"""
200200

201201
instrumentation_name = self._get_instrumentation_name(extension)
202-
event_logger = self._event_loggers.get(instrumentation_name)
203-
if event_logger:
204-
return event_logger
202+
instrumentation_logger = self._loggers.get(instrumentation_name)
203+
if instrumentation_logger:
204+
return instrumentation_logger
205205

206206
schema_version = extension.event_logger_schema_version()
207-
self._event_loggers[instrumentation_name] = get_event_logger(
207+
self._loggers[instrumentation_name] = get_logger(
208208
instrumentation_name,
209209
"",
210210
schema_url=f"https://opentelemetry.io/schemas/{schema_version}",
211-
event_logger_provider=self.event_logger_provider,
211+
logger_provider=self.logger_provider,
212212
)
213213

214-
return self._event_loggers[instrumentation_name]
214+
return self._loggers[instrumentation_name]
215215

216216
def _get_meter(self, extension: _AwsSdkExtension):
217217
"""This is a multiplexer in order to have a meter per extension"""
@@ -287,11 +287,10 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
287287
end_span_on_exit = extension.should_end_span_on_exit()
288288

289289
tracer = self._get_tracer(extension)
290-
event_logger = self._get_event_logger(extension)
291290
meter = self._get_meter(extension)
292291
metrics = self._get_metrics(extension, meter)
293292
instrumentor_ctx = _BotocoreInstrumentorContext(
294-
event_logger=event_logger,
293+
logger=self._get_logger(extension),
295294
metrics=metrics,
296295
)
297296
with tracer.start_as_current_span(

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/bedrock.py

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from botocore.eventstream import EventStream
2929
from botocore.response import StreamingBody
3030

31+
from opentelemetry.context import get_current
3132
from opentelemetry.instrumentation.botocore.extensions.bedrock_utils import (
3233
ConverseStreamWrapper,
3334
InvokeModelWithResponseStreamWrapper,
@@ -67,6 +68,7 @@
6768
GEN_AI_CLIENT_OPERATION_DURATION,
6869
GEN_AI_CLIENT_TOKEN_USAGE,
6970
)
71+
from opentelemetry.trace.propagation import set_span_in_context
7072
from opentelemetry.trace.span import Span
7173
from opentelemetry.trace.status import Status, StatusCode
7274

@@ -457,9 +459,9 @@ def before_service_call(
457459

458460
messages = self._get_request_messages()
459461
for message in messages:
460-
event_logger = instrumentor_context.event_logger
462+
logger = instrumentor_context.logger
461463
for event in message_to_event(message, capture_content):
462-
event_logger.emit(event)
464+
logger.emit(event)
463465

464466
if span.is_recording():
465467
operation_name = span.attributes.get(GEN_AI_OPERATION_NAME, "")
@@ -501,18 +503,12 @@ def _converse_on_success(
501503

502504
# In case of an early stream closure, the result may not contain outputs
503505
if self._stream_has_output_content(result):
504-
event_logger = instrumentor_context.event_logger
506+
logger = instrumentor_context.logger
505507
choice = _Choice.from_converse(result, capture_content)
506508
# this path is used by streaming apis, in that case we are already out of the span
507509
# context so need to add the span context manually
508-
span_ctx = span.get_span_context()
509-
event_logger.emit(
510-
choice.to_choice_event(
511-
trace_id=span_ctx.trace_id,
512-
span_id=span_ctx.span_id,
513-
trace_flags=span_ctx.trace_flags,
514-
)
515-
)
510+
context = set_span_in_context(span, get_current())
511+
logger.emit(choice.to_choice_event(context=context))
516512

517513
metrics = instrumentor_context.metrics
518514
metrics_attributes = self._extract_metrics_attributes()
@@ -729,11 +725,11 @@ def _handle_amazon_titan_response(
729725
[result["completionReason"]],
730726
)
731727

732-
event_logger = instrumentor_context.event_logger
728+
logger = instrumentor_context.logger
733729
choice = _Choice.from_invoke_amazon_titan(
734730
response_body, capture_content
735731
)
736-
event_logger.emit(choice.to_choice_event())
732+
logger.emit(choice.to_choice_event())
737733

738734
metrics = instrumentor_context.metrics
739735
metrics_attributes = self._extract_metrics_attributes()
@@ -791,9 +787,9 @@ def _handle_amazon_nova_response(
791787

792788
# In case of an early stream closure, the result may not contain outputs
793789
if self._stream_has_output_content(response_body):
794-
event_logger = instrumentor_context.event_logger
790+
logger = instrumentor_context.logger
795791
choice = _Choice.from_converse(response_body, capture_content)
796-
event_logger.emit(choice.to_choice_event())
792+
logger.emit(choice.to_choice_event())
797793

798794
metrics = instrumentor_context.metrics
799795
metrics_attributes = self._extract_metrics_attributes()
@@ -848,11 +844,11 @@ def _handle_anthropic_claude_response(
848844
GEN_AI_RESPONSE_FINISH_REASONS, [response_body["stop_reason"]]
849845
)
850846

851-
event_logger = instrumentor_context.event_logger
847+
logger = instrumentor_context.logger
852848
choice = _Choice.from_invoke_anthropic_claude(
853849
response_body, capture_content
854850
)
855-
event_logger.emit(choice.to_choice_event())
851+
logger.emit(choice.to_choice_event())
856852

857853
metrics = instrumentor_context.metrics
858854
metrics_attributes = self._extract_metrics_attributes()
@@ -903,11 +899,11 @@ def _handle_cohere_command_r_response(
903899
[response_body["finish_reason"]],
904900
)
905901

906-
event_logger = instrumentor_context.event_logger
902+
logger = instrumentor_context.logger
907903
choice = _Choice.from_invoke_cohere_command_r(
908904
response_body, capture_content
909905
)
910-
event_logger.emit(choice.to_choice_event())
906+
logger.emit(choice.to_choice_event())
911907

912908
def _handle_cohere_command_response(
913909
self,
@@ -929,11 +925,11 @@ def _handle_cohere_command_response(
929925
[generations["finish_reason"]],
930926
)
931927

932-
event_logger = instrumentor_context.event_logger
928+
logger = instrumentor_context.logger
933929
choice = _Choice.from_invoke_cohere_command(
934930
response_body, capture_content
935931
)
936-
event_logger.emit(choice.to_choice_event())
932+
logger.emit(choice.to_choice_event())
937933

938934
def _handle_meta_llama_response(
939935
self,
@@ -956,9 +952,9 @@ def _handle_meta_llama_response(
956952
GEN_AI_RESPONSE_FINISH_REASONS, [response_body["stop_reason"]]
957953
)
958954

959-
event_logger = instrumentor_context.event_logger
955+
logger = instrumentor_context.logger
960956
choice = _Choice.from_invoke_meta_llama(response_body, capture_content)
961-
event_logger.emit(choice.to_choice_event())
957+
logger.emit(choice.to_choice_event())
962958

963959
def _handle_mistral_ai_response(
964960
self,
@@ -979,11 +975,11 @@ def _handle_mistral_ai_response(
979975
GEN_AI_RESPONSE_FINISH_REASONS, [outputs["stop_reason"]]
980976
)
981977

982-
event_logger = instrumentor_context.event_logger
978+
logger = instrumentor_context.logger
983979
choice = _Choice.from_invoke_mistral_mistral(
984980
response_body, capture_content
985981
)
986-
event_logger.emit(choice.to_choice_event())
982+
logger.emit(choice.to_choice_event())
987983

988984
def on_error(
989985
self,

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
from botocore.eventstream import EventStream, EventStreamError
2323
from wrapt import ObjectProxy
2424

25-
from opentelemetry._events import Event
25+
from opentelemetry._logs import LogRecord
26+
from opentelemetry.context import get_current
2627
from opentelemetry.instrumentation.botocore.environment_variables import (
2728
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
2829
)
@@ -492,7 +493,7 @@ def extract_tool_results(
492493

493494
def message_to_event(
494495
message: dict[str, Any], capture_content: bool
495-
) -> Iterator[Event]:
496+
) -> Iterator[LogRecord]:
496497
attributes = {GEN_AI_SYSTEM: GenAiSystemValues.AWS_BEDROCK.value}
497498
role = message.get("role")
498499
content = message.get("content")
@@ -507,16 +508,18 @@ def message_to_event(
507508
elif role == "user":
508509
# in case of tool calls we send one tool event for tool call and one for the user event
509510
for tool_body in extract_tool_results(message, capture_content):
510-
yield Event(
511-
name="gen_ai.tool.message",
511+
yield LogRecord(
512+
event_name="gen_ai.tool.message",
512513
attributes=attributes,
513514
body=tool_body,
515+
context=get_current(),
514516
)
515517

516-
yield Event(
517-
name=f"gen_ai.{role}.message",
518+
yield LogRecord(
519+
event_name=f"gen_ai.{role}.message",
518520
attributes=attributes,
519521
body=body if body else None,
522+
context=get_current(),
520523
)
521524

522525

@@ -617,10 +620,10 @@ def _to_body_dict(self) -> dict[str, Any]:
617620
"message": self.message,
618621
}
619622

620-
def to_choice_event(self, **event_kwargs) -> Event:
623+
def to_choice_event(self, **event_kwargs) -> LogRecord:
621624
attributes = {GEN_AI_SYSTEM: GenAiSystemValues.AWS_BEDROCK.value}
622-
return Event(
623-
name="gen_ai.choice",
625+
return LogRecord(
626+
event_name="gen_ai.choice",
624627
attributes=attributes,
625628
body=self._to_body_dict(),
626629
**event_kwargs,

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/types.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import logging
1818
from typing import Any, Dict, Optional, Tuple
1919

20-
from opentelemetry._events import EventLogger
20+
from opentelemetry._logs import Logger
2121
from opentelemetry.metrics import Instrument, Meter
2222
from opentelemetry.trace import SpanKind
2323
from opentelemetry.trace.span import Span
@@ -96,10 +96,10 @@ def _get_attr(obj, name: str, default=None):
9696
class _BotocoreInstrumentorContext:
9797
def __init__(
9898
self,
99-
event_logger: EventLogger,
99+
logger: Logger,
100100
metrics: Dict[str, Instrument] | None = None,
101101
):
102-
self.event_logger = event_logger
102+
self.logger = logger
103103
self.metrics = metrics or {}
104104

105105

instrumentation/opentelemetry-instrumentation-botocore/tests/bedrock_utils.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@
2626
)
2727
from opentelemetry.sdk.metrics._internal.point import ResourceMetrics
2828
from opentelemetry.sdk.trace import ReadableSpan
29-
from opentelemetry.semconv._incubating.attributes import (
30-
event_attributes as EventAttributes,
31-
)
3229
from opentelemetry.semconv._incubating.attributes import (
3330
gen_ai_attributes as GenAIAttributes,
3431
)
@@ -282,17 +279,17 @@ def remove_none_values(body):
282279

283280
def assert_log_parent(log, span):
284281
if span:
285-
assert log.log_record.trace_id == span.get_span_context().trace_id
282+
assert (
283+
log.log_record.trace_id == span.get_span_context().trace_id
284+
), f"{span.get_span_context().trace_id} does not equal {log.log_record.trace_id}"
286285
assert log.log_record.span_id == span.get_span_context().span_id
287286
assert (
288287
log.log_record.trace_flags == span.get_span_context().trace_flags
289288
)
290289

291290

292291
def assert_message_in_logs(log, event_name, expected_content, parent_span):
293-
assert (
294-
log.log_record.attributes[EventAttributes.EVENT_NAME] == event_name
295-
), log.log_record.attributes[EventAttributes.EVENT_NAME]
292+
assert log.log_record.event_name == event_name, log.log_record.event_name
296293
assert (
297294
log.log_record.attributes[GenAIAttributes.GEN_AI_SYSTEM]
298295
== GenAIAttributes.GenAiSystemValues.AWS_BEDROCK.value

0 commit comments

Comments
 (0)