Skip to content

feat(api-server): add OpenTelemetry instrumentation for HTTP and gRPC - #141

Closed
JuanmaBM wants to merge 1 commit into
openshift-online:mainfrom
JuanmaBM:feat/api-server-otel-instrumentation
Closed

feat(api-server): add OpenTelemetry instrumentation for HTTP and gRPC#141
JuanmaBM wants to merge 1 commit into
openshift-online:mainfrom
JuanmaBM:feat/api-server-otel-instrumentation

Conversation

@JuanmaBM

Copy link
Copy Markdown
Collaborator

Summary

  • Add OTel tracing and metrics to the API server via a new plugins/otel plugin that registers HTTP middleware (otelhttp) and gRPC interceptors through the framework's pre-auth hooks
  • Instrumentation is opt-in: when OTEL_EXPORTER_OTLP_ENDPOINT is unset the plugin is a no-op with zero overhead
  • Add Jaeger all-in-one deployment for Kind clusters via KIND_JAEGER=true
  • Add spec: specs/platform/api-server-observability.spec.md

What's included

Component Change
plugins/otel/plugin.go OTel SDK init (TracerProvider + MeterProvider), HTTP middleware, gRPC unary/streaming interceptors, W3C Trace Context propagation, graceful shutdown
cmd/hypershell/main.go Side-effect import for the OTel plugin
deploy/kind/jaeger.yaml Jaeger all-in-one Deployment + Service + HTTPRoute
scripts/kind/up.sh Conditional Jaeger deployment and API server env var patch when KIND_JAEGER=true
specs/platform/api-server-observability.spec.md Full spec with 6 requirements and scenarios
specs/index.spec.md Registry entry
specs/platform/local-development.spec.md KIND_JAEGER and JAEGER_VERSION env vars

Environment Variables

Var Default Description
OTEL_EXPORTER_OTLP_ENDPOINT (unset) OTLP collector endpoint; enables instrumentation when set
OTEL_TRACES_SAMPLER_ARG 1.0 Trace sampling ratio (0.0–1.0)
OTEL_SERVICE_NAME hypershell-api-server Service name in spans/metrics
KIND_JAEGER (unset) Set to true to deploy Jaeger in Kind
JAEGER_VERSION 2.6 Jaeger image tag

Test plan

  • go build ./... and go vet ./... pass (verified)
  • Verify Jaeger deploys in Kind with KIND_JAEGER=true make kind-up
  • Send HTTP requests and confirm traces appear in Jaeger UI
  • Verify gRPC Watch streams produce spans
  • Confirm no telemetry overhead when OTEL_EXPORTER_OTLP_ENDPOINT is unset
  • Verify no sensitive data (auth headers, tokens) in span attributes

🤖 Generated with Claude Code

Add OTel tracing and metrics to the API server via a new plugin that
registers HTTP middleware and gRPC interceptors through the framework's
pre-auth hooks. Instrumentation is opt-in: when OTEL_EXPORTER_OTLP_ENDPOINT
is unset the plugin is a no-op with zero overhead.

- OTel SDK init with TracerProvider + MeterProvider (OTLP gRPC exporters)
- HTTP middleware via otelhttp with W3C Trace Context propagation
- gRPC unary + streaming interceptors with semantic convention attributes
- Graceful shutdown flushes buffered telemetry on SIGTERM
- Jaeger all-in-one manifest for Kind (KIND_JAEGER=true)
- Spec: specs/platform/api-server-observability.spec.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jsell-rh added a commit that referenced this pull request Aug 19, 2026
…ase tracing (#158)

* [HYPERSHELL-26] docs(specs): add API server observability spec

Author the desired state for OpenTelemetry instrumentation of the
HyperShell API server (HTTP + gRPC): OTel SDK bootstrap and env-driven
configuration, HTTP server spans with templated route names, gRPC server
spans, W3C cross-service trace continuation from the web-console BFF,
OTLP request metrics, telemetry privacy with operation-id correlation,
and development trace export via KIND_JAEGER.

This is the API-server counterpart to web-console/tracing.spec.md
(HYPERSHELL-27) and closes the trace chain browser -> BFF -> API. It
fixes the static-span-name gap observed in the reference draft (PR #141)
by requiring method + templated route span names, and aligns with main's
reality (Jaeger v2 on 4317, existing operation_id error envelope).

Register the spec in the Spec Registry for autonomous reconciliation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(api-server): add OpenTelemetry tracing and metrics plugin

Add an opt-in cross-cutting `otel` plugin that instruments the API
server's HTTP and gRPC surfaces, implementing HYPERSHELL-26 per
specs/platform/api-server-observability.spec.md.

- Bootstrap the OTel SDK from standard OTEL_* env vars; enabled only
  when OTEL_EXPORTER_OTLP_ENDPOINT is set. Trace and metric export go
  over OTLP/gRPC with a parent-based trace-id-ratio sampler and a W3C
  TraceContext + Baggage propagator. Setup failures degrade rather than
  crash, and a bounded shutdown flushes buffered telemetry on signal.
- HTTP: one server span per request via otelhttp (registered pre-auth so
  inbound W3C context is extracted before routing), refined in-router to
  a templated "METHOD /route" span name with http.route recorded. This
  closes the static span-name gap from the web-console tracing work.
- gRPC: pre-auth unary and stream interceptors that continue an inbound
  trace from metadata, name spans by full method, record rpc.* semconv
  attributes and status, and emit an rpc.server.duration histogram.
- Record the framework operation id on HTTP spans for trace-to-support
  correlation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dev): export API server traces to Jaeger in KIND_JAEGER dev clusters

Wire the api-server deployment's OTEL_EXPORTER_OTLP_ENDPOINT to the
in-cluster Jaeger OTLP/gRPC endpoint when KIND_JAEGER=true, mirroring the
existing BFF wiring, and unset it when Jaeger is disabled. This lets the
`otel` plugin export API server traces during local e2e verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(api-server): honor OTEL_METRICS_EXPORTER=none to disable metric export

Add opt-out support for the standard OTEL_METRICS_EXPORTER=none env var: when
set, the plugin exports traces but skips the OTLP metric exporter and meter
provider, leaving the global no-op meter (recordRPCDuration then no-ops).

A trace-only backend such as Jaeger has no OTLP metrics service and answers
metric uploads with "Unimplemented", producing a periodic upload error. The
KIND_JAEGER dev wiring now sets OTEL_METRICS_EXPORTER=none on the API server so
local trace verification is clean; production points at a full collector that
accepts both signals and leaves metrics enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(specs): document OTEL_METRICS_EXPORTER in api-server observability spec

Record the OTEL_METRICS_EXPORTER=none opt-out in the configuration env table so
the spec matches the implemented behavior for trace-only backends.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(web-console): accept same-origin browser telemetry via Fetch Metadata

The BFF CSRF hook rejected any mutating request whose Origin header did
not parse to the Host header, returning 403. Firefox sends `Origin: null`
on same-origin beacons under this app's no-referrer policy, so the browser
OTLP telemetry POST to /telemetry/v1/traces was rejected and every browser
span was dropped. That left BFF traces headless: no browser root span and
no hypershell-web-console service in the collector, even though the gateway
faithfully preserves the Host header and the browser reports the request as
same-origin.

Check the browser-set, unforgeable Sec-Fetch-Site header first: allow
same-origin and user-initiated (none) requests and reject cross-site. Fall
back to the existing Origin/Host comparison for non-browser clients that
omit Fetch Metadata. Cross-site attacks remain rejected, preserving the
CSRF protection required by WEB-AUTH-02 and WEB-TRACE-02.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(specs): add API-OBS-08 database client spans requirement

Extend the API server observability spec with a database-tracing requirement:
each query issued while handling a request emits a client span nested under the
request span, installed once as a cross-cutting GORM plugin on the base
connection (not per data-access object) so plugin queries are traced without
per-plugin changes. Span names stay bounded by operation and table; the
parameterized statement may be recorded but bound values, credentials, and
secrets are excluded per API-OBS-06. Instrumentation is opt-in on the same
collector-endpoint presence as the rest of the SDK and inherits the parent-based
sampling decision, so an unsampled or telemetry-disabled request produces no
database spans. Records the plugin-registry design decision for wiring through
the rh-trex-ai SessionFactory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(api-server): trace database queries via otelgorm (API-OBS-08)

Register the otelgorm GORM plugin through the framework's new gorm-plugin
registry (openshift-online/rh-trex-ai#51) so every database query issued while
handling a request produces a client span nested under that request's span. This
is a cross-cutting concern applied once on the base connection, transparent to
plugin authors, mirroring the HTTP middleware and gRPC interceptors.

registerDBTracing runs from the otel plugin init() after setupOTel installs the
global TracerProvider, because otelgorm captures the provider when the plugin is
constructed. WithoutQueryVariables masks bound parameter values in db.statement
so no identifier, secret, or literal is recorded (API-OBS-06); WithoutMetrics
keeps this to tracing, leaving database metrics on the framework's existing
Prometheus collector (API-OBS-05). Gated on the collector-endpoint presence like
the rest of the SDK, so with no endpoint no plugin is registered and the database
layer runs with no overhead.

Bump rh-trex-ai to the merged registry commit and refresh its dependency-age
allowlist entry accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: user <u@example.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@JuanmaBM

Copy link
Copy Markdown
Collaborator Author

Closing — superseded by #158 which was merged with a more complete implementation (database tracing, templated route span names, metrics opt-out, framework upstream changes).

@JuanmaBM JuanmaBM closed this Aug 20, 2026
@JuanmaBM

JuanmaBM commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Already implemented on #158

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.

1 participant