Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Breaking changes

- `opentelemetry-instrumentation-dbapi`, `opentelemetry-instrumentation-asyncpg`,
`opentelemetry-instrumentation-tortoiseorm`: Replace `db.statement.parameters`
attribute with individual `db.query.parameter.<key>` attributes following
[OTel database semantic conventions](https://opentelemetry.io/docs/specs/semconv/attributes-registry/db/).
This also affects instrumentations that delegate to dbapi (psycopg, psycopg2, aiopg, mysql, etc.).
Comment on lines +16 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to avoid breaking change and make the new attribute optional


## Version 1.40.0/0.61b0 (2026-03-04)

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from opentelemetry.sdk import resources
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
DB_USER,
Expand Down Expand Up @@ -329,8 +330,12 @@ def test_span_succeeded(self):
self.assertEqual(span.attributes[DB_NAME], "testdatabase")
self.assertEqual(span.attributes[DB_STATEMENT], "Test query")
self.assertEqual(
span.attributes["db.statement.parameters"],
"('param1Value', False)",
span.attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"'param1Value'",
)
self.assertEqual(
span.attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.1"],
"False",
)
self.assertEqual(span.attributes[DB_USER], "testuser")
self.assertEqual(span.attributes[NET_PEER_NAME], "testhost")
Expand Down
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply wanted the change to be propagated everywhere

Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ async def main():
from opentelemetry.instrumentation.asyncpg.package import _instruments
from opentelemetry.instrumentation.asyncpg.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.utils import unwrap
from opentelemetry.instrumentation.utils import (
unwrap,
)
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
DB_USER,
Expand Down Expand Up @@ -107,7 +110,10 @@ def _hydrate_span_from_args(connection, query, parameters) -> dict:
span_attributes[DB_STATEMENT] = query

if parameters is not None and len(parameters) > 0:
span_attributes["db.statement.parameters"] = str(parameters)
for idx, value in enumerate(parameters):
span_attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.{idx}"] = repr(
value
)

return span_attributes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
)
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
DB_USER,
Expand Down Expand Up @@ -231,7 +232,7 @@ def trace_integration(
user in Connection object.
tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to
use. If omitted the current configured one is used.
capture_parameters: Configure if db.statement.parameters should be captured.
capture_parameters: Configure if db.query.parameter.<key> attributes should be captured.
enable_commenter: Flag to enable/disable sqlcommenter.
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
default one is used.
Expand Down Expand Up @@ -280,7 +281,7 @@ def wrap_connect(
user in Connection object.
tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to
use. If omitted the current configured one is used.
capture_parameters: Configure if db.statement.parameters should be captured.
capture_parameters: Configure if db.query.parameter.<key> attributes should be captured.
enable_commenter: Flag to enable/disable sqlcommenter.
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
default one is used.
Expand Down Expand Up @@ -359,7 +360,7 @@ def instrument_connection(
user in a connection object.
tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to
use. If omitted the current configured one is used.
capture_parameters: Configure if db.statement.parameters should be captured.
capture_parameters: Configure if db.query.parameter.<key> attributes should be captured.
enable_commenter: Flag to enable/disable sqlcommenter.
commenter_options: Configurations for tags to be appended at the sql query.
connect_module: Module name where connect method is available.
Expand Down Expand Up @@ -701,7 +702,17 @@ def _populate_span(
span.set_attribute(attribute_key, attribute_value)

if self._db_api_integration.capture_parameters and len(args) > 1:
span.set_attribute("db.statement.parameters", str(args[1]))
params = args[1]
if isinstance(params, dict):
for key, value in params.items():
span.set_attribute(
f"{DB_QUERY_PARAMETER_TEMPLATE}.{key}", repr(value)
)
elif isinstance(params, (tuple, list)):
for idx, value in enumerate(params):
span.set_attribute(
f"{DB_QUERY_PARAMETER_TEMPLATE}.{idx}", repr(value)
)

def get_operation_name(
self, cursor: CursorT, args: tuple[Any, ...]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from opentelemetry.semconv._incubating.attributes import net_attributes
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
DB_USER,
Expand Down Expand Up @@ -75,7 +76,7 @@ def test_span_succeeded(self):
self.assertEqual(span.attributes[DB_SYSTEM], "testcomponent")
self.assertEqual(span.attributes[DB_NAME], "testdatabase")
self.assertEqual(span.attributes[DB_STATEMENT], "Test query")
self.assertFalse("db.statement.parameters" in span.attributes)
self.assertNotIn(f"{DB_QUERY_PARAMETER_TEMPLATE}.0", span.attributes)
self.assertEqual(span.attributes[DB_USER], "testuser")
self.assertEqual(span.attributes[NET_PEER_NAME], "testhost")
self.assertEqual(span.attributes[NET_PEER_PORT], 123)
Expand Down Expand Up @@ -142,8 +143,12 @@ def test_span_succeeded_with_capture_of_statement_parameters(self):
self.assertEqual(span.attributes[DB_NAME], "testdatabase")
self.assertEqual(span.attributes[DB_STATEMENT], "Test query")
self.assertEqual(
span.attributes["db.statement.parameters"],
"('param1Value', False)",
span.attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"'param1Value'",
)
self.assertEqual(
span.attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.1"],
"False",
)
self.assertEqual(span.attributes[DB_USER], "testuser")
self.assertEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import opentelemetry.instrumentation.psycopg
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
from opentelemetry.sdk import resources
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_QUERY_PARAMETER_TEMPLATE,
)
from opentelemetry.test.test_base import TestBase


Expand Down Expand Up @@ -288,7 +291,11 @@ def test_span_params_attribute(self):
assert spans_list[0].attributes is not None
self.assertEqual(spans_list[0].attributes["db.statement"], query)
self.assertEqual(
spans_list[0].attributes["db.statement.parameters"], str(params)
spans_list[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"'test'",
)
self.assertEqual(
spans_list[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.1"], "42"
)

# pylint: disable=unused-argument
Expand Down Expand Up @@ -568,7 +575,11 @@ async def test_span_params_attribute(self):
assert spans_list[0].attributes is not None
self.assertEqual(spans_list[0].attributes["db.statement"], query)
self.assertEqual(
spans_list[0].attributes["db.statement.parameters"], str(params)
spans_list[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"'test'",
)
self.assertEqual(
spans_list[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.1"], "42"
)

# pylint: disable=unused-argument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
)
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
DB_USER,
Expand Down Expand Up @@ -237,7 +238,9 @@ def _uninstrument(self, **kwargs):
tortoise.contrib.pydantic.base.PydanticListModel, "from_queryset"
)

def _hydrate_span_from_args(self, connection, query, parameters) -> dict:
def _hydrate_span_from_args( # pylint: disable=too-many-branches
self, connection, query, parameters
) -> dict:
"""Get network and database attributes from connection."""
span_attributes = {}
capabilities = getattr(connection, "capabilities", None)
Expand Down Expand Up @@ -268,7 +271,10 @@ def _hydrate_span_from_args(self, connection, query, parameters) -> dict:

if self.capture_parameters:
if parameters is not None and len(parameters) > 0:
span_attributes["db.statement.parameters"] = str(parameters)
for idx, value in enumerate(parameters):
span_attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.{idx}"] = (
repr(value)
)

return span_attributes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from opentelemetry.instrumentation.utils import suppress_instrumentation
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_NAME,
DB_QUERY_PARAMETER_TEMPLATE,
DB_STATEMENT,
DB_SYSTEM,
)
Expand All @@ -30,6 +31,7 @@
class MockModel(models.Model):
id = fields.IntField(pk=True)
name = fields.TextField()
description = fields.TextField(default="")

def __str__(self):
return self.name
Expand Down Expand Up @@ -87,15 +89,31 @@ def test_capture_parameters(self):

async def run():
await self._init_tortoise()
await MockModel.create(name="Test Parameterized")
await MockModel.create(
name="Test Capture Params", description="Multiple Params"
)

self._async_call(run())
spans = self.memory_exporter.get_finished_spans()
insert_span = next(s for s in spans if s.name == "INSERT")
self.assertIn("db.statement.parameters", insert_span.attributes)
self.assertIn(
"Test Parameterized",
insert_span.attributes["db.statement.parameters"],
self.assertEqual(
insert_span.attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"['Test Capture Params', 'Multiple Params']",
)

def test_capture_no_parameters(self):
TortoiseORMInstrumentor().uninstrument()
TortoiseORMInstrumentor().instrument(capture_parameters=True)

async def run():
await self._init_tortoise()
await MockModel.all()

self._async_call(run())
spans = self.memory_exporter.get_finished_spans()
select_span = next(s for s in spans if s.name == "SELECT")
self.assertNotIn(
f"{DB_QUERY_PARAMETER_TEMPLATE}.0", select_span.attributes
)

def test_uninstrument(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_QUERY_PARAMETER_TEMPLATE,
)
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import StatusCode
Expand Down Expand Up @@ -360,7 +363,7 @@ def test_instrumented_execute_method_with_arguments(self, *_, **__):
spans[0].attributes[SpanAttributes.DB_STATEMENT], "SELECT $1;"
)
self.assertEqual(
spans[0].attributes["db.statement.parameters"], "('1',)"
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"], "'1'"
)

def test_instrumented_fetch_method_with_arguments(self, *_, **__):
Expand All @@ -375,7 +378,7 @@ def test_instrumented_fetch_method_with_arguments(self, *_, **__):
spans[0].attributes[SpanAttributes.DB_STATEMENT], "SELECT $1;"
)
self.assertEqual(
spans[0].attributes["db.statement.parameters"], "('1',)"
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"], "'1'"
)

def test_instrumented_executemany_method_with_arguments(self, *_, **__):
Expand All @@ -389,7 +392,8 @@ def test_instrumented_executemany_method_with_arguments(self, *_, **__):
spans[0].attributes[SpanAttributes.DB_STATEMENT], "SELECT $1;"
)
self.assertEqual(
spans[0].attributes["db.statement.parameters"], "([['1'], ['2']],)"
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"],
"[['1'], ['2']]",
)

def test_instrumented_execute_interface_error_method(self, *_, **__):
Expand All @@ -404,7 +408,13 @@ def test_instrumented_execute_interface_error_method(self, *_, **__):
spans[0].attributes[SpanAttributes.DB_STATEMENT], "SELECT 42;"
)
self.assertEqual(
spans[0].attributes["db.statement.parameters"], "(1, 2, 3)"
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"], "'1'"
)
self.assertEqual(
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.1"], "2"
)
self.assertEqual(
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.2"], "3"
)

def test_instrumented_executemany_method_empty_query(self, *_, **__):
Expand All @@ -417,7 +427,7 @@ def test_instrumented_executemany_method_empty_query(self, *_, **__):
self.assertEqual(spans[0].name, POSTGRES_DB_NAME)
self.assertEqual(spans[0].attributes[SpanAttributes.DB_STATEMENT], "")
self.assertEqual(
spans[0].attributes["db.statement.parameters"], "([],)"
spans[0].attributes[f"{DB_QUERY_PARAMETER_TEMPLATE}.0"], "[]"
)

def test_instrumented_fetch_method_broken_asyncpg(self, *_, **__):
Expand Down Expand Up @@ -489,7 +499,7 @@ async def _fake_execute(*args, **kwargs):
SpanAttributes.NET_PEER_PORT: 5432,
SpanAttributes.NET_TRANSPORT: "ip_tcp",
SpanAttributes.DB_STATEMENT: "SELECT $1",
"db.statement.parameters": "('42',)",
f"{DB_QUERY_PARAMETER_TEMPLATE}.0": "'42'",
},
)
self.assertEqual(span.kind, trace.SpanKind.CLIENT)
Expand Down Expand Up @@ -523,7 +533,7 @@ async def _fake_cursor_execute(*args, **kwargs):
SpanAttributes.NET_PEER_PORT: 5432,
SpanAttributes.NET_TRANSPORT: "ip_tcp",
SpanAttributes.DB_STATEMENT: "SELECT $1",
"db.statement.parameters": "('99',)",
f"{DB_QUERY_PARAMETER_TEMPLATE}.0": "'99'",
},
)
self.assertEqual(span.kind, trace.SpanKind.CLIENT)
Expand Down