Skip to content

feat(health): normalized, actionable health-check errors (RFC #110) - #111

Draft
yesoreyeram wants to merge 5 commits into
mainfrom
claude/rfc-draft-issue-ea23tn
Draft

feat(health): normalized, actionable health-check errors (RFC #110)#111
yesoreyeram wants to merge 5 commits into
mainfrom
claude/rfc-draft-issue-ea23tn

Conversation

@yesoreyeram

Copy link
Copy Markdown
Collaborator

Draft — reference implementation for the RFC in #110. Opened for early feedback on the API shape and classification behaviour.

What this adds

A new health module (github.com/grafana/dsconfig/health) that normalizes data source CheckHealth failures (the errors behind Save & test) into a consistent, safe, machine-classified and actionable result:

  • a stable, machine-readable error code (closed taxonomy),
  • a clean, secret-free message,
  • structured, context-aware remediation, and
  • a redacted verbose detail for support.

The core depends only on grafana-plugin-sdk-go (per ADR-001). Provider knowledge (AWS/Azure/SQL/…) is injected by family libraries via RegisterClassifier / RegisterRules — the core never imports those SDKs.

Design ↔ code

RFC area Code
§6.2 taxonomy + canonical copy + default error source code.go
§6.4 explicit tags errors.go
§6.3 diagnosis dimensions diagnosis.go
§6.4/§6.4b classification pipeline (net/TLS/timeout, cancellation, priority) classify.go
§6.4a HTML interception + inconsistent-JSON envelopes httpresponse.go
§11 secret redaction + safe body summaries redact.go
§6.5 specificity-ranked remediation rules remediation.go
§6.6/§10/ADR-009 shaping → UI / logs / metrics / trace span result.go

Entry points

  • Result(ctx, err, opts...) — classify a transport/driver error.
  • ResultForResponse(ctx, resp, body, rawErr, opts...) — response-aware, so HTML/odd bodies classify correctly with sub-signals preserved.
  • OK(message) — success constructor.
  • Diagnose, ClassifyHTTPResponse, ExtractJSONError — exported building blocks.
  • WithContext / WithLogger / WithMetrics / WithSpanRecorder / WithVerbose / WithDuration — sinks are injected, keeping the core dependency-light.

Observability follows "classify once, render three ways": the safe payload is returned, while a redacted structured log line (severity keyed on error source), bounded-cardinality metrics, and a redacted span record fire only when their sinks are provided. The correlation ID is the trace ID when a sampled trace is present, else a generated fallback.

Tests — simulated scenarios

Table-driven unit tests plus httptest-backed end-to-end scenarios that stand up a live server and run a model CheckHealth through it:

  • HTML 502 gateway → UPSTREAM_ERROR
  • 200 HTML SSO login behind a proxy → AUTHENTICATION_FAILED
  • HTML 403 WAF block → PERMISSION_DENIED
  • Prometheus-style JSON error (400) → INVALID_CONFIGURATION + provider code, hint in verbose
  • healthy 200 JSON → OK
  • connection refused → HOST_UNREACHABLE
  • client deadline < server → CONNECTION_TIMEOUT
  • caller cancels mid-check → benign UNKNOWN status (not surfaced as an error)

Plus units for generic classification (DNS/TLS/timeout variants), envelope extraction (Prometheus, Elastic, JSON:API, GraphQL, OAuth), redaction (URL creds, secret kv, bearer tokens, truncation), remediation specificity, and a runnable family-registration example.

go test ./...   # ~100 cases, passes under -race

Notes for reviewers

  • Module path (/health vs a package in the existing /dsconfig module) is still an open question in the RFC — happy to relocate.
  • Span annotation is injected via SpanRecorder rather than importing OpenTelemetry directly, to keep the core's dependency to just the SDK.
  • A few RFC items remain open questions (warning/degraded state, verbose UI gating, telemetry destination) and are intentionally not implemented here.

Closes nothing yet — tracks #110.


Generated by Claude Code

claude added 2 commits June 30, 2026 03:48
Introduce a new `health` module that normalizes data source CheckHealth
failures into a consistent, safe, machine-classified and actionable result:
a stable error code, a secret-free message, context-aware remediation and a
redacted verbose detail.

The core depends only on grafana-plugin-sdk-go. Provider-specific knowledge is
injected by family libraries via RegisterClassifier / RegisterRules.

Includes:
- closed error-code taxonomy with canonical copy and default error source
- classification pipeline: explicit tags, family classifiers (priority-ranked),
  generic net/TLS/timeout inspection, cancellation handling
- response-aware handling for HTML interception and inconsistent JSON envelopes
- secret redaction and safe body summaries
- specificity-ranked remediation rules
- single classify -> render to UI / logs / metrics / trace span
- table-driven unit tests plus httptest-backed simulated scenarios
  (HTML gateway, SSO redirect, WAF block, JSON error, refused, timeout, cancel)

Reference implementation for the RFC in #110.
Add an external (health_test) suite that exercises the module exactly as a
plugin would, covering the two caller concerns:

- context propagation: a sampled trace becomes the correlation id, an unsampled
  trace falls back to a generated id, and ctx deadline/cancellation flow through
  a model CheckHealth into the classified result.
- error wrapping: tags survive deep fmt.Errorf wrapping, family classifiers
  match wrapped typed SDK errors via errors.As, wrapped sentinels classify via
  errors.Is, and secrets in wrapped driver errors never reach the message.

Also make tag selection precedence-aware: when an error tree carries multiple
tags (e.g. errors.Join of independent failures), Diagnose now picks the most
actionable code (config > TLS > auth > connection > permission) instead of
traversal order.
claude added 3 commits June 30, 2026 04:12
Add httpStatus to the JSONDetails contract so the upstream HTTP status code
reaches the UI for reference alongside errorCode and providerCode. Populated
from Diagnosis.HTTPStatus; omitted for non-HTTP failures (TLS/DNS/timeout).
Surface the diagnostic sub-signals (tlsKind, timeoutKind, bodyKind,
contentType) in JSONDetails and fold the offending config field into
remediation.fields, so the UI can render a complete structured description and
highlight the relevant fields without consulting logs. All fields are optional
and omitted when not applicable.
…rbose

Revert the top-level httpStatus/tlsKind/timeoutKind/bodyKind/contentType fields
added for self-description. The JSONDetails schema is restored to its fixed shape
(errorCode, providerCode, errorSource, correlationId, remediation, verbose); the
diagnostic metadata is now appended to the redacted verbose string instead, so
the contract never grows new keys and the existing verbose message is preserved
as the prefix. The offending field continues to surface via remediation.fields.
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 2 weeks if no further activity occurs. Please feel free to give a status update or ping for review. Thank you for your contributions!

@github-actions github-actions Bot added the stale label Jul 31, 2026
@yesoreyeram

Copy link
Copy Markdown
Collaborator Author

Work in progress

@github-actions github-actions Bot removed the stale label Aug 1, 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