diff --git a/CHANGELOG.md b/CHANGELOG.md index d946d825d1..e840f0c4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +### Fixed + +- `opentelemetry-instrumentation-openai-v2`: migrate off the deprecated events API to use the logs API + ([#3625](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3628)) + ## Version 1.35.0/0.56b0 (2025-07-11) ### Added diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/pyproject.toml b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/pyproject.toml index 5007bc6448..2ce22fe34e 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/pyproject.toml +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/pyproject.toml @@ -25,9 +25,9 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "opentelemetry-api ~= 1.30", - "opentelemetry-instrumentation ~= 0.51b0", - "opentelemetry-semantic-conventions ~= 0.51b0" + "opentelemetry-api >= 1.35.0", + "opentelemetry-instrumentation ~= 0.56b0", + "opentelemetry-semantic-conventions ~= 0.56b0" ] [project.optional-dependencies] diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py index ab4b6f9d7b..1775b704cf 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py @@ -44,7 +44,7 @@ from wrapt import wrap_function_wrapper -from opentelemetry._events import get_event_logger +from opentelemetry._logs import get_logger from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.openai_v2.package import _instruments from opentelemetry.instrumentation.openai_v2.utils import is_content_enabled @@ -73,12 +73,12 @@ def _instrument(self, **kwargs): tracer_provider, schema_url=Schemas.V1_28_0.value, ) - event_logger_provider = kwargs.get("event_logger_provider") - event_logger = get_event_logger( + logger_provider = kwargs.get("logger_provider") + logger = get_logger( __name__, "", schema_url=Schemas.V1_28_0.value, - event_logger_provider=event_logger_provider, + logger_provider=logger_provider, ) meter_provider = kwargs.get("meter_provider") self._meter = get_meter( @@ -94,7 +94,7 @@ def _instrument(self, **kwargs): module="openai.resources.chat.completions", name="Completions.create", wrapper=chat_completions_create( - tracer, event_logger, instruments, is_content_enabled() + tracer, logger, instruments, is_content_enabled() ), ) @@ -102,7 +102,7 @@ def _instrument(self, **kwargs): module="openai.resources.chat.completions", name="AsyncCompletions.create", wrapper=async_chat_completions_create( - tracer, event_logger, instruments, is_content_enabled() + tracer, logger, instruments, is_content_enabled() ), ) diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py index 072365abb7..1ee87b6029 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py @@ -18,7 +18,7 @@ from openai import Stream -from opentelemetry._events import Event, EventLogger +from opentelemetry._logs import Logger, LogRecord from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -40,7 +40,7 @@ def chat_completions_create( tracer: Tracer, - event_logger: EventLogger, + logger: Logger, instruments: Instruments, capture_content: bool, ): @@ -57,7 +57,7 @@ def traced_method(wrapped, instance, args, kwargs): end_on_exit=False, ) as span: for message in kwargs.get("messages", []): - event_logger.emit(message_to_event(message, capture_content)) + logger.emit(message_to_event(message, capture_content)) start = default_timer() result = None @@ -65,16 +65,14 @@ def traced_method(wrapped, instance, args, kwargs): try: result = wrapped(*args, **kwargs) if is_streaming(kwargs): - return StreamWrapper( - result, span, event_logger, capture_content - ) + return StreamWrapper(result, span, logger, capture_content) if span.is_recording(): _set_response_attributes( - span, result, event_logger, capture_content + span, result, logger, capture_content ) for choice in getattr(result, "choices", []): - event_logger.emit(choice_to_event(choice, capture_content)) + logger.emit(choice_to_event(choice, capture_content)) span.end() return result @@ -98,7 +96,7 @@ def traced_method(wrapped, instance, args, kwargs): def async_chat_completions_create( tracer: Tracer, - event_logger: EventLogger, + logger: Logger, instruments: Instruments, capture_content: bool, ): @@ -115,7 +113,7 @@ async def traced_method(wrapped, instance, args, kwargs): end_on_exit=False, ) as span: for message in kwargs.get("messages", []): - event_logger.emit(message_to_event(message, capture_content)) + logger.emit(message_to_event(message, capture_content)) start = default_timer() result = None @@ -123,16 +121,14 @@ async def traced_method(wrapped, instance, args, kwargs): try: result = await wrapped(*args, **kwargs) if is_streaming(kwargs): - return StreamWrapper( - result, span, event_logger, capture_content - ) + return StreamWrapper(result, span, logger, capture_content) if span.is_recording(): _set_response_attributes( - span, result, event_logger, capture_content + span, result, logger, capture_content ) for choice in getattr(result, "choices", []): - event_logger.emit(choice_to_event(choice, capture_content)) + logger.emit(choice_to_event(choice, capture_content)) span.end() return result @@ -221,7 +217,7 @@ def _record_metrics( def _set_response_attributes( - span, result, event_logger: EventLogger, capture_content: bool + span, result, logger: Logger, capture_content: bool ): set_span_attribute( span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, result.model @@ -311,7 +307,7 @@ def __init__( self, stream: Stream, span: Span, - event_logger: EventLogger, + logger: Logger, capture_content: bool, ): self.stream = stream @@ -320,7 +316,7 @@ def __init__( self._span_started = False self.capture_content = capture_content - self.event_logger = event_logger + self.logger = logger self.setup() def setup(self): @@ -399,9 +395,9 @@ def cleanup(self): # this span is not current, so we need to manually set the context on event span_ctx = self.span.get_span_context() - self.event_logger.emit( - Event( - name="gen_ai.choice", + self.logger.emit( + LogRecord( + event_name="gen_ai.choice", attributes=event_attributes, body=body, trace_id=span_ctx.trace_id, diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py index f8a837259e..7ff1cc509a 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py @@ -19,7 +19,7 @@ from httpx import URL from openai import NOT_GIVEN -from opentelemetry._events import Event +from opentelemetry._logs import LogRecord from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -123,8 +123,8 @@ def message_to_event(message, capture_content): if tool_call_id: body["id"] = tool_call_id - return Event( - name=f"gen_ai.{role}.message", + return LogRecord( + event_name=f"gen_ai.{role}.message", attributes=attributes, body=body if body else None, ) @@ -156,8 +156,8 @@ def choice_to_event(choice, capture_content): message["content"] = content body["message"] = message - return Event( - name="gen_ai.choice", + return LogRecord( + event_name="gen_ai.choice", attributes=attributes, body=body, ) diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/conftest.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/conftest.py index 87505046aa..83a4ba8c67 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/conftest.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/conftest.py @@ -11,7 +11,6 @@ from opentelemetry.instrumentation.openai_v2.utils import ( OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, ) -from opentelemetry.sdk._events import EventLoggerProvider from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk._logs.export import ( InMemoryLogExporter, @@ -56,13 +55,11 @@ def fixture_tracer_provider(span_exporter): return provider -@pytest.fixture(scope="function", name="event_logger_provider") -def fixture_event_logger_provider(log_exporter): +@pytest.fixture(scope="function", name="logger_provider") +def fixture_logger_provider(log_exporter): provider = LoggerProvider() provider.add_log_record_processor(SimpleLogRecordProcessor(log_exporter)) - event_logger_provider = EventLoggerProvider(provider) - - return event_logger_provider + return provider @pytest.fixture(scope="function", name="meter_provider") @@ -105,9 +102,7 @@ def vcr_config(): @pytest.fixture(scope="function") -def instrument_no_content( - tracer_provider, event_logger_provider, meter_provider -): +def instrument_no_content(tracer_provider, logger_provider, meter_provider): os.environ.update( {OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: "False"} ) @@ -115,7 +110,7 @@ def instrument_no_content( instrumentor = OpenAIInstrumentor() instrumentor.instrument( tracer_provider=tracer_provider, - event_logger_provider=event_logger_provider, + logger_provider=logger_provider, meter_provider=meter_provider, ) @@ -125,16 +120,14 @@ def instrument_no_content( @pytest.fixture(scope="function") -def instrument_with_content( - tracer_provider, event_logger_provider, meter_provider -): +def instrument_with_content(tracer_provider, logger_provider, meter_provider): os.environ.update( {OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: "True"} ) instrumentor = OpenAIInstrumentor() instrumentor.instrument( tracer_provider=tracer_provider, - event_logger_provider=event_logger_provider, + logger_provider=logger_provider, meter_provider=meter_provider, ) @@ -145,7 +138,7 @@ def instrument_with_content( @pytest.fixture(scope="function") def instrument_with_content_unsampled( - span_exporter, event_logger_provider, meter_provider + span_exporter, logger_provider, meter_provider ): os.environ.update( {OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: "True"} @@ -157,7 +150,7 @@ def instrument_with_content_unsampled( instrumentor = OpenAIInstrumentor() instrumentor.instrument( tracer_provider=tracer_provider, - event_logger_provider=event_logger_provider, + logger_provider=logger_provider, meter_provider=meter_provider, ) diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/requirements.oldest.txt b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/requirements.oldest.txt index 6b46a6eaa4..125dea9f70 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/requirements.oldest.txt +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/requirements.oldest.txt @@ -26,8 +26,8 @@ pytest-vcr==1.0.2 pytest-asyncio==0.21.0 wrapt==1.16.0 opentelemetry-exporter-otlp-proto-http~=1.30 -opentelemetry-api==1.30 # when updating, also update in pyproject.toml -opentelemetry-sdk==1.30 # when updating, also update in pyproject.toml -opentelemetry-semantic-conventions==0.51b0 # when updating, also update in pyproject.toml +opentelemetry-api==1.35 # when updating, also update in pyproject.toml +opentelemetry-sdk==1.35 # when updating, also update in pyproject.toml +opentelemetry-semantic-conventions==0.56b0 # when updating, also update in pyproject.toml -e instrumentation-genai/opentelemetry-instrumentation-openai-v2 diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_chat_completions.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_chat_completions.py index 468caa232c..6a3d0b28d0 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_chat_completions.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_async_chat_completions.py @@ -23,9 +23,6 @@ from opentelemetry.semconv._incubating.attributes import ( error_attributes as ErrorAttributes, ) -from opentelemetry.semconv._incubating.attributes import ( - event_attributes as EventAttributes, -) from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -797,7 +794,7 @@ async def async_chat_completion_multiple_tools_streaming( def assert_message_in_logs(log, event_name, expected_content, parent_span): - assert log.log_record.attributes[EventAttributes.EVENT_NAME] == event_name + assert log.log_record.event_name == event_name assert ( log.log_record.attributes[GenAIAttributes.GEN_AI_SYSTEM] == GenAIAttributes.GenAiSystemValues.OPENAI.value diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_chat_completions.py b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_chat_completions.py index 914d5b5b98..f849926e82 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_chat_completions.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_chat_completions.py @@ -23,9 +23,6 @@ from opentelemetry.semconv._incubating.attributes import ( error_attributes as ErrorAttributes, ) -from opentelemetry.semconv._incubating.attributes import ( - event_attributes as EventAttributes, -) from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -812,7 +809,7 @@ def chat_completion_multiple_tools_streaming( def assert_message_in_logs(log, event_name, expected_content, parent_span): - assert log.log_record.attributes[EventAttributes.EVENT_NAME] == event_name + assert log.log_record.event_name == event_name assert ( log.log_record.attributes[GenAIAttributes.GEN_AI_SYSTEM] == GenAIAttributes.GenAiSystemValues.OPENAI.value