Skip to content

Commit ee7a74a

Browse files
henry3260xrmx
andauthored
opentelemetry-instrumentation-aiopg: semantic convention stability migration (#1636) (#4820)
Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent e244407 commit ee7a74a

5 files changed

Lines changed: 230 additions & 12 deletions

File tree

.changelog/4820.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-instrumentation-aiopg`: add database semantic convention stability support through `OTEL_SEMCONV_STABILITY_OPT_IN`

instrumentation/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
| [opentelemetry-instrumentation-aiohttp-client](./opentelemetry-instrumentation-aiohttp-client) | aiohttp ~= 3.0 | Yes | migration
66
| [opentelemetry-instrumentation-aiohttp-server](./opentelemetry-instrumentation-aiohttp-server) | aiohttp ~= 3.0 | Yes | migration
77
| [opentelemetry-instrumentation-aiokafka](./opentelemetry-instrumentation-aiokafka) | aiokafka >= 0.8, < 1.0 | No | development
8-
| [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 2.0.0 | No | development
8+
| [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 2.0.0 | No | migration
99
| [opentelemetry-instrumentation-asgi](./opentelemetry-instrumentation-asgi) | asgiref ~= 3.0 | Yes | migration
1010
| [opentelemetry-instrumentation-asyncclick](./opentelemetry-instrumentation-asyncclick) | asyncclick ~= 8.0 | No | development
1111
| [opentelemetry-instrumentation-asyncio](./opentelemetry-instrumentation-asyncio) | asyncio | No | development

instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/aiopg_integration.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
import wrapt
99

10+
from opentelemetry.instrumentation._semconv import _report_new
1011
from opentelemetry.instrumentation.dbapi import (
1112
CursorTracer,
1213
DatabaseApiIntegration,
1314
)
15+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
1416
from opentelemetry.trace import SpanKind
1517

1618

@@ -121,8 +123,16 @@ async def traced_execution(
121123
with self._db_api_integration._tracer.start_as_current_span(
122124
name, kind=SpanKind.CLIENT
123125
) as span:
124-
self._populate_span(span, cursor, *args)
125-
return await query_method(*args, **kwargs)
126+
if span.is_recording():
127+
self._populate_span(span, cursor, *args)
128+
try:
129+
return await query_method(*args, **kwargs)
130+
except Exception as exc:
131+
if span.is_recording() and _report_new(
132+
self._db_api_integration._sem_conv_opt_in_mode_db
133+
):
134+
span.set_attribute(ERROR_TYPE, type(exc).__qualname__)
135+
raise
126136

127137

128138
def get_traced_cursor_proxy(cursor, db_api_integration, *args, **kwargs):

instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/package.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33

44

55
_instruments = ("aiopg >= 0.13.0, < 2.0.0",)
6+
7+
_semconv_status = "migration"

instrumentation/opentelemetry-instrumentation-aiopg/tests/test_aiopg_integration.py

Lines changed: 214 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Copyright The OpenTelemetry Authors
22
# SPDX-License-Identifier: Apache-2.0
33
import asyncio
4+
import contextlib
45
import logging
6+
from types import SimpleNamespace
57
from unittest import mock
68
from unittest.mock import MagicMock
79

@@ -10,6 +12,10 @@
1012

1113
import opentelemetry.instrumentation.aiopg
1214
from opentelemetry import trace as trace_api
15+
from opentelemetry.instrumentation._semconv import (
16+
OTEL_SEMCONV_STABILITY_OPT_IN,
17+
_OpenTelemetrySemanticConventionStability,
18+
)
1319
from opentelemetry.instrumentation.aiopg import AiopgInstrumentor, wrappers
1420
from opentelemetry.instrumentation.aiopg.aiopg_integration import (
1521
AiopgIntegration,
@@ -27,13 +33,38 @@
2733
NET_PEER_NAME,
2834
NET_PEER_PORT,
2935
)
36+
from opentelemetry.semconv.attributes.db_attributes import (
37+
DB_NAMESPACE,
38+
DB_QUERY_TEXT,
39+
DB_SYSTEM_NAME,
40+
)
41+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
42+
from opentelemetry.semconv.attributes.server_attributes import (
43+
SERVER_ADDRESS,
44+
SERVER_PORT,
45+
)
3046
from opentelemetry.test.test_base import TestBase
3147

3248

3349
def async_call(coro):
3450
return asyncio.run(coro)
3551

3652

53+
@contextlib.contextmanager
54+
def use_semconv_opt_in(sem_conv_mode):
55+
env_patch = mock.patch.dict(
56+
"os.environ",
57+
{OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode},
58+
)
59+
_OpenTelemetrySemanticConventionStability._initialized = False
60+
env_patch.start()
61+
try:
62+
yield
63+
finally:
64+
env_patch.stop()
65+
_OpenTelemetrySemanticConventionStability._initialized = False
66+
67+
3768
class TestAiopgInstrumentor(TestBase):
3869
def setUp(self):
3970
super().setUp()
@@ -79,6 +110,34 @@ def test_instrumentor_connect(self):
79110
spans_list = self.memory_exporter.get_finished_spans()
80111
self.assertEqual(len(spans_list), 1)
81112

113+
def test_instrumentor_connect_new_semconv(self):
114+
with use_semconv_opt_in("database,http"):
115+
AiopgInstrumentor().instrument()
116+
117+
cnx = async_call(
118+
aiopg.connect(
119+
database="testdatabase",
120+
server_host="testhost",
121+
server_port=123,
122+
)
123+
)
124+
cursor = async_call(cnx.cursor())
125+
async_call(cursor.execute("SELECT * FROM test"))
126+
127+
spans_list = self.memory_exporter.get_finished_spans()
128+
self.assertEqual(len(spans_list), 1)
129+
span = spans_list[0]
130+
self.assertEqual(span.attributes[DB_SYSTEM_NAME], "postgresql")
131+
self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase")
132+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "SELECT * FROM test")
133+
self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost")
134+
self.assertEqual(span.attributes[SERVER_PORT], 123)
135+
self.assertNotIn(DB_SYSTEM, span.attributes)
136+
self.assertNotIn(DB_NAME, span.attributes)
137+
self.assertNotIn(DB_STATEMENT, span.attributes)
138+
self.assertNotIn(NET_PEER_NAME, span.attributes)
139+
self.assertNotIn(NET_PEER_PORT, span.attributes)
140+
82141
def test_instrumentor_connect_ctx_manager(self):
83142
async def _ctx_manager_connect():
84143
AiopgInstrumentor().instrument()
@@ -203,6 +262,27 @@ def test_instrument_connection(self):
203262
spans_list = self.memory_exporter.get_finished_spans()
204263
self.assertEqual(len(spans_list), 1)
205264

265+
def test_instrument_connection_new_semconv(self):
266+
with use_semconv_opt_in("database,http"):
267+
cnx = async_call(
268+
aiopg.connect(
269+
database="testdatabase",
270+
server_host="testhost",
271+
server_port=123,
272+
)
273+
)
274+
cnx = AiopgInstrumentor().instrument_connection(cnx)
275+
cursor = async_call(cnx.cursor())
276+
async_call(cursor.execute("SELECT * FROM test"))
277+
278+
spans_list = self.memory_exporter.get_finished_spans()
279+
self.assertEqual(len(spans_list), 1)
280+
span = spans_list[0]
281+
self.assertEqual(span.attributes[DB_SYSTEM_NAME], "postgresql")
282+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "SELECT * FROM test")
283+
self.assertNotIn(DB_SYSTEM, span.attributes)
284+
self.assertNotIn(DB_STATEMENT, span.attributes)
285+
206286
def test_instrument_connection_after_instrument(self):
207287
cnx = async_call(aiopg.connect(database="test"))
208288
query = "SELECT * FROM test"
@@ -280,8 +360,13 @@ def test_uninstrument_connection(self):
280360
class TestAiopgIntegration(TestBase):
281361
def setUp(self):
282362
super().setUp()
363+
_OpenTelemetrySemanticConventionStability._initialized = False
283364
self.tracer = self.tracer_provider.get_tracer(__name__)
284365

366+
def tearDown(self):
367+
_OpenTelemetrySemanticConventionStability._initialized = False
368+
super().tearDown()
369+
285370
def test_span_succeeded(self):
286371
connection_props = {
287372
"database": "testdatabase",
@@ -339,12 +424,11 @@ def test_span_not_recording(self):
339424
"host": "server_host",
340425
"user": "user",
341426
}
342-
mock_tracer = mock.Mock()
343-
mock_span = mock.Mock()
344-
mock_span.is_recording.return_value = False
345-
mock_tracer.start_span.return_value = mock_span
346427
db_integration = AiopgIntegration(
347-
mock_tracer, "testcomponent", connection_attributes
428+
__name__,
429+
"testcomponent",
430+
connection_attributes,
431+
tracer_provider=trace_api.NoOpTracerProvider(),
348432
)
349433
mock_connection = async_call(
350434
db_integration.wrapped_connection(
@@ -353,10 +437,108 @@ def test_span_not_recording(self):
353437
)
354438
cursor = async_call(mock_connection.cursor())
355439
async_call(cursor.execute("Test query", ("param1Value", False)))
356-
self.assertFalse(mock_span.is_recording())
357-
self.assertTrue(mock_span.is_recording.called)
358-
self.assertFalse(mock_span.set_attribute.called)
359-
self.assertFalse(mock_span.set_status.called)
440+
self.assertEqual(len(self.memory_exporter.get_finished_spans()), 0)
441+
442+
def test_span_succeeded_new_semconv(self):
443+
connection_props = {
444+
"database": "testdatabase",
445+
"server_host": "testhost",
446+
"server_port": 123,
447+
"user": "testuser",
448+
}
449+
connection_attributes = {
450+
"database": "database",
451+
"port": "server_port",
452+
"host": "server_host",
453+
"user": "user",
454+
}
455+
with use_semconv_opt_in("database,http"):
456+
db_integration = AiopgIntegration(
457+
__name__, "testcomponent", connection_attributes
458+
)
459+
mock_connection = async_call(
460+
db_integration.wrapped_connection(
461+
mock_connect, {}, connection_props
462+
)
463+
)
464+
cursor = async_call(mock_connection.cursor())
465+
async_call(cursor.execute("Test query"))
466+
467+
spans_list = self.memory_exporter.get_finished_spans()
468+
self.assertEqual(len(spans_list), 1)
469+
span = spans_list[0]
470+
self.assertEqual(
471+
span.instrumentation_scope.schema_url,
472+
"https://opentelemetry.io/schemas/1.25.0",
473+
)
474+
self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent")
475+
self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase")
476+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query")
477+
self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost")
478+
self.assertEqual(span.attributes[SERVER_PORT], 123)
479+
self.assertNotIn(DB_SYSTEM, span.attributes)
480+
self.assertNotIn(DB_NAME, span.attributes)
481+
self.assertNotIn(DB_STATEMENT, span.attributes)
482+
self.assertNotIn(DB_USER, span.attributes)
483+
self.assertNotIn(NET_PEER_NAME, span.attributes)
484+
self.assertNotIn(NET_PEER_PORT, span.attributes)
485+
486+
def test_span_succeeded_both_semconv(self):
487+
connection_props = {
488+
"database": "testdatabase",
489+
"server_host": "testhost",
490+
"server_port": 123,
491+
"user": "testuser",
492+
}
493+
connection_attributes = {
494+
"database": "database",
495+
"port": "server_port",
496+
"host": "server_host",
497+
"user": "user",
498+
}
499+
with use_semconv_opt_in("database/dup,http/dup"):
500+
db_integration = AiopgIntegration(
501+
__name__, "testcomponent", connection_attributes
502+
)
503+
mock_connection = async_call(
504+
db_integration.wrapped_connection(
505+
mock_connect, {}, connection_props
506+
)
507+
)
508+
cursor = async_call(mock_connection.cursor())
509+
async_call(cursor.execute("Test query"))
510+
511+
spans_list = self.memory_exporter.get_finished_spans()
512+
self.assertEqual(len(spans_list), 1)
513+
span = spans_list[0]
514+
self.assertEqual(span.attributes[DB_SYSTEM], "testcomponent")
515+
self.assertEqual(span.attributes[DB_NAME], "testdatabase")
516+
self.assertEqual(span.attributes[DB_STATEMENT], "Test query")
517+
self.assertEqual(span.attributes[DB_USER], "testuser")
518+
self.assertEqual(span.attributes[NET_PEER_NAME], "testhost")
519+
self.assertEqual(span.attributes[NET_PEER_PORT], 123)
520+
self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent")
521+
self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase")
522+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query")
523+
self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost")
524+
self.assertEqual(span.attributes[SERVER_PORT], 123)
525+
526+
def test_executemany_new_semconv(self):
527+
with use_semconv_opt_in("database"):
528+
db_integration = AiopgIntegration(__name__, "testcomponent")
529+
mock_connection = async_call(
530+
db_integration.wrapped_connection(mock_connect, {}, {})
531+
)
532+
cursor = async_call(mock_connection.cursor())
533+
async_call(cursor.executemany("Test query"))
534+
535+
spans_list = self.memory_exporter.get_finished_spans()
536+
self.assertEqual(len(spans_list), 1)
537+
span = spans_list[0]
538+
self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent")
539+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query")
540+
self.assertNotIn(DB_SYSTEM, span.attributes)
541+
self.assertNotIn(DB_STATEMENT, span.attributes)
360542

361543
def test_span_failed(self):
362544
db_integration = AiopgIntegration(self.tracer, "testcomponent")
@@ -376,6 +558,23 @@ def test_span_failed(self):
376558
span.status.description, "ProgrammingError: Test Exception"
377559
)
378560

561+
def test_span_failed_new_semconv(self):
562+
with use_semconv_opt_in("database"):
563+
db_integration = AiopgIntegration(__name__, "testcomponent")
564+
mock_connection = async_call(
565+
db_integration.wrapped_connection(mock_connect, {}, {})
566+
)
567+
cursor = async_call(mock_connection.cursor())
568+
with self.assertRaises(psycopg2.ProgrammingError):
569+
async_call(cursor.execute("Test query", throw_exception=True))
570+
571+
spans_list = self.memory_exporter.get_finished_spans()
572+
self.assertEqual(len(spans_list), 1)
573+
span = spans_list[0]
574+
self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query")
575+
self.assertEqual(span.attributes[ERROR_TYPE], "ProgrammingError")
576+
self.assertIs(span.status.status_code, trace_api.StatusCode.ERROR)
577+
379578
def test_executemany(self):
380579
db_integration = AiopgIntegration(self.tracer, "testcomponent")
381580
mock_connection = async_call(
@@ -534,6 +733,12 @@ def __init__(self, database, server_port, server_host, user):
534733
self.server_port = server_port
535734
self.server_host = server_host
536735
self.user = user
736+
self.info = SimpleNamespace(
737+
dbname=database,
738+
port=server_port,
739+
host=server_host,
740+
user=user,
741+
)
537742

538743

539744
class MockConnection:

0 commit comments

Comments
 (0)