11# Copyright The OpenTelemetry Authors
22# SPDX-License-Identifier: Apache-2.0
33import asyncio
4+ import contextlib
45import logging
6+ from types import SimpleNamespace
57from unittest import mock
68from unittest .mock import MagicMock
79
1012
1113import opentelemetry .instrumentation .aiopg
1214from opentelemetry import trace as trace_api
15+ from opentelemetry .instrumentation ._semconv import (
16+ OTEL_SEMCONV_STABILITY_OPT_IN ,
17+ _OpenTelemetrySemanticConventionStability ,
18+ )
1319from opentelemetry .instrumentation .aiopg import AiopgInstrumentor , wrappers
1420from opentelemetry .instrumentation .aiopg .aiopg_integration import (
1521 AiopgIntegration ,
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+ )
3046from opentelemetry .test .test_base import TestBase
3147
3248
3349def 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+
3768class 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):
280360class 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
539744class MockConnection :
0 commit comments