Descriptions
Here is semantic convention for DB operations spans https://opentelemetry.io/docs/specs/semconv/db/database-spans/
Here is what client can set in this adapter:
| Attribute |
Available? |
Where from |
| db.system.name |
always |
constant clickhouse |
| db.namespace |
always |
ClientConfigProperties.DATABASE from merged settings |
| db.query.text |
query/command only |
the sqlQuery argument; stream inserts have no user SQL |
| db.collection.name |
insert / getTableSchema only |
the tableName argument |
| db.operation.name |
insert/ping/schema only |
known by which API was called |
| db.query.summary |
no (client-v2) |
requires a SQL parser |
| server.address / server.port |
per attempt; at start only if single-endpoint |
Endpoint from nodeSelector |
| db.response.status_code |
on failure |
X-ClickHouse-Exception-Code header, read in readError |
| error.type |
on failure |
exception class or ClientFaultCause |
| db.response.returned_rows |
on success |
result_rows from X-ClickHouse-Summary |
| db.operation.batch.size |
POJO insert only |
List#size() |
| db.query.parameter. |
query only |
the queryParams map |
| network.peer.* |
no |
Apache HttpClient doesn't surface it at this layer |
| clickhouse.query_id |
usually |
assigned at Client.java:1819-1821 |
Goal
Let an application observe every client-v2 operation and every HTTP request the client makes, through a
backend-agnostic SPI, plus one ready-made OpenTelemetry implementation of it.
Constraints
- Nothing is recorded, and nothing changes, when no recorder is configured. This is the default path.
- No new runtime dependency. The OpenTelemetry API must be compile-only, so it is neither packaged with nor
transitively pulled in by client-v2, and no OpenTelemetry type may leak into any signature outside the
OpenTelemetry recorder itself. The SPI must be usable with OpenTelemetry absent from the classpath.
- Java 8, public API additions only, no existing signature changed or removed.
Contract to implement
New public package com.clickhouse.client.api.observability, installed via a new
Client.Builder.setSpanRecorder(...) (null means record nothing):
- A recorder starts a span for a query operation, for an insert operation, and for a single transport
request, and ends spans. Every started span is ended exactly once — on success, on failure, and across
retries.
- A span receives attributes. Give it a no-op default implementation so the client can record more over
time without breaking existing recorders, and provide a no-op recorder that doubles as the base class for a
recorder interested in only some operations.
- Attribute keys and span names follow the OpenTelemetry semantic conventions for database and HTTP client
spans, so a recorder can forward them to its backend verbatim. Keep those names and the operation-name
constants on the SPI side, not inside the OpenTelemetry recorder, so every recorder reports the same values.
- Nesting: each HTTP request of an operation, including each retry, is a child span of that operation's
span. Operation spans join the caller's ambient trace. Both hold only while the operation runs on the calling
thread — with async_operations the operation span starts a new trace, which is a documented limitation, not
a bug to solve here.
- The client records, at minimum: the db system, operation name, database, and query id on operation spans; the
request method, server address and port, URL, and response status on request spans; and an error attribute on
every failure path, which the OpenTelemetry recorder translates into an error span status.
- An HTTP request span cannot be named after its method until the request exists, so it must be renamed once the
method is known.
Tests
- Unit-test the OpenTelemetry recorder against an in-memory exporter: span names, kinds, attributes and their
value typing, parent/child nesting, joining and restoring an ambient context, error status, idempotent end,
and rejection of bad input. Use a @DataProvider for the attribute-typing cases.
- Add one integration test that runs a real successful query and a real failing query and asserts the resulting
span tree.
- Fix up the existing tests that construct the internal HTTP helper directly.
Docs
Update CHANGELOG.md and both the feature list and the compatibility-sensitive section of docs/features.md —
the shape of recorded spans (kinds, names, attributes, error semantics, the async caveat) is part of the
contract.
Do not copy these defects from the branch
- Two empty placeholder types in
com.clickhouse.client.api.metrics are added and threaded through
constructors without ever being called. Leave them out; they belong to unrelated metrics work.
HttpAPIClientHelper.java was CRLF on main and got rewritten as LF, turning a ~30-line change into a
~2500-line diff. Preserve existing line endings.
- The query-id attribute key disagrees between the code and
docs/features.md; pick one and make code, tests,
and docs agree.
server.port is recorded as a String and the db response status is prefixed with "Err:". Record both as
plain values of the right type.
Acceptance
mvn -pl client-v2 -am test passes and packages/clickhouse-jdbc-all still builds.
- The published
client-v2 POM has no compile-scope OpenTelemetry dependency.
- This is new public API, so per
AGENTS.md it needs an approved issue or proposal; link it, or say one is
missing.
Descriptions
Here is semantic convention for DB operations spans https://opentelemetry.io/docs/specs/semconv/db/database-spans/
Here is what client can set in this adapter:
Goal
Let an application observe every
client-v2operation and every HTTP request the client makes, through abackend-agnostic SPI, plus one ready-made OpenTelemetry implementation of it.
Constraints
transitively pulled in by
client-v2, and no OpenTelemetry type may leak into any signature outside theOpenTelemetry recorder itself. The SPI must be usable with OpenTelemetry absent from the classpath.
Contract to implement
New public package
com.clickhouse.client.api.observability, installed via a newClient.Builder.setSpanRecorder(...)(null means record nothing):request, and ends spans. Every started span is ended exactly once — on success, on failure, and across
retries.
time without breaking existing recorders, and provide a no-op recorder that doubles as the base class for a
recorder interested in only some operations.
spans, so a recorder can forward them to its backend verbatim. Keep those names and the operation-name
constants on the SPI side, not inside the OpenTelemetry recorder, so every recorder reports the same values.
span. Operation spans join the caller's ambient trace. Both hold only while the operation runs on the calling
thread — with
async_operationsthe operation span starts a new trace, which is a documented limitation, nota bug to solve here.
request method, server address and port, URL, and response status on request spans; and an error attribute on
every failure path, which the OpenTelemetry recorder translates into an error span status.
method is known.
Tests
value typing, parent/child nesting, joining and restoring an ambient context, error status, idempotent end,
and rejection of bad input. Use a
@DataProviderfor the attribute-typing cases.span tree.
Docs
Update
CHANGELOG.mdand both the feature list and the compatibility-sensitive section ofdocs/features.md—the shape of recorded spans (kinds, names, attributes, error semantics, the async caveat) is part of the
contract.
Do not copy these defects from the branch
com.clickhouse.client.api.metricsare added and threaded throughconstructors without ever being called. Leave them out; they belong to unrelated metrics work.
HttpAPIClientHelper.javawas CRLF onmainand got rewritten as LF, turning a ~30-line change into a~2500-line diff. Preserve existing line endings.
docs/features.md; pick one and make code, tests,and docs agree.
server.portis recorded as a String and the db response status is prefixed with"Err:". Record both asplain values of the right type.
Acceptance
mvn -pl client-v2 -am testpasses andpackages/clickhouse-jdbc-allstill builds.client-v2POM has no compile-scope OpenTelemetry dependency.AGENTS.mdit needs an approved issue or proposal; link it, or say one ismissing.