mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-28 20:52:57 +08:00
SQLAlchemy: db.statement inclusion of sqlcomment as opt-in (#3112)
This commit is contained in:
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user