Skip to content

feat: replace db.statement.parameters with db.query.parameter.<key> per OTel semconv#1

Closed
Lotram wants to merge 4 commits intomainfrom
feat/db-query-parameter-semconv
Closed

feat: replace db.statement.parameters with db.query.parameter.<key> per OTel semconv#1
Lotram wants to merge 4 commits intomainfrom
feat/db-query-parameter-semconv

Conversation

@Lotram
Copy link
Copy Markdown
Owner

@Lotram Lotram commented Mar 6, 2026

Summary

Replace the non-standard db.statement.parameters span attribute with individual db.query.parameter.<key> attributes, following the OpenTelemetry database semantic conventions.

Before

A single attribute containing the stringified tuple of all parameters:

db.statement.parameters = "('param1Value', False)"

After

Individual attributes, one per parameter:

db.query.parameter.0 = "'param1Value'"
db.query.parameter.1 = "False"

For dict-style parameters (named placeholders, dbapi only):

db.query.parameter.name = "'John'"
db.query.parameter.id = "42"

Motivation

The db.statement.parameters attribute was a non-standard, implementation-specific choice. The OTel semantic conventions define db.query.parameter.<key> as individual per-parameter attributes with string values. This change aligns the instrumentation with that specification.

Design choices

Inline logic per instrumentation (no shared helper)

Each instrumentation knows the shape of its parameters:

  • dbapi: tuple/list (positional) or dict (named) per PEP 249 — needs both indexed and named key handling
  • asyncpg: always a tuple (positional *args) — simple indexed loop
  • tortoiseorm: always a tuple of lists (from args[1:]) — simple indexed loop

Since the logic is a simple for-loop in each case, it's inlined at each call site rather than abstracted into a shared utility function.

repr() instead of str() for parameter values

The old code used str(args[1]) on the entire tuple, which internally calls repr() on each element (that's how tuple.__str__ works). To preserve the same per-element representation — and to avoid ambiguity between e.g. the string "42" and the integer 42 — we use repr() on individual values. This means strings appear quoted ('param1Value') while numbers and booleans appear unquoted (42, False).

DB_QUERY_PARAMETER_TEMPLATE constant

Uses the existing DB_QUERY_PARAMETER_TEMPLATE constant from opentelemetry.semconv._incubating.attributes.db_attributes (value: "db.query.parameter") rather than hardcoding the string, following the pattern used throughout the repo.

tortoiseorm: parameters grouped under a single key

The tortoiseorm instrumentation wraps tortoise DB client methods (e.g. execute_insert(query, values)). The existing code passes args[1:] (all arguments after the query) as the parameters value. This results in all query values being grouped under db.query.parameter.0 as a single repr'd list (e.g. "['Alice', 'Engineer']"), rather than one attribute per parameter.

We chose not to change this behavior in this PR (e.g. by unwrapping args[1] instead of args[1:]) because the tortoise client methods have varying signatures across backends and operation types (execute_insert, execute_many, execute_query, execute_script), and we lack sufficient understanding of the ORM internals to safely flatten the parameters in all cases. This can be improved in a follow-up.

Changes

  1. opentelemetry-instrumentation-dbapi: Replaced db.statement.parameters with inline db.query.parameter.<key> logic in TracedCursor._populate_span; handles both positional (tuple/list) and named (dict) parameters; updated docstrings and tests (dbapi, aiopg, psycopg)
  2. opentelemetry-instrumentation-asyncpg: Replaced db.statement.parameters with inline indexed loop in _hydrate_span_from_args; updated docker functional tests
  3. opentelemetry-instrumentation-tortoiseorm: Replaced db.statement.parameters with inline indexed loop; added tests for multiple parameters and no parameters
  4. CHANGELOG: Breaking change + Added entry

Breaking change

Consumers that relied on the db.statement.parameters attribute will need to switch to reading individual db.query.parameter.<key> attributes. This affects all DB instrumentations that support capture_parameters=True.

Unlike the db.statementdb.query.text migration (which uses OTEL_SEMCONV_STABILITY_OPT_IN for a gradual transition), we don't provide a dual-emit feature flag here. db.statement.parameters was never part of the OTel semantic conventions — it was a custom, non-standard attribute. A clean break is more appropriate than a transition period that would legitimize the old format.

Related PRs

Follow-ups

  • db.statementdb.query.text: The OTel semantic conventions also deprecate db.statement in favor of db.query.text. PR #4109 handles this for dbapi and its dependents. asyncpg and tortoiseorm still need the same migration.
  • tortoiseorm parameter flattening: Unwrap args[1] instead of args[1:] to emit one attribute per parameter instead of a single grouped list (see design choices above).

@Lotram Lotram force-pushed the feat/db-query-parameter-semconv branch 7 times, most recently from b061a56 to 8157850 Compare March 6, 2026 13:16
yield


def db_query_parameter_attributes(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this should probably not be in opentelemetry/instrumentation/utils.py it's very dbapi related, here we are in the utils common to all instrumentations


if parameters is not None and len(parameters) > 0:
span_attributes["db.statement.parameters"] = str(parameters)
span_attributes.update(db_query_parameter_attributes(parameters))
Copy link
Copy Markdown

@gregoiredx gregoiredx Mar 6, 2026

Choose a reason for hiding this comment

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

I would keep it simple and

Suggested change
span_attributes.update(db_query_parameter_attributes(parameters))
span_attributes["db.statement.parameters"] = str(parameters)
for key, value in enumerate(parameters):
attrs[f"{DB_QUERY_PARAMETER_TEMPLATE}.{key}"] = repr(value)

Comment thread CHANGELOG.md
Comment on lines +16 to +20
- `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.).
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

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

@Lotram Lotram force-pushed the feat/db-query-parameter-semconv branch 2 times, most recently from c0e508e to f0e4aa6 Compare March 6, 2026 14:20
Lotram added 4 commits March 6, 2026 15:42
…<key>

Replace the single stringified db.statement.parameters attribute with
individual db.query.parameter.<key> attributes following OTel semconv.

For positional parameters (tuple/list): db.query.parameter.0, .1, etc.
For named parameters (dict): db.query.parameter.<name>.
Values use repr() to preserve type information.

This also affects all instrumentations that delegate to dbapi:
psycopg, psycopg2, aiopg, mysql, etc.

BREAKING CHANGE: db.statement.parameters span attribute replaced by
individual db.query.parameter.<key> attributes.
…r.<key>

Replace db.statement.parameters with individual db.query.parameter.<key>
attributes. asyncpg parameters are always positional (tuple from *args),
so a simple indexed loop is used.
…meter.<key>

Replace db.statement.parameters with individual db.query.parameter.<key>
attributes. Tortoise parameters are always positional (tuple from
args[1:]), so a simple indexed loop is used.
@Lotram Lotram force-pushed the feat/db-query-parameter-semconv branch from f0e4aa6 to e65a740 Compare March 6, 2026 14:45
@Lotram
Copy link
Copy Markdown
Owner Author

Lotram commented Mar 19, 2026

Closing for now, as db.operation.parameter.key is not stable yet

@Lotram Lotram closed this Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants