Skip to content

Commit a663628

Browse files
author
Daniel Rogers
committed
Strip leading comments from SQL queries when generating the span name.
1 parent 868049e commit a663628

File tree

9 files changed

+93
-6
lines changed

9 files changed

+93
-6
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
([#1350](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1350))
1818
- `opentelemetry-instrumentation-starlette` Add support for regular expression matching and sanitization of HTTP headers.
1919
([#1404](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1404))
20+
- Strip leading comments from SQL queries when generating the span name.
21+
([#1434](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1434))
2022

2123
### Fixed
2224

instrumentation/opentelemetry-instrumentation-asyncpg/src/opentelemetry/instrumentation/asyncpg/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
---
3535
"""
3636

37+
import re
3738
from typing import Collection
3839

3940
import asyncpg
@@ -99,6 +100,7 @@ def __init__(self, capture_parameters=False):
99100
super().__init__()
100101
self.capture_parameters = capture_parameters
101102
self._tracer = None
103+
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
102104

103105
def instrumentation_dependencies(self) -> Collection[str]:
104106
return _instruments
@@ -135,7 +137,8 @@ async def _do_execute(self, func, instance, args, kwargs):
135137
name = args[0] if args[0] else params.get("database", "postgresql")
136138

137139
try:
138-
name = name.split()[0]
140+
# Strip leading comments so we get the operation name.
141+
name = self._leading_comment_remover.sub("", name).split()[0]
139142
except IndexError:
140143
name = ""
141144

instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import functools
4141
import logging
42+
import re
4243
import typing
4344

4445
import wrapt
@@ -368,6 +369,7 @@ def __init__(self, db_api_integration: DatabaseApiIntegration) -> None:
368369
else {}
369370
)
370371
self._connect_module = self._db_api_integration.connect_module
372+
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
371373

372374
def _populate_span(
373375
self,
@@ -397,7 +399,8 @@ def _populate_span(
397399

398400
def get_operation_name(self, cursor, args): # pylint: disable=no-self-use
399401
if args and isinstance(args[0], str):
400-
return args[0].split()[0]
402+
# Strip leading comments so we get the operation name.
403+
return self._leading_comment_remover.sub("", args[0]).split()[0]
401404
return ""
402405

403406
def get_statement(self, cursor, args): # pylint: disable=no-self-use

instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,17 @@ def test_span_name(self):
8888
query"""
8989
)
9090
cursor.execute("tab\tseparated query")
91+
cursor.execute("/* leading comment */ query")
92+
cursor.execute("/* leading comment */ query /* trailing comment */")
93+
cursor.execute("query /* trailing comment */")
9194
spans_list = self.memory_exporter.get_finished_spans()
92-
self.assertEqual(len(spans_list), 3)
95+
self.assertEqual(len(spans_list), 6)
9396
self.assertEqual(spans_list[0].name, "Test")
9497
self.assertEqual(spans_list[1].name, "multi")
9598
self.assertEqual(spans_list[2].name, "tab")
99+
self.assertEqual(spans_list[3].name, "query")
100+
self.assertEqual(spans_list[4].name, "query")
101+
self.assertEqual(spans_list[5].name, "query")
96102

97103
def test_span_succeeded_with_capture_of_statement_parameters(self):
98104
connection_props = {

instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ def get_operation_name(self, cursor, args):
216216
statement = statement.as_string(cursor)
217217

218218
if isinstance(statement, str):
219-
return statement.split()[0]
219+
# Strip leading comments so we get the operation name.
220+
return self._leading_comment_remover.sub("", statement).split()[0]
220221

221222
return ""
222223

instrumentation/opentelemetry-instrumentation-psycopg2/tests/test_psycopg2_integration.py

+26
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,32 @@ def test_instrumentor(self):
116116
spans_list = self.memory_exporter.get_finished_spans()
117117
self.assertEqual(len(spans_list), 1)
118118

119+
def test_span_name(self):
120+
Psycopg2Instrumentor().instrument()
121+
122+
cnx = psycopg2.connect(database="test")
123+
124+
cursor = cnx.cursor()
125+
126+
cursor.execute("Test query", ("param1Value", False))
127+
cursor.execute(
128+
"""multi
129+
line
130+
query"""
131+
)
132+
cursor.execute("tab\tseparated query")
133+
cursor.execute("/* leading comment */ query")
134+
cursor.execute("/* leading comment */ query /* trailing comment */")
135+
cursor.execute("query /* trailing comment */")
136+
spans_list = self.memory_exporter.get_finished_spans()
137+
self.assertEqual(len(spans_list), 6)
138+
self.assertEqual(spans_list[0].name, "Test")
139+
self.assertEqual(spans_list[1].name, "multi")
140+
self.assertEqual(spans_list[2].name, "tab")
141+
self.assertEqual(spans_list[3].name, "query")
142+
self.assertEqual(spans_list[4].name, "query")
143+
self.assertEqual(spans_list[5].name, "query")
144+
119145
# pylint: disable=unused-argument
120146
def test_not_recording(self):
121147
mock_tracer = mock.Mock()

instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import os
15+
import re
1516

1617
from sqlalchemy.event import listen # pylint: disable=no-name-in-module
1718

@@ -100,6 +101,7 @@ def __init__(
100101
self.vendor = _normalize_vendor(engine.name)
101102
self.enable_commenter = enable_commenter
102103
self.commenter_options = commenter_options if commenter_options else {}
104+
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
103105

104106
listen(
105107
engine, "before_cursor_execute", self._before_cur_exec, retval=True
@@ -115,7 +117,10 @@ def _operation_name(self, db_name, statement):
115117
# use cases and uses the SQL statement in span name correctly as per the spec.
116118
# For some very special cases it might not record the correct statement if the SQL
117119
# dialect is too weird but in any case it shouldn't break anything.
118-
parts.append(statement.split()[0])
120+
# Strip leading comments so we get the operation name.
121+
parts.append(
122+
self._leading_comment_remover.sub("", statement).split()[0]
123+
)
119124
if db_name:
120125
parts.append(db_name)
121126
if not parts:

instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,27 @@ def test_trace_integration(self):
4242
)
4343
cnx = engine.connect()
4444
cnx.execute("SELECT 1 + 1;").fetchall()
45+
cnx.execute("/* leading comment */ SELECT 1 + 1;").fetchall()
46+
cnx.execute(
47+
"/* leading comment */ SELECT 1 + 1; /* trailing comment */"
48+
).fetchall()
49+
cnx.execute("SELECT 1 + 1; /* trailing comment */").fetchall()
4550
spans = self.memory_exporter.get_finished_spans()
4651

47-
self.assertEqual(len(spans), 2)
52+
self.assertEqual(len(spans), 5)
4853
# first span - the connection to the db
4954
self.assertEqual(spans[0].name, "connect")
5055
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
5156
# second span - the query itself
5257
self.assertEqual(spans[1].name, "SELECT :memory:")
5358
self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT)
59+
# spans for queries with comments
60+
self.assertEqual(spans[2].name, "SELECT :memory:")
61+
self.assertEqual(spans[2].kind, trace.SpanKind.CLIENT)
62+
self.assertEqual(spans[3].name, "SELECT :memory:")
63+
self.assertEqual(spans[3].kind, trace.SpanKind.CLIENT)
64+
self.assertEqual(spans[4].name, "SELECT :memory:")
65+
self.assertEqual(spans[4].kind, trace.SpanKind.CLIENT)
5466

5567
def test_instrument_two_engines(self):
5668
engine_1 = create_engine("sqlite:///:memory:")

tests/opentelemetry-docker-tests/tests/asyncpg/test_asyncpg_functional.py

+29
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@ def test_instrumented_fetch_method_without_arguments(self, *_, **__):
7777
spans[0].attributes[SpanAttributes.DB_STATEMENT], "SELECT 42;"
7878
)
7979

80+
def test_instrumented_remove_comments(self, *_, **__):
81+
async_call(self._connection.fetch("/* leading comment */ SELECT 42;"))
82+
async_call(
83+
self._connection.fetch(
84+
"/* leading comment */ SELECT 42; /* trailing comment */"
85+
)
86+
)
87+
async_call(self._connection.fetch("SELECT 42; /* trailing comment */"))
88+
spans = self.memory_exporter.get_finished_spans()
89+
self.assertEqual(len(spans), 3)
90+
self.check_span(spans[0])
91+
self.assertEqual(spans[0].name, "SELECT")
92+
self.assertEqual(
93+
spans[0].attributes[SpanAttributes.DB_STATEMENT],
94+
"/* leading comment */ SELECT 42;",
95+
)
96+
self.check_span(spans[1])
97+
self.assertEqual(spans[1].name, "SELECT")
98+
self.assertEqual(
99+
spans[1].attributes[SpanAttributes.DB_STATEMENT],
100+
"/* leading comment */ SELECT 42; /* trailing comment */",
101+
)
102+
self.check_span(spans[2])
103+
self.assertEqual(spans[2].name, "SELECT")
104+
self.assertEqual(
105+
spans[2].attributes[SpanAttributes.DB_STATEMENT],
106+
"SELECT 42; /* trailing comment */",
107+
)
108+
80109
def test_instrumented_transaction_method(self, *_, **__):
81110
async def _transaction_execute():
82111
async with self._connection.transaction():

0 commit comments

Comments
 (0)