Skip to content

Commit 09881f8

Browse files
committed
Merge branch 'main' of https://github.com/open-telemetry/opentelemetry-python-contrib into fastapi-metrics
2 parents 14d8181 + 262a097 commit 09881f8

File tree

55 files changed

+2686
-1067
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2686
-1067
lines changed

CHANGELOG.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
([#3819](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3819))
3030
- `opentelemetry-instrumentation-system-metrics`: Add support for the `OTEL_PYTHON_SYSTEM_METRICS_EXCLUDED_METRICS` environment variable
3131
([#3959](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3959))
32-
- `opentelemetry-instrumentation-httpx`: add ability to capture custom headers
32+
- `opentelemetry-instrumentation-httpx`: add ability to capture custom headers
3333
([#4047](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4047))
3434
- `opentelemetry-instrumentation-urllib3`: add ability to capture custom headers
3535
([#4050](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4050))
3636
- `opentelemetry-instrumentation-urllib`: add ability to capture custom headers
3737
([#4051](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4051))
38+
- `opentelemetry-instrumentation-confluent-kafka`: Increase confluent-kafka upper bound to support newer versions (2.13.0)
39+
([#4099](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4099))
3840
- `opentelemetry-instrumentation-aiohttp-server` Implement new semantic convention opt-in migration
3941
([#3980](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3980))
42+
- `opentelemetry-instrumentation-falcon`: pass request attributes at span creation
43+
([#4119](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4119))
4044
- `opentelemetry-instrumentation`: add database stability attribute setters in `_semconv` utilities
4145
([#4108](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4108))
46+
- `opentelemetry-instrumentation-aiohttp-server`: pass request attributes at span creation
47+
([#4118](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4118))
48+
- `opentelemetry-instrumentation-tornado`: Implement new semantic convention opt-in migration
49+
([#3993](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3993))
50+
- `opentelemetry-instrumentation-tornado`: pass request attributes at span creation
51+
([#4140](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4140))
52+
- `opentelemetry-instrumentation-pyramid` Implement new semantic convention opt-in migration
53+
([#3982](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3982))
54+
- `opentelemetry-instrumentation-tortoiseorm` Add unit tests for Tortoise ORM instrumentation
55+
([#4141](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4141))
56+
- `opentelemetry-instrumentation-pyramid`: pass request attributes at span creation
57+
([#4139](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4139))
4258

4359
### Fixed
4460

@@ -82,9 +98,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8298
([#4081](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4081))
8399
- `opentelemetry-instrumentation-system-metrics`: Use proper numeric `cpython.gc.generation` attribute in CPython metrics, out of spec `generation` attribute is deprecated and will be removed in the future
84100
([#4092](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4092))
101+
- `opentelemetry-instrumentation-dbapi`: Fix sqlcomment calculation of mysql_client_version field if connection reassignment, with "unknown" fallback
102+
([#3729](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3729))
103+
- `opentelemetry-instrumentation-confluent-kafka`: Fix incorrect number of argument to `_inner_wrap_close`
104+
([#3922](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3922))
105+
- `opentelemetry-instrumentation-urllib3`: fix multiple arguments error
106+
([#4144](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4144))
85107

86108
### Breaking changes
87109

110+
- `opentelemetry-instrumentation-logging`: Inject span context attributes into logging LogRecord only if configured to do so
111+
([#4112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4112))
88112
- `opentelemetry-instrumentation-django`: Drop support for Django < 2.0
89113
([#3848](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4083))
90114

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ Some packages may require additional system-wide dependencies to be installed. F
122122

123123
For `docs` building, you may need to install `mysql-client` and other required dependencies as necessary. Ensure the Python version used in your local setup matches the version used in the [CI](./.github/workflows/) to maintain compatibility when building the documentation.
124124

125+
If you are using `tox-uv` for tests and have issues with resolving OpenTelemetry dependencies try:
126+
127+
```sh
128+
uv sync --refresh
129+
```
130+
125131
### Benchmarks
126132

127133
Some packages have benchmark tests. To run them, run `tox -f benchmark`. Benchmark tests use `pytest-benchmark` and they output a table with results to the console.

docs-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ botocore~=1.0
2424
boto3~=1.0
2525
cassandra-driver~=3.25
2626
celery>=4.0
27-
confluent-kafka>= 1.8.2,<= 2.11.0
27+
confluent-kafka>= 1.8.2,<= 2.13.0
2828
elasticsearch>=6.0,<9.0
2929
flask~=2.0
3030
falcon~=2.0

instrumentation-genai/opentelemetry-instrumentation-google-genai/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## Version 0.6b0 (2026-01-27)
11+
1012
- Enable the addition of custom attributes to the `generate_content {model.name}` span via the Context API. ([#3961](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3961)).
13+
- Enable the addition of custom attributes to `gen_ai.client.inference.operation.details` log events ([#4103](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4103)).
1114

1215
## Version 0.5b0 (2025-12-11)
1316

instrumentation-genai/opentelemetry-instrumentation-google-genai/src/opentelemetry/instrumentation/google_genai/generate_content.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
AsyncIterator,
2626
Awaitable,
2727
Iterator,
28-
Mapping,
2928
Optional,
3029
Union,
3130
)
@@ -189,7 +188,7 @@ def _to_dict(value: object):
189188
def _create_request_attributes(
190189
config: Optional[GenerateContentConfigOrDict],
191190
allow_list: AllowList,
192-
) -> dict[str, Any]:
191+
) -> dict[str, AttributeValue]:
193192
if not config:
194193
return {}
195194
config = _to_dict(config)
@@ -291,8 +290,8 @@ def _create_completion_details_attributes(
291290
output_messages: list[OutputMessage],
292291
system_instructions: list[MessagePart],
293292
as_str: bool = False,
294-
) -> dict[str, Any]:
295-
attributes: dict[str, Any] = {
293+
) -> dict[str, AttributeValue]:
294+
attributes: dict[str, AttributeValue] = {
296295
gen_ai_attributes.GEN_AI_INPUT_MESSAGES: [
297296
dataclasses.asdict(input_message)
298297
for input_message in input_messages
@@ -310,10 +309,11 @@ def _create_completion_details_attributes(
310309
return attributes
311310

312311

313-
def _get_extra_generate_content_attributes() -> Optional[
314-
Mapping[str, AttributeValue]
315-
]:
316-
return context_api.get_value(GENERATE_CONTENT_EXTRA_ATTRIBUTES_CONTEXT_KEY)
312+
def _get_extra_generate_content_attributes() -> dict[str, AttributeValue]:
313+
attrs = context_api.get_value(
314+
GENERATE_CONTENT_EXTRA_ATTRIBUTES_CONTEXT_KEY
315+
)
316+
return dict(attrs or {})
317317

318318

319319
class _GenerateContentInstrumentationHelper:
@@ -371,7 +371,7 @@ def start_span_as_current_span(
371371
end_on_exit=end_on_exit,
372372
)
373373

374-
def create_final_attributes(self) -> dict[str, Any]:
374+
def create_final_attributes(self) -> dict[str, AttributeValue]:
375375
final_attributes = {
376376
gen_ai_attributes.GEN_AI_USAGE_INPUT_TOKENS: self._input_tokens,
377377
gen_ai_attributes.GEN_AI_USAGE_OUTPUT_TOKENS: self._output_tokens,
@@ -463,8 +463,9 @@ def _maybe_update_error_type(self, response: GenerateContentResponse):
463463

464464
def _maybe_log_completion_details(
465465
self,
466-
request_attributes: dict[str, Any],
467-
final_attributes: dict[str, Any],
466+
extra_attributes: dict[str, AttributeValue],
467+
request_attributes: dict[str, AttributeValue],
468+
final_attributes: dict[str, AttributeValue],
468469
request: Union[ContentListUnion, ContentListUnionDict],
469470
candidates: list[Candidate],
470471
config: Optional[GenerateContentConfigOrDict] = None,
@@ -487,7 +488,9 @@ def _maybe_log_completion_details(
487488
span = trace.get_current_span()
488489
event = LogRecord(
489490
event_name="gen_ai.client.inference.operation.details",
490-
attributes=request_attributes | final_attributes,
491+
attributes=extra_attributes
492+
| request_attributes
493+
| final_attributes,
491494
)
492495
self.completion_hook.on_completion(
493496
inputs=input_messages,
@@ -742,9 +745,8 @@ def instrumented_generate_content(
742745
with helper.start_span_as_current_span(
743746
model, "google.genai.Models.generate_content"
744747
) as span:
745-
if extra_attributes := _get_extra_generate_content_attributes():
746-
span.set_attributes(extra_attributes)
747-
span.set_attributes(request_attributes)
748+
extra_attributes = _get_extra_generate_content_attributes()
749+
span.set_attributes(extra_attributes | request_attributes)
748750
if helper.sem_conv_opt_in_mode == _StabilityMode.DEFAULT:
749751
helper.process_request(contents, config, span)
750752
try:
@@ -773,6 +775,7 @@ def instrumented_generate_content(
773775
final_attributes = helper.create_final_attributes()
774776
span.set_attributes(final_attributes)
775777
helper._maybe_log_completion_details(
778+
extra_attributes,
776779
request_attributes,
777780
final_attributes,
778781
contents,
@@ -817,9 +820,8 @@ def instrumented_generate_content_stream(
817820
with helper.start_span_as_current_span(
818821
model, "google.genai.Models.generate_content_stream"
819822
) as span:
820-
if extra_attributes := _get_extra_generate_content_attributes():
821-
span.set_attributes(extra_attributes)
822-
span.set_attributes(request_attributes)
823+
extra_attributes = _get_extra_generate_content_attributes()
824+
span.set_attributes(extra_attributes | request_attributes)
823825
if helper.sem_conv_opt_in_mode == _StabilityMode.DEFAULT:
824826
helper.process_request(contents, config, span)
825827
try:
@@ -848,6 +850,7 @@ def instrumented_generate_content_stream(
848850
final_attributes = helper.create_final_attributes()
849851
span.set_attributes(final_attributes)
850852
helper._maybe_log_completion_details(
853+
extra_attributes,
851854
request_attributes,
852855
final_attributes,
853856
contents,
@@ -892,9 +895,8 @@ async def instrumented_generate_content(
892895
with helper.start_span_as_current_span(
893896
model, "google.genai.AsyncModels.generate_content"
894897
) as span:
895-
if extra_attributes := _get_extra_generate_content_attributes():
896-
span.set_attributes(extra_attributes)
897-
span.set_attributes(request_attributes)
898+
extra_attributes = _get_extra_generate_content_attributes()
899+
span.set_attributes(extra_attributes | request_attributes)
898900
if helper.sem_conv_opt_in_mode == _StabilityMode.DEFAULT:
899901
helper.process_request(contents, config, span)
900902
try:
@@ -922,6 +924,7 @@ async def instrumented_generate_content(
922924
final_attributes = helper.create_final_attributes()
923925
span.set_attributes(final_attributes)
924926
helper._maybe_log_completion_details(
927+
extra_attributes,
925928
request_attributes,
926929
final_attributes,
927930
contents,
@@ -968,9 +971,8 @@ async def instrumented_generate_content_stream(
968971
"google.genai.AsyncModels.generate_content_stream",
969972
end_on_exit=False,
970973
) as span:
971-
if extra_attributes := _get_extra_generate_content_attributes():
972-
span.set_attributes(extra_attributes)
973-
span.set_attributes(request_attributes)
974+
extra_attributes = _get_extra_generate_content_attributes()
975+
span.set_attributes(extra_attributes | request_attributes)
974976
if (
975977
not helper.sem_conv_opt_in_mode
976978
== _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
@@ -990,6 +992,7 @@ async def instrumented_generate_content_stream(
990992
final_attributes = helper.create_final_attributes()
991993
span.set_attributes(final_attributes)
992994
helper._maybe_log_completion_details(
995+
extra_attributes,
993996
request_attributes,
994997
final_attributes,
995998
contents,
@@ -1023,6 +1026,7 @@ async def _response_async_generator_wrapper():
10231026
final_attributes = helper.create_final_attributes()
10241027
span.set_attributes(final_attributes)
10251028
helper._maybe_log_completion_details(
1029+
extra_attributes,
10261030
request_attributes,
10271031
final_attributes,
10281032
contents,

instrumentation-genai/opentelemetry-instrumentation-google-genai/src/opentelemetry/instrumentation/google_genai/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
# This version should stay below "1.0" until the fundamentals
1818
# in "TODOS.md" have been addressed. Please revisit the TODOs
1919
# listed there before bumping to a stable version.
20-
__version__ = "0.6b0.dev"
20+
__version__ = "0.7b0.dev"

instrumentation-genai/opentelemetry-instrumentation-google-genai/tests/generate_content/nonstreaming_base.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,46 @@ def test_new_semconv_record_completion_in_span(self):
520520

521521
self.tearDown()
522522

523+
def test_new_semconv_log_has_extra_genai_attributes(self):
524+
patched_environ = patch.dict(
525+
"os.environ",
526+
{
527+
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "EVENT_ONLY",
528+
"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental",
529+
},
530+
)
531+
patched_otel_mapping = patch.dict(
532+
_OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING,
533+
{
534+
_OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
535+
},
536+
)
537+
with patched_environ, patched_otel_mapping:
538+
self.configure_valid_response(text="Yep, it works!")
539+
tok = context_api.attach(
540+
context_api.set_value(
541+
GENERATE_CONTENT_EXTRA_ATTRIBUTES_CONTEXT_KEY,
542+
{"extra_attribute_key": "extra_attribute_value"},
543+
)
544+
)
545+
try:
546+
self.generate_content(
547+
model="gemini-2.0-flash",
548+
contents="Does this work?",
549+
)
550+
self.otel.assert_has_event_named(
551+
"gen_ai.client.inference.operation.details"
552+
)
553+
event = self.otel.get_event_named(
554+
"gen_ai.client.inference.operation.details"
555+
)
556+
assert (
557+
event.attributes["extra_attribute_key"]
558+
== "extra_attribute_value"
559+
)
560+
finally:
561+
context_api.detach(tok)
562+
523563
def test_records_metrics_data(self):
524564
self.configure_valid_response()
525565
self.generate_content(model="gemini-2.0-flash", contents="Some input")

instrumentation-genai/opentelemetry-instrumentation-google-genai/tests/generate_content/streaming_base.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@
1313
# limitations under the License.
1414

1515
import unittest
16+
from unittest.mock import patch
1617

1718
from opentelemetry import context as context_api
19+
from opentelemetry.instrumentation._semconv import (
20+
_OpenTelemetrySemanticConventionStability,
21+
_OpenTelemetryStabilitySignalType,
22+
_StabilityMode,
23+
)
1824
from opentelemetry.instrumentation.google_genai import (
1925
GENERATE_CONTENT_EXTRA_ATTRIBUTES_CONTEXT_KEY,
2026
)
@@ -99,3 +105,43 @@ def test_includes_token_counts_in_span_aggregated_from_responses(self):
99105
span = self.otel.get_span_named("generate_content gemini-2.0-flash")
100106
self.assertEqual(span.attributes["gen_ai.usage.input_tokens"], 9)
101107
self.assertEqual(span.attributes["gen_ai.usage.output_tokens"], 12)
108+
109+
def test_new_semconv_log_has_extra_genai_attributes(self):
110+
patched_environ = patch.dict(
111+
"os.environ",
112+
{
113+
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "EVENT_ONLY",
114+
"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental",
115+
},
116+
)
117+
patched_otel_mapping = patch.dict(
118+
_OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING,
119+
{
120+
_OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
121+
},
122+
)
123+
with patched_environ, patched_otel_mapping:
124+
self.configure_valid_response(text="Yep, it works!")
125+
tok = context_api.attach(
126+
context_api.set_value(
127+
GENERATE_CONTENT_EXTRA_ATTRIBUTES_CONTEXT_KEY,
128+
{"extra_attribute_key": "extra_attribute_value"},
129+
)
130+
)
131+
try:
132+
self.generate_content(
133+
model="gemini-2.0-flash",
134+
contents="Does this work?",
135+
)
136+
self.otel.assert_has_event_named(
137+
"gen_ai.client.inference.operation.details"
138+
)
139+
event = self.otel.get_event_named(
140+
"gen_ai.client.inference.operation.details"
141+
)
142+
assert (
143+
event.attributes["extra_attribute_key"]
144+
== "extra_attribute_value"
145+
)
146+
finally:
147+
context_api.detach(tok)

instrumentation-genai/opentelemetry-instrumentation-langchain/src/opentelemetry/instrumentation/langchain/span_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
gen_ai_attributes as GenAI,
2121
)
2222
from opentelemetry.semconv.attributes import (
23-
error_attributes as ErrorAttributes,
23+
error_attributes,
2424
)
2525
from opentelemetry.trace import Span, SpanKind, Tracer, set_span_in_context
2626
from opentelemetry.trace.status import Status, StatusCode
@@ -112,6 +112,6 @@ def handle_error(self, error: BaseException, run_id: UUID):
112112
return
113113
span.set_status(Status(StatusCode.ERROR, str(error)))
114114
span.set_attribute(
115-
ErrorAttributes.ERROR_TYPE, type(error).__qualname__
115+
error_attributes.ERROR_TYPE, type(error).__qualname__
116116
)
117117
self.end_span(run_id)

0 commit comments

Comments
 (0)