Skip to content

feat: opt-in OpenTelemetry tracing#28

Merged
rahulcrl merged 1 commit into
mainfrom
add_otel_tracing
Jul 7, 2026
Merged

feat: opt-in OpenTelemetry tracing#28
rahulcrl merged 1 commit into
mainfrom
add_otel_tracing

Conversation

@rahulcrl

@rahulcrl rahulcrl commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Opt-in OpenTelemetry tracing. Off by default. Enable via CRDB_MCP_OTEL_FILE or OTEL_EXPORTER_OTLP_ENDPOINT (Datadog Agent, OTel Collector).

What it produces

  • Root span per MCP tool call (middleware.ToolCallSpan).
  • Child span per SQL statement with db.system.name, db.query.text,
    db.operation.name, db.rows_affected / db.response.returned_rows.
  • db.query.text is redacted via cockroachdb-parser
    (FmtAnonymize + FmtHideConstants), same pattern as managed-service's
    pkg/mcpserver/handler.go. No PII in spans.
  • Zap records also mirror to the OTel log pipeline via otelzap.
    Trace-context propagation onto log records is a follow-up.

Follow up

Test plan

  • go build, go vet, go test ./... clean
  • Redaction verified end-to-end: PII literal sent via select_query
    landed in Jaeger as _
  • All 13 tools exercised against a live cluster; parent/child span
    structure visible in Jaeger

Comment thread otel/otel.go
Comment thread tools/tools.go Outdated
Comment thread otel/otel_test.go Outdated
@rahulcrl
rahulcrl marked this pull request as draft June 26, 2026 14:30
@rahulcrl
rahulcrl force-pushed the add_structured_logging branch 4 times, most recently from bc1a0fb to 83c87b3 Compare June 29, 2026 12:53
@rahulcrl
rahulcrl force-pushed the add_structured_logging branch from 83c87b3 to 45945bb Compare June 30, 2026 13:44
Base automatically changed from add_structured_logging to main June 30, 2026 13:53
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch 5 times, most recently from 4203c6b to 6cd0838 Compare July 1, 2026 13:26
@rahulcrl
rahulcrl marked this pull request as ready for review July 1, 2026 13:29
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 6cd0838 to bad0cce Compare July 1, 2026 13:31
@rahulcrl rahulcrl changed the title feat: opt-in OpenTelemetry tracing and log export feat: opt-in OpenTelemetry tracing Jul 1, 2026
Comment thread otel/otel.go Outdated
Comment thread db/adapter.go
Comment thread middleware/otel.go Outdated
Comment thread db/adapter.go Outdated
Comment thread db/adapter.go Outdated
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 275cb51 to 09d9023 Compare July 1, 2026 14:10
Comment thread db/adapter.go Outdated
Comment thread middleware/otel.go Outdated
Comment thread main.go Outdated
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 09d9023 to 861d745 Compare July 2, 2026 05:30
Comment thread middleware/otel.go Outdated

@arjunmahishi arjunmahishi left a comment

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 is an AI assisted code review.

Overall this is a well-scoped, high-quality PR: opt-in with zero cost when off, serious PII handling (SQL redaction plus pgx-error scrubbing), and it follows the idiomatic OTel-Go scaffolding (Resource/provider/shutdown, DB semconv, correct SpanKinds). Two non-blocking nits below.

Comment thread otel/otel.go Outdated
"go.uber.org/zap/zapcore"
)

const tracerName = "cockroachdb-mcp-server"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit (non-blocking): The tracer scope name here duplicates service.name:

const tracerName = "cockroachdb-mcp-server"
func Tracer() trace.Tracer { return otelapi.Tracer(tracerName) }

The name passed to otel.Tracer() sets the span's instrumentation scope (otel.scope.name), which is meant to identify what code produced the span — deliberately distinct from service.name (already set on the Resource to cockroachdb-mcp-server). Convention is to use the Go import path, e.g. github.com/cockroachdb/cockroachdb-mcp-server/otel.

As-is, scope and service name are identical, so every span carries the same scope and we lose the ability to filter/group spans by originating component. Since spans are actually created from two packages (middleware for the tool span, db for SQL spans), the most idiomatic fix would be two package-named tracers rather than one shared service-named one — so tool spans and SQL spans get distinct scopes. Fine to punt, but flagging so we match the OTel idiom and keep scope useful as a grouping dimension.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Added the shared service-named Tracer() helper; middleware and db now pass their Go import paths as the scope, so tool spans and SQL spans carry distinct otel.scope.name values and scope stays useful as a grouping dimension.

Comment thread middleware/otel.go

// ToolCallSpan wraps every tools/call in an OTel span named after the tool.
// No-op until otel.Setup installs an exporter.
func ToolCallSpan(next mcp.MethodHandler) mcp.MethodHandler {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit / follow-up (non-blocking): The existing ToolCallLogger already attaches duration_ms to the tool-call log, which is fine and worth keeping. But it'd be valuable to also emit tool-call latency as an OTel histogram metric, so MCP performance can be measured in the standard way — percentile/rate dashboards and alerts straight off the metrics pipeline, without scraping logs or reconstructing from spans.

This is the one signal the PR doesn't cover (traces + logs, no metrics). Concretely: a float64 histogram (e.g. mcp.tool.call.duration, unit s) recorded in the tool-call middleware, keyed by tool name and outcome. It also sidesteps the duration_ms sub-millisecond truncation-to-0 issue for free. Doesn't need to land in this PR — happy for it to be a follow-up like the trace-context work.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Logging on every tool call is too verbose for some operators, and when shipping to an obs platform they may drop tool-call logs entirely or set a low sample rate. A histogram metric is the one signal whose fidelity doesn't degrade under that — it lets them measure MCP performance (p50/p95/p99, throughput, error rate) in a standard way even with logs sampled or off. That's why I'd treat this as genuinely useful rather than just a nit, though still fine as a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed on adding the metrics.
Added a stacked PR: 38, which adds mcp.tool.call.duration (unit s), recorded in the tool-call middleware and keyed by tool name and outcome (success, error, tool_error).
Kept it out of this PR to avoid growing the diff further.

@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 861d745 to 2312d84 Compare July 6, 2026 12:46
Comment thread otel/otel.go
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 2312d84 to 3dee420 Compare July 6, 2026 12:53
Comment thread middleware/otel.go
Comment thread otel/otel_test.go
Off by default. Opt in via CRDB_MCP_OTEL_FILE (JSON spans to a file) or
OTEL_EXPORTER_OTLP_ENDPOINT (OTLP gRPC to any collector, honoring
standard OTEL_* env vars). Neither set: providers stay no-op.

A receiving middleware wraps each MCP tools/call in a span named after
the tool. db/adapter hand-instruments Query and Exec with SQL child
spans; db.query.text is redacted via cockroachdb-parser
(FmtAnonymize + FmtHideConstants), matching managed-service, so no user
data lands in span attributes. Zap records also mirror to the OTel log
pipeline via the otelzap bridge; trace-context propagation onto log
records is a follow-up.
@rahulcrl
rahulcrl force-pushed the add_otel_tracing branch from 2a9532e to e3e5e0c Compare July 6, 2026 13:23
@rahulcrl
rahulcrl requested a review from arjunmahishi July 6, 2026 13:24
@rahulcrl

rahulcrl commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @arjunmahishi!

@rahulcrl
rahulcrl merged commit 8f32877 into main Jul 7, 2026
5 checks passed
@rahulcrl
rahulcrl deleted the add_otel_tracing branch July 7, 2026 05:40
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.

3 participants