feat(otel): richer spans — full summary on query span, span_attributes, and dangerously_log_query_text#950
Merged
Conversation
…s, dangerously_log_query_text Implements the clickhouse-js instrumentation feedback from Langfuse (Slack thread with Steffen Schmitz): - Attach the X-ClickHouse-Summary counters to the `clickhouse.query` span too (previously only on command/exec/insert). Node now parses the summary on `connection.query`; the Web client learns to parse the summary header for query/command/exec/insert (it parsed none before). - Add `clickhouse.summary.total_rows_to_read` and `clickhouse.summary.memory_usage`, plus `real_time_microseconds` on servers that report it. Add `memory_usage` to `ClickHouseSummary`. - Add a per-request `span_attributes` bag (query/command/exec/insert/ping) for app-level context (e.g. mirroring `log_comment` tags). Caller attributes never override the client's own db.*/server.*/clickhouse.*. - Add a `dangerously_log_query_text` client option (default false). When enabled, the raw SQL is attached as OTEL `db.query.text` on spans and (Node) included in error-level request logs. Bound query_params values and credentials are never logged or traced. This guards — rather than drops — the query-text plumbing that PR #884 removes, superseding it. Adds unit tests (common tracer, node no-leak/guarded-log, web summary parsing) and updates docs/howto/tracing.md and the package CHANGELOGs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
… json() Addresses the remaining points from issue #948: - Record every key present in the X-ClickHouse-Summary header as `clickhouse.summary.<key>` instead of a hardcoded subset, so `total_rows_to_read`, `memory_usage`, and future server-side additions (e.g. `real_time_microseconds`) are picked up automatically. - Set `db.response.returned_rows` for non-streaming `json()` consumption too (JSON / JSONObjectEachRow / other single-document JSON formats), not just row-streaming paths — parity across node and web ResultSet. Adds unit tests and updates docs/CHANGELOGs (with the #948/#950 refs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enhances ClickHouse JS client OpenTelemetry instrumentation so downstream apps can rely on the client’s spans (and optional SQL text) without maintaining their own ClickHouse span wrapper.
Changes:
- Attach full
X-ClickHouse-Summarycounters to the outerclickhouse.queryspan (Node) and add header parsing on the Web client so web spans also get summary counters. - Add per-request
span_attributesto enrich spans with app-level context, with precedence rules to avoid overriding core client attributes. - Introduce
dangerously_log_query_text(defaultfalse) to optionally attach raw SQL asdb.query.texton spans and include it in Node error logs on request failure.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/client-web/src/connection/web_connection.ts | Parse X-ClickHouse-Summary header and propagate summary through query/exec/command/insert results. |
| packages/client-web/CHANGELOG.md | Document new tracing/span features for the web package. |
| packages/client-web/tests/unit/web_summary.test.ts | Add unit coverage for Web summary-header parsing behavior (present/absent/malformed). |
| packages/client-node/src/connection/node_base_connection.ts | Parse summary for query() and gate raw SQL logging behind dangerously_log_query_text. |
| packages/client-node/CHANGELOG.md | Document new tracing/span features for the node package. |
| packages/client-node/tests/unit/node_log_query_text.test.ts | Add regression tests ensuring params/credentials never log, and SQL logs only when opted in. |
| packages/client-common/src/connection.ts | Plumb dangerously_log_query_text into connection params; ensure query results carry optional summary. |
| packages/client-common/src/config.ts | Add dangerously_log_query_text config option and pass it into normalized connection params. |
| packages/client-common/src/client.ts | Merge span_attributes, attach db.query.text when enabled, and set new summary span attributes. |
| packages/client-common/src/clickhouse_types.ts | Extend ClickHouseSummary with optional memory_usage. |
| packages/client-common/tests/unit/tracing.test.ts | Add tests for summary-on-query-span, optional summary fields, query text on/off, and span_attributes behavior. |
| docs/howto/tracing.md | Document span_attributes, summary counters on spans, and the dangerously_log_query_text opt-in. |
…flag
Copilot review: a caller could set `span_attributes: { "db.query.text": ... }`
and, with `dangerously_log_query_text` disabled, the value survived onto the
span — undermining the safe-by-default guarantee. `db.query.text` is now
reserved and only ever set through the `dangerously_log_query_text` path.
Adds tests for both flag states.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CODE_OF_CONDUCT.md (added to main in #942) was not prettier-formatted, so the repo-wide `prettier --check .` in the code-quality job failed on every PR that merges with main. Reformat it (`*` list markers -> `-`) to unblock the check. Folded into this PR per maintainer request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
peter-leonov-ch
added a commit
that referenced
this pull request
Jul 16, 2026
Retarget the staged (unreleased) `# 1.23.2` CHANGELOG section to `# 1.24.0` in both client packages, ahead of cutting the 1.24.0 beta. #950 added new public API (`dangerously_log_query_text` config option + per-request `span_attributes`), which is an additive **minor** release. `1.23.2` was never published, so the section's contents — the OTEL features (#950) and the #947 `Array(Date)` binding fix — ship together as `1.24.0`. Header-only change; content is unchanged. Required by the release skill's Step 1 (the CHANGELOG must carry the exact version header before the version bump). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the clickhouse-js instrumentation feedback from Langfuse — closes #948 (Slack thread with Steffen Schmitz). The goal: make the client's OpenTelemetry spans carry enough detail that a downstream app can drop its own hand-rolled ClickHouse span wrapper.
Coverage of #948
1. Record
X-ClickHouse-Summaryon thequery()span, and record all keys.connection.query(parse_summary: true) and records it on theclickhouse.queryspan (previously onlycommand/exec/insertcarried it).X-ClickHouse-Summaryheader forquery/command/exec/insert— it parsed none before.setResponseSpanAttributesnow iterates over every key in the parsed summary (clickhouse.summary.<key>) instead of a hardcoded subset, sototal_rows_to_read,memory_usage, and future keys (real_time_microseconds, …) arrive for free.memory_usageadded toClickHouseSummary.2. Opt-in query text attribute (
db.query.text).dangerously_log_query_textclient option (defaultfalse). When enabled, the raw SQL is attached as OTELdb.query.textand (Node) included in theerror-level request-failure logs. Boundquery_paramsvalues and credentials are never logged or traced. Parameterized queries record only the statement text (with placeholders), which is exactly Langfuse's use case.3. Row counts for non-streaming result consumption.
db.response.returned_rowsis now set for non-streamingjson()consumption (JSON/JSONObjectEachRow/ other single-document JSON formats) too, not just row-streaming paths. Parity across node and webResultSet.4. A small, scoped hook for enriching span attributes.
span_attributesbag onquery/command/exec/insert/ping(the per-call option Steffen preferred). Merged into the operation span; caller attributes never override the client's owndb.*/server.*/clickhouse.*attributes.Relationship to #884
This supersedes #884. That PR drops the query-text plumbing from the Node log/transport layer; per the design discussion, this PR instead guards it behind
dangerously_log_query_text(safe-by-default — query text is absent from logs unless explicitly opted in). ThelogRequestErrorhardening from #884 (scalars only, no full params/search-params object) is preserved here, and the no-SQL-leak regression test is carried forward and extended to cover the flag-on path. #884 can be closed once this lands.Verification
db.query.texton/off, summary on the query span, all-keys recording,span_attributesmerge + precedence), node ResultSet span (returned_rowsforJSON/JSONObjectEachRow), node no-leak/guarded-log regression, web summary parsing (present/absent/malformed).npm run build,npm run typecheck,npm run lint, full Node (406) and Web (255) unit suites all pass.clickhouse.queryspan carries the full summary (incl.total_rows_to_read,memory_usage),db.query.text, and the caller'sspan_attributeswith core attributes intact; andreturned_rowsis present for non-streamingjson().Checklist
docs/howto/tracing.md)🤖 Generated with Claude Code