diff --git a/.changelog/4943.fixed b/.changelog/4943.fixed new file mode 100644 index 0000000000..9f5cf38c26 --- /dev/null +++ b/.changelog/4943.fixed @@ -0,0 +1 @@ +`opentelemetry-instrumentation-sqlalchemy`: avoid `IndexError` in `_operation_name` for comment-only or whitespace-only statements diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py index 3185422b47..67ce599071 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -315,8 +315,12 @@ def _operation_name(self, db_name, statement): # use cases and uses the SQL statement in span name correctly as per the spec. # For some very special cases it might not record the correct statement if the SQL # dialect is too weird but in any case it shouldn't break anything. - # Strip leading comments so we get the operation name. - parts.append(self._leading_comment_remover.sub("", statement).split()[0]) + # Strip leading comments so we get the operation name. A statement that + # is truthy but has no tokens left (comment-only or whitespace-only) must + # not raise IndexError; skip adding an operation part in that case. + tokens = self._leading_comment_remover.sub("", statement).split() + if tokens: + parts.append(tokens[0]) if db_name: parts.append(db_name) if not parts: diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 023df92101..969750e001 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -59,6 +59,20 @@ def tearDown(self): super().tearDown() SQLAlchemyInstrumentor().uninstrument() + def test_operation_name_comment_or_whitespace_only(self): + # Regression: a comment-only or whitespace-only statement is truthy but has + # no tokens after leading-comment stripping; _operation_name must not raise + # IndexError and should fall back to the db name / vendor. + engine = create_engine("sqlite:///:memory:") + tracer = self.tracer_provider.get_tracer(__name__) + engine_tracer = EngineTracer(tracer, engine, mock.Mock()) + self.assertEqual( + engine_tracer._operation_name("mydb", "/* comment only */"), "mydb" + ) + self.assertEqual( + engine_tracer._operation_name(None, " "), engine_tracer.vendor + ) + def test_trace_integration(self): engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument(