SQLAlchemy: db.statement inclusion of sqlcomment as opt-in (#3112)

This commit is contained in:
Tammy Baylis
2025-01-09 12:56:14 -08:00
committed by GitHub
parent 908437db5d
commit 26bcc9347b
5 changed files with 494 additions and 26 deletions

View File

@ -37,6 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
### Breaking changes
- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))
## Version 1.29.0/0.50b0 (2024-12-11)

View File

@ -65,6 +65,30 @@ For example,
::
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
SQLComment in span attribute
****************************
If sqlcommenter is enabled, you can further configure SQLAlchemy instrumentation to append sqlcomment to the `db.statement` span attribute for convenience of your platform.
.. code:: python
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={},
enable_attribute_commenter=True,
)
For example,
::
Invoking `engine.execute("select * from auth_users")` will lead to sql query "select * from auth_users" but when SQLCommenter and `attribute_commenter` is enabled
the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" for both server query and `db.statement` span attribute.
Warning: capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans <https://opentelemetry.io/docs/specs/semconv/database/database-spans/#generating-a-summary-of-the-query-text>`_ for more information.
Usage
-----
.. code:: python
@ -138,6 +162,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
``meter_provider``: a MeterProvider, defaults to global
``enable_commenter``: bool to enable sqlcommenter, defaults to False
``commenter_options``: dict of sqlcommenter config, defaults to {}
``enable_attribute_commenter``: bool to enable sqlcomment addition to span attribute, defaults to False. Must also set `enable_commenter`.
Returns:
An instrumented engine if passed in as an argument or list of instrumented engines, None otherwise.
@ -166,19 +191,30 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
enable_commenter = kwargs.get("enable_commenter", False)
commenter_options = kwargs.get("commenter_options", {})
enable_attribute_commenter = kwargs.get(
"enable_attribute_commenter", False
)
_w(
"sqlalchemy",
"create_engine",
_wrap_create_engine(
tracer, connections_usage, enable_commenter, commenter_options
tracer,
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
_w(
"sqlalchemy.engine",
"create_engine",
_wrap_create_engine(
tracer, connections_usage, enable_commenter, commenter_options
tracer,
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
# sqlalchemy.engine.create is not present in earlier versions of sqlalchemy (which we support)
@ -191,6 +227,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
_w(
@ -207,6 +244,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
if kwargs.get("engine") is not None:
@ -216,6 +254,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage,
kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
)
if kwargs.get("engines") is not None and isinstance(
kwargs.get("engines"), Sequence
@ -227,6 +266,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage,
kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
)
for engine in kwargs.get("engines")
]

View File

@ -43,7 +43,11 @@ def _normalize_vendor(vendor):
def _wrap_create_async_engine(
tracer, connections_usage, enable_commenter=False, commenter_options=None
tracer,
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
# pylint: disable=unused-argument
def _wrap_create_async_engine_internal(func, module, args, kwargs):
@ -57,6 +61,7 @@ def _wrap_create_async_engine(
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
)
return engine
@ -64,7 +69,11 @@ def _wrap_create_async_engine(
def _wrap_create_engine(
tracer, connections_usage, enable_commenter=False, commenter_options=None
tracer,
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
def _wrap_create_engine_internal(func, _module, args, kwargs):
"""Trace the SQLAlchemy engine, creating an `EngineTracer`
@ -77,6 +86,7 @@ def _wrap_create_engine(
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
)
return engine
@ -110,12 +120,14 @@ class EngineTracer:
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
self.tracer = tracer
self.connections_usage = connections_usage
self.vendor = _normalize_vendor(engine.name)
self.enable_commenter = enable_commenter
self.commenter_options = commenter_options if commenter_options else {}
self.enable_attribute_commenter = enable_attribute_commenter
self._engine_attrs = _get_attributes_from_engine(engine)
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
@ -218,6 +230,32 @@ class EngineTracer:
return self.vendor
return " ".join(parts)
def _get_commenter_data(self, conn) -> dict:
"""Calculate sqlcomment contents from conn and configured options"""
commenter_data = {
"db_driver": conn.engine.driver,
# Driver/framework centric information.
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
}
if self.commenter_options.get("opentelemetry_values", True):
commenter_data.update(**_get_opentelemetry_values())
# Filter down to just the requested attributes.
commenter_data = {
k: v
for k, v in commenter_data.items()
if self.commenter_options.get(k, True)
}
return commenter_data
def _set_db_client_span_attributes(self, span, statement, attrs) -> None:
"""Uses statement and attrs to set attributes of provided Otel span"""
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
for key, value in attrs.items():
span.set_attribute(key, value)
def _before_cur_exec(
self, conn, cursor, statement, params, context, _executemany
):
@ -233,30 +271,30 @@ class EngineTracer:
with trace.use_span(span, end_on_exit=False):
if span.is_recording():
if self.enable_commenter:
commenter_data = {
"db_driver": conn.engine.driver,
# Driver/framework centric information.
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
}
commenter_data = self._get_commenter_data(conn)
if self.commenter_options.get(
"opentelemetry_values", True
):
commenter_data.update(**_get_opentelemetry_values())
if self.enable_attribute_commenter:
# sqlcomment is added to executed query and db.statement span attribute
statement = _add_sql_comment(
statement, **commenter_data
)
self._set_db_client_span_attributes(
span, statement, attrs
)
# Filter down to just the requested attributes.
commenter_data = {
k: v
for k, v in commenter_data.items()
if self.commenter_options.get(k, True)
}
else:
# sqlcomment is only added to executed query
# so db.statement is set before add_sql_comment
self._set_db_client_span_attributes(
span, statement, attrs
)
statement = _add_sql_comment(
statement, **commenter_data
)
statement = _add_sql_comment(statement, **commenter_data)
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
for key, value in attrs.items():
span.set_attribute(key, value)
else:
# no sqlcomment anywhere
self._set_db_client_span_attributes(span, statement, attrs)
context._otel_span = span

View File

@ -209,6 +209,44 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_create_engine_wrapper_enable_commenter_stmt_enabled(self):
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={"db_framework": False},
enable_attribute_commenter=True,
)
from sqlalchemy import create_engine # pylint: disable-all
engine = create_engine("sqlite:///:memory:")
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
# sqlcommenter
self.assertRegex(
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
def test_create_engine_wrapper_enable_commenter_otel_values_false(self):
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
@ -229,6 +267,49 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_create_engine_wrapper_enable_commenter_stmt_enabled_otel_values_false(
self,
):
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={
"db_framework": False,
"opentelemetry_values": False,
},
enable_attribute_commenter=True,
)
from sqlalchemy import create_engine # pylint: disable-all
engine = create_engine("sqlite:///:memory:")
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
# sqlcommenter
self.assertRegex(
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
def test_custom_tracer_provider(self):
provider = TracerProvider(
@ -321,6 +402,55 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[1].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
asyncio.get_event_loop().run_until_complete(run())
@pytest.mark.skipif(
not sqlalchemy.__version__.startswith("1.4"),
reason="only run async tests for 1.4",
)
def test_create_async_engine_wrapper_enable_commenter_stmt_enabled(self):
async def run():
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={
"db_framework": False,
},
enable_attribute_commenter=True,
)
from sqlalchemy.ext.asyncio import ( # pylint: disable-all
create_async_engine,
)
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.connect() as cnx:
await cnx.execute(text("SELECT 1;"))
# sqlcommenter
self.assertRegex(
self.caplog.records[1].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
asyncio.get_event_loop().run_until_complete(run())
@ -352,6 +482,58 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[1].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
asyncio.get_event_loop().run_until_complete(run())
@pytest.mark.skipif(
not sqlalchemy.__version__.startswith("1.4"),
reason="only run async tests for 1.4",
)
def test_create_async_engine_wrapper_enable_commenter_stmt_enabled_otel_values_false(
self,
):
async def run():
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={
"db_framework": False,
"opentelemetry_values": False,
},
enable_attribute_commenter=True,
)
from sqlalchemy.ext.asyncio import ( # pylint: disable-all
create_async_engine,
)
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.connect() as cnx:
await cnx.execute(text("SELECT 1;"))
# sqlcommenter
self.assertRegex(
self.caplog.records[1].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)'*/;",
)
asyncio.get_event_loop().run_until_complete(run())

View File

@ -61,13 +61,97 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
def test_sqlcommenter_enabled_matches_db_statement_attribute(self):
def test_sqlcommenter_default_stmt_enabled_no_comments_anywhere(self):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
# enable_commenter not set
enable_attribute_commenter=True,
)
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
query_log = self.caplog.records[-2].getMessage()
self.assertEqual(
query_log,
"SELECT 1;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_sqlcommenter_disabled_stmt_enabled_no_comments_anywhere(self):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
enable_commenter=False,
enable_attribute_commenter=True,
)
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
query_log = self.caplog.records[-2].getMessage()
self.assertEqual(
query_log,
"SELECT 1;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_sqlcommenter_enabled_stmt_disabled_default(
self,
):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
enable_commenter=True,
commenter_options={"db_framework": False},
# enable_attribute_commenter not set
)
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
query_log = self.caplog.records[-2].getMessage()
self.assertRegex(
query_log,
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_sqlcommenter_enabled_stmt_enabled_matches_db_statement_attribute(
self,
):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
enable_commenter=True,
commenter_options={"db_framework": False},
enable_attribute_commenter=True,
)
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
@ -110,6 +194,45 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1;",
)
def test_sqlcommenter_enabled_stmt_enabled_otel_values_false(self):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
enable_commenter=True,
commenter_options={
"db_framework": False,
"opentelemetry_values": False,
},
enable_attribute_commenter=True,
)
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
self.assertRegex(
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)'*/;",
)
def test_sqlcommenter_flask_integration(self):
engine = create_engine("sqlite:///:memory:")
@ -132,6 +255,49 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',flask=1,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_sqlcommenter_stmt_enabled_flask_integration(self):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
enable_commenter=True,
commenter_options={"db_framework": False},
enable_attribute_commenter=True,
)
cnx = engine.connect()
current_context = context.get_current()
sqlcommenter_context = context.set_value(
"SQLCOMMENTER_ORM_TAGS_AND_VALUES", {"flask": 1}, current_context
)
context.attach(sqlcommenter_context)
cnx.execute(text("SELECT 1;")).fetchall()
self.assertRegex(
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',flask=1,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)',flask=1,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
def test_sqlcommenter_enabled_create_engine_after_instrumentation(self):
SQLAlchemyInstrumentor().instrument(
@ -147,6 +313,44 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertEqual(
query_span.attributes[SpanAttributes.DB_STATEMENT],
"SELECT 1;",
)
def test_sqlcommenter_enabled_stmt_enabled_create_engine_after_instrumentation(
self,
):
SQLAlchemyInstrumentor().instrument(
tracer_provider=self.tracer_provider,
enable_commenter=True,
enable_attribute_commenter=True,
)
from sqlalchemy import create_engine # pylint: disable-all
engine = create_engine("sqlite:///:memory:")
cnx = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall()
self.assertRegex(
self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# first span is connection to db
self.assertEqual(spans[0].name, "connect")
# second span is query itself
query_span = spans[1]
self.assertRegex(
query_span.attributes[SpanAttributes.DB_STATEMENT],
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
)
def test_sqlcommenter_disabled_create_engine_after_instrumentation(self):
SQLAlchemyInstrumentor().instrument(