Skip to content

Commit 02adc40

Browse files
Duplicate: add _semconv schem_url helper
1 parent a428585 commit 02adc40

2 files changed

Lines changed: 252 additions & 2 deletions

File tree

opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818
import threading
1919
from enum import Enum
2020
from typing import Container, Mapping, MutableMapping
21+
from urllib.parse import urlparse
22+
23+
from packaging import version as package_version
2124

2225
from opentelemetry.instrumentation.utils import http_status_to_status_code
2326
from opentelemetry.semconv._incubating.attributes.db_attributes import (
2427
DB_NAME,
28+
DB_OPERATION,
2529
DB_STATEMENT,
2630
DB_SYSTEM,
2731
DB_USER,
@@ -50,6 +54,7 @@
5054
)
5155
from opentelemetry.semconv.attributes.db_attributes import (
5256
DB_NAMESPACE,
57+
DB_OPERATION_NAME,
5358
DB_QUERY_TEXT,
5459
DB_SYSTEM_NAME,
5560
)
@@ -177,6 +182,9 @@
177182

178183
OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
179184

185+
# Legacy/default schema version when schema_url was first introduced
186+
_LEGACY_SCHEMA_VERSION = "1.11.0"
187+
180188

181189
class _OpenTelemetryStabilitySignalType(Enum):
182190
HTTP = "http"
@@ -590,6 +598,17 @@ def _set_db_user(
590598
# No new attribute - db.user was removed with no replacement
591599

592600

601+
def _set_db_operation(
602+
result: MutableMapping[str, AttributeValue],
603+
operation: str,
604+
sem_conv_opt_in_mode: _StabilityMode,
605+
) -> None:
606+
if _report_old(sem_conv_opt_in_mode):
607+
set_string_attribute(result, DB_OPERATION, operation)
608+
if _report_new(sem_conv_opt_in_mode):
609+
set_string_attribute(result, DB_OPERATION_NAME, operation)
610+
611+
593612
# General
594613

595614

@@ -634,8 +653,63 @@ def _set_status(
634653
span.set_status(Status(status))
635654

636655

637-
# Get schema version based off of opt-in mode
638656
def _get_schema_url(mode: _StabilityMode) -> str:
657+
"""Get schema version URL for a single signal type's opt-in mode (backwards compatible).
658+
659+
For new instrumentations using multiple signal types, use
660+
_get_schema_url_for_signal_types()
661+
"""
639662
if mode is _StabilityMode.DEFAULT:
640-
return "https://opentelemetry.io/schemas/1.11.0"
663+
return f"https://opentelemetry.io/schemas/{_LEGACY_SCHEMA_VERSION}"
641664
return Schemas.V1_21_0.value
665+
666+
667+
def _get_schema_version_for_opt_in_mode(
668+
signal_type: _OpenTelemetryStabilitySignalType,
669+
mode: _StabilityMode,
670+
) -> str:
671+
"""Get the schema version for a specific signal type and opt-in mode."""
672+
if mode == _StabilityMode.DEFAULT:
673+
return _LEGACY_SCHEMA_VERSION
674+
675+
signal_versions = {
676+
_OpenTelemetryStabilitySignalType.HTTP: Schemas.V1_21_0.value,
677+
_OpenTelemetryStabilitySignalType.DATABASE: Schemas.V1_25_0.value,
678+
_OpenTelemetryStabilitySignalType.GEN_AI: Schemas.V1_26_0.value,
679+
}
680+
schema_url = signal_versions.get(signal_type)
681+
if not schema_url:
682+
return _LEGACY_SCHEMA_VERSION
683+
684+
path = urlparse(schema_url).path
685+
schema_version = path.rstrip("/").split("/")[-1]
686+
return schema_version or _LEGACY_SCHEMA_VERSION
687+
688+
689+
def _get_schema_url_for_signal_types(
690+
signal_types: list[_OpenTelemetryStabilitySignalType],
691+
) -> str:
692+
"""Get the highest applicable schema URL for multiple signal types.
693+
694+
Note:
695+
Instrumentors should call _OpenTelemetrySemanticConventionStability._initialize()
696+
before using this function to ensure proper initialization of stability modes.
697+
698+
Args:
699+
signal_types: List of signal types used by the instrumentation
700+
701+
Returns:
702+
Schema URL string representing the highest applicable semconv version
703+
"""
704+
highest_schema_version = _LEGACY_SCHEMA_VERSION
705+
for signal_type in signal_types:
706+
mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
707+
signal_type
708+
)
709+
schema_version = _get_schema_version_for_opt_in_mode(signal_type, mode)
710+
# Keep the highest for all signals
711+
if package_version.Version(schema_version) > package_version.Version(
712+
highest_schema_version
713+
):
714+
highest_schema_version = schema_version
715+
return f"https://opentelemetry.io/schemas/{highest_schema_version}"

opentelemetry-instrumentation/tests/test_semconv.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
from unittest.mock import Mock, patch
1818

1919
from opentelemetry.instrumentation._semconv import (
20+
_LEGACY_SCHEMA_VERSION,
2021
OTEL_SEMCONV_STABILITY_OPT_IN,
22+
_get_schema_url_for_signal_types,
23+
_get_schema_version_for_opt_in_mode,
2124
_OpenTelemetrySemanticConventionStability,
2225
_OpenTelemetryStabilitySignalType,
2326
_set_db_name,
27+
_set_db_operation,
2428
_set_db_statement,
2529
_set_db_system,
2630
_set_db_user,
@@ -29,12 +33,14 @@
2933
)
3034
from opentelemetry.semconv._incubating.attributes.db_attributes import (
3135
DB_NAME,
36+
DB_OPERATION,
3237
DB_STATEMENT,
3338
DB_SYSTEM,
3439
DB_USER,
3540
)
3641
from opentelemetry.semconv.attributes.db_attributes import (
3742
DB_NAMESPACE,
43+
DB_OPERATION_NAME,
3844
DB_QUERY_TEXT,
3945
DB_SYSTEM_NAME,
4046
)
@@ -188,6 +194,134 @@ def test_stability_mode_dup_precedence(self):
188194
)
189195

190196

197+
class TestOpenTelemetrySemConvSchemaUrl(TestCase):
198+
@stability_mode("")
199+
def test_get_schema_version_for_opt_in_mode_default(self):
200+
version = _get_schema_version_for_opt_in_mode(
201+
_OpenTelemetryStabilitySignalType.HTTP, _StabilityMode.DEFAULT
202+
)
203+
self.assertEqual(version, _LEGACY_SCHEMA_VERSION)
204+
205+
version = _get_schema_version_for_opt_in_mode(
206+
_OpenTelemetryStabilitySignalType.DATABASE, _StabilityMode.DEFAULT
207+
)
208+
self.assertEqual(version, _LEGACY_SCHEMA_VERSION)
209+
210+
version = _get_schema_version_for_opt_in_mode(
211+
_OpenTelemetryStabilitySignalType.GEN_AI, _StabilityMode.DEFAULT
212+
)
213+
self.assertEqual(version, _LEGACY_SCHEMA_VERSION)
214+
215+
@stability_mode("")
216+
def test_get_schema_version_for_opt_in_mode_http_stable(self):
217+
version = _get_schema_version_for_opt_in_mode(
218+
_OpenTelemetryStabilitySignalType.HTTP, _StabilityMode.HTTP
219+
)
220+
self.assertEqual(version, "1.21.0")
221+
222+
@stability_mode("")
223+
def test_get_schema_version_for_opt_in_mode_database_stable(self):
224+
version = _get_schema_version_for_opt_in_mode(
225+
_OpenTelemetryStabilitySignalType.DATABASE, _StabilityMode.DATABASE
226+
)
227+
self.assertEqual(version, "1.25.0")
228+
229+
@stability_mode("")
230+
def test_get_schema_version_for_opt_in_mode_gen_ai_stable(self):
231+
version = _get_schema_version_for_opt_in_mode(
232+
_OpenTelemetryStabilitySignalType.GEN_AI,
233+
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
234+
)
235+
self.assertEqual(version, "1.26.0")
236+
237+
@stability_mode("")
238+
def test_get_schema_url_for_signal_types_single_http_default(self):
239+
url = _get_schema_url_for_signal_types(
240+
[_OpenTelemetryStabilitySignalType.HTTP]
241+
)
242+
self.assertEqual(
243+
url, f"https://opentelemetry.io/schemas/{_LEGACY_SCHEMA_VERSION}"
244+
)
245+
246+
@stability_mode("http")
247+
def test_get_schema_url_for_signal_types_single_http_stable(self):
248+
url = _get_schema_url_for_signal_types(
249+
[_OpenTelemetryStabilitySignalType.HTTP]
250+
)
251+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.21.0")
252+
253+
@stability_mode("database")
254+
def test_get_schema_url_for_signal_types_single_database_stable(self):
255+
url = _get_schema_url_for_signal_types(
256+
[_OpenTelemetryStabilitySignalType.DATABASE]
257+
)
258+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.25.0")
259+
260+
@stability_mode("http,database")
261+
def test_get_schema_url_for_signal_types_multiple_both_stable(self):
262+
# DATABASE has higher version (1.25.0) than HTTP (1.21.0)
263+
url = _get_schema_url_for_signal_types(
264+
[
265+
_OpenTelemetryStabilitySignalType.HTTP,
266+
_OpenTelemetryStabilitySignalType.DATABASE,
267+
]
268+
)
269+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.25.0")
270+
271+
@stability_mode("http")
272+
def test_get_schema_url_for_signal_types_mixed_modes(self):
273+
# HTTP is stable (1.21.0), DATABASE is default (1.11.0)
274+
# Should return HTTP version as it's higher
275+
url = _get_schema_url_for_signal_types(
276+
[
277+
_OpenTelemetryStabilitySignalType.HTTP,
278+
_OpenTelemetryStabilitySignalType.DATABASE,
279+
]
280+
)
281+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.21.0")
282+
283+
@stability_mode("database")
284+
def test_get_schema_url_for_signal_types_database_only_stable(self):
285+
# DATABASE is stable (1.25.0), HTTP is default (1.11.0)
286+
# Should return DATABASE version as it's highest
287+
url = _get_schema_url_for_signal_types(
288+
[
289+
_OpenTelemetryStabilitySignalType.HTTP,
290+
_OpenTelemetryStabilitySignalType.DATABASE,
291+
]
292+
)
293+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.25.0")
294+
295+
@stability_mode("")
296+
def test_get_schema_url_for_signal_types_empty_list(self):
297+
url = _get_schema_url_for_signal_types([])
298+
self.assertEqual(
299+
url, f"https://opentelemetry.io/schemas/{_LEGACY_SCHEMA_VERSION}"
300+
)
301+
302+
@stability_mode("http/dup,database/dup")
303+
def test_get_schema_url_for_signal_types_dup_modes(self):
304+
url = _get_schema_url_for_signal_types(
305+
[
306+
_OpenTelemetryStabilitySignalType.HTTP,
307+
_OpenTelemetryStabilitySignalType.DATABASE,
308+
]
309+
)
310+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.25.0")
311+
312+
@stability_mode("http,database,gen_ai_latest_experimental")
313+
def test_get_schema_url_for_signal_types_with_gen_ai(self):
314+
# GEN_AI should be highest at 1.26.0
315+
url = _get_schema_url_for_signal_types(
316+
[
317+
_OpenTelemetryStabilitySignalType.HTTP,
318+
_OpenTelemetryStabilitySignalType.DATABASE,
319+
_OpenTelemetryStabilitySignalType.GEN_AI,
320+
]
321+
)
322+
self.assertEqual(url, "https://opentelemetry.io/schemas/1.26.0")
323+
324+
191325
class TestOpenTelemetrySemConvStabilityHTTP(TestCase):
192326
def test_set_status_for_non_http_code_with_recording_span(self):
193327
span = Mock()
@@ -443,3 +577,45 @@ def test_db_user_none_value(self):
443577
result = {}
444578
_set_db_user(result, None, sem_conv_opt_in_mode=_StabilityMode.DEFAULT)
445579
self.assertNotIn(DB_USER, result)
580+
581+
def test_db_operation_default(self):
582+
result = {}
583+
_set_db_operation(
584+
result,
585+
"SELECT",
586+
sem_conv_opt_in_mode=_StabilityMode.DEFAULT,
587+
)
588+
self.assertIn(DB_OPERATION, result)
589+
self.assertEqual(result[DB_OPERATION], "SELECT")
590+
self.assertNotIn(DB_OPERATION_NAME, result)
591+
592+
def test_db_operation_database_stable(self):
593+
result = {}
594+
_set_db_operation(
595+
result,
596+
"SELECT",
597+
sem_conv_opt_in_mode=_StabilityMode.DATABASE,
598+
)
599+
self.assertNotIn(DB_OPERATION, result)
600+
self.assertIn(DB_OPERATION_NAME, result)
601+
self.assertEqual(result[DB_OPERATION_NAME], "SELECT")
602+
603+
def test_db_operation_database_dup(self):
604+
result = {}
605+
_set_db_operation(
606+
result,
607+
"SELECT",
608+
sem_conv_opt_in_mode=_StabilityMode.DATABASE_DUP,
609+
)
610+
self.assertIn(DB_OPERATION, result)
611+
self.assertEqual(result[DB_OPERATION], "SELECT")
612+
self.assertIn(DB_OPERATION_NAME, result)
613+
self.assertEqual(result[DB_OPERATION_NAME], "SELECT")
614+
615+
def test_db_operation_none_value(self):
616+
result = {}
617+
_set_db_operation(
618+
result, None, sem_conv_opt_in_mode=_StabilityMode.DEFAULT
619+
)
620+
self.assertNotIn(DB_OPERATION, result)
621+
self.assertNotIn(DB_OPERATION_NAME, result)

0 commit comments

Comments
 (0)