Observability: frontend error reporting — ErrorBoundary → API + traceparent + sourcemaps (#217)#232
Open
mforce wants to merge 3 commits into
Open
Observability: frontend error reporting — ErrorBoundary → API + traceparent + sourcemaps (#217)#232mforce wants to merge 3 commits into
mforce wants to merge 3 commits into
Conversation
added 3 commits
July 25, 2026 23:39
Anonymous, per-IP rate-limited (new client-errors fixed-window policy on the #143 infrastructure), 16 KB byte cap enforced by a capped manual read (the #123 logo-upload pattern), per-field truncation, one structured Error-level log line per report — no storage. FakeRemoteIpStartupFilter moves to shared test infrastructure so the new tests and the auth rate-limit tests use the same remote-IP shim.
…aceparent (#217) Every API request now carries a W3C traceparent minted client-side, so a browser action correlates with the API request log and spans (#214); the client remembers the last trace id and the ErrorBoundary attaches it to a best-effort, keepalive crash report that can never break the fallback UI. Production builds emit hidden sourcemaps so reported stacks resolve to source lines.
- Server strips control characters from the two console-rendered fields (message, route) — CR/LF or ANSI escapes from the anonymous caller could forge extra plain-text log lines; stacks stay verbatim in structured properties (codex/pi). - Client budget is now the SERIALIZED body in bytes (15 KB, shrink-by-halves): the per-field caps summed past the server's 16 KB cap, so the deepest legitimate crash reports would have been silently 413'd (codex + reviewer). - Whitespace-only messages get the placeholder before the server can 400 them (cavecrew). - The crash-report POST carries its own traceparent like every SPA request; the payload traceId still points at the crashed screen's last request (codex). - Sourcemaps leave wwwroot: the Dockerfile parks every .map under /app/sourcemaps (docker cp to resolve stacks) instead of serving full original sources at predictable public URLs; workbox's own maps (which carried sourceMappingURL comments) are no longer emitted (codex + reviewer). - New integration tests: control-char stripping, missing message, non-JSON body, body exactly at / one byte over the cap, chunked no-Content-Length oversize (the capped-read-loop path); new web tests: byte budget incl. multibyte, whitespace message, report traceparent.
Owner
Author
Review round outcome (codex + pi + 2 Claude agents)19 findings across 4 reviewers → 6 accepted (fixed in c89fc2e), 13 rejected with evidence. Several findings were duplicates across reviewers; grouped below. Accepted → fixed
Rejected — with evidence
Verification after fixes
|
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.
Closes #217 — the last slice of the observability program (#214 traces, #215 metrics, #216 handler logs).
What
API —
POST /api/v1/client-errors(anonymous, no storage)Client error ({Scope}) at {Route}: {Message}withStack,ComponentStack,AppVersion,ClientTraceIdattached as structured properties via the provider scope (Observability: structured logs at domain state transitions #216 mechanism).client-errorsfixed-window policy (default 10/300 s) on the Security: rate limiting on anonymous auth endpoints (login/refresh) #143 rate-limiting infrastructure; documented indeploy/.env.example.SPA
web/src/lib/traceparent.ts— every API request (JSON + blob paths) now carries a W3Ctraceparentminted client-side (crypto-random, all-zero ids guarded), so a browser action correlates with the API request log and spans from Observability: activate the telemetry pipeline — OTLP trace export, request logging, TraceId↔log correlation #214. The client remembers the last trace id — a failed request's id is exactly the one worth having.web/src/api/errorReport.ts— best-effort by contract: plainfetch(no bearer, no refresh churn, no idempotency key),keepalive: trueso the report survives the fallback's Reload, client-side truncation mirroring the server, and the returned promise always resolves.ErrorBoundary.componentDidCatchposts the report (message, stacks, scope,location.pathname) fire-and-forget; the fallback UI cannot be broken or delayed by it..mapfiles, nosourceMappingURLcomment) so reported minified stacks resolve to source lines; verified 115 sources with embedded content in the build output.Tests — TDD, RED verified before implementation:
ClientErrorReportTests, dedicated factory: sink tap + fake remote IP + tight limit): structured-fields log, app scope, 413 + not-logged, blank message 400, unknown scope 400, per-IP 429 + independent buckets, truncation.FakeRemoteIpStartupFilterpromoted fromRateLimitingTeststo shared infrastructure (auth rate-limit tests unchanged, still green).Acceptance criteria
web/rule)traceparent; the id becomes the request log's TraceId (Observability: activate the telemetry pipeline — OTLP trace export, request logging, TraceId↔log correlation #214'sIncoming_traceparent_becomes_the_logged_traceidcovers the server half)lib/**100 % lock holds for the new traceparent module)Verification
dotnet build: 0 warnings, 0 errorsNo GLOSSARY/Help update: nothing user-visible changed (the fallback UI is untouched; reporting is invisible to the user).