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 - `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168)) ([#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) ## 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'*/ 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 Usage
----- -----
.. code:: python .. code:: python
@ -138,6 +162,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
``meter_provider``: a MeterProvider, defaults to global ``meter_provider``: a MeterProvider, defaults to global
``enable_commenter``: bool to enable sqlcommenter, defaults to False ``enable_commenter``: bool to enable sqlcommenter, defaults to False
``commenter_options``: dict of sqlcommenter config, defaults to {} ``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: Returns:
An instrumented engine if passed in as an argument or list of instrumented engines, None otherwise. 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) enable_commenter = kwargs.get("enable_commenter", False)
commenter_options = kwargs.get("commenter_options", {}) commenter_options = kwargs.get("commenter_options", {})
enable_attribute_commenter = kwargs.get(
"enable_attribute_commenter", False
)
_w( _w(
"sqlalchemy", "sqlalchemy",
"create_engine", "create_engine",
_wrap_create_engine( _wrap_create_engine(
tracer, connections_usage, enable_commenter, commenter_options tracer,
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
), ),
) )
_w( _w(
"sqlalchemy.engine", "sqlalchemy.engine",
"create_engine", "create_engine",
_wrap_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) # sqlalchemy.engine.create is not present in earlier versions of sqlalchemy (which we support)
@ -191,6 +227,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage, connections_usage,
enable_commenter, enable_commenter,
commenter_options, commenter_options,
enable_attribute_commenter,
), ),
) )
_w( _w(
@ -207,6 +244,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage, connections_usage,
enable_commenter, enable_commenter,
commenter_options, commenter_options,
enable_attribute_commenter,
), ),
) )
if kwargs.get("engine") is not None: if kwargs.get("engine") is not None:
@ -216,6 +254,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage, connections_usage,
kwargs.get("enable_commenter", False), kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}), kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
) )
if kwargs.get("engines") is not None and isinstance( if kwargs.get("engines") is not None and isinstance(
kwargs.get("engines"), Sequence kwargs.get("engines"), Sequence
@ -227,6 +266,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
connections_usage, connections_usage,
kwargs.get("enable_commenter", False), kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}), kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
) )
for engine in kwargs.get("engines") for engine in kwargs.get("engines")
] ]

View File

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

View File

@ -209,6 +209,44 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[-2].getMessage(), 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}'\*/;", 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): def test_create_engine_wrapper_enable_commenter_otel_values_false(self):
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
@ -229,6 +267,49 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[-2].getMessage(), self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;", 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): def test_custom_tracer_provider(self):
provider = TracerProvider( provider = TracerProvider(
@ -321,6 +402,55 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[1].getMessage(), 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}'\*/;", 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()) asyncio.get_event_loop().run_until_complete(run())
@ -352,6 +482,58 @@ class TestSqlalchemyInstrumentation(TestBase):
self.caplog.records[1].getMessage(), self.caplog.records[1].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;", 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()) 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}'\*/;", 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:") engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument( SQLAlchemyInstrumentor().instrument(
engine=engine, engine=engine,
tracer_provider=self.tracer_provider, tracer_provider=self.tracer_provider,
enable_commenter=True, enable_commenter=True,
commenter_options={"db_framework": False}, 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 = engine.connect()
cnx.execute(text("SELECT 1;")).fetchall() cnx.execute(text("SELECT 1;")).fetchall()
@ -110,6 +194,45 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(), self.caplog.records[-2].getMessage(),
r"SELECT 1 /\*db_driver='(.*)'\*/;", 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): def test_sqlcommenter_flask_integration(self):
engine = create_engine("sqlite:///:memory:") engine = create_engine("sqlite:///:memory:")
@ -132,6 +255,49 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(), 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}'\*/;", 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): def test_sqlcommenter_enabled_create_engine_after_instrumentation(self):
SQLAlchemyInstrumentor().instrument( SQLAlchemyInstrumentor().instrument(
@ -147,6 +313,44 @@ class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
self.caplog.records[-2].getMessage(), 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}'\*/;", 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): def test_sqlcommenter_disabled_create_engine_after_instrumentation(self):
SQLAlchemyInstrumentor().instrument( SQLAlchemyInstrumentor().instrument(