Skip to content

feat: add React + Sentry example#1

Open
Catalin4513 wants to merge 74 commits into
mainfrom
add-react-sentry-example
Open

feat: add React + Sentry example#1
Catalin4513 wants to merge 74 commits into
mainfrom
add-react-sentry-example

Conversation

@Catalin4513

Copy link
Copy Markdown

Adds the first example to the repo: a minimal React (Vite + TypeScript) todo app instrumented with @sentry/react, sending data to Uptrace via Uptrace's Sentry-compatible ingest endpoint.

What it shows

  • Breadcrumbs — add / toggle / delete / filter actions each leave a breadcrumb.
  • Errors — a button throws one of several error types (Error, TypeError, RangeError, TodoSyncError), raised a few frames deep so the stack trace has real app frames.
  • Messages — a button sends Sentry.captureMessage() at a random level (info / warning / error).
  • Spans / traces — a button runs run_traced_task with two child spans; browser tracing also emits page-load/navigation spans.

Wiring

The only Uptrace-specific code is a plain Sentry.init({ dsn }) in src/instrument.ts; the DSN comes from VITE_SENTRY_DSN. The README walks through copying the Sentry DSN from a project's Data Source Name page, configuring .env, running with npm run dev, and where each signal appears in the Uptrace UI.

Verified: tsc -b + vite build pass; empty and populated states render correctly in a headless Chrome check.

@Catalin4513
Catalin4513 requested a review from vmihailenco June 29, 2026 14:23
@Catalin4513

Copy link
Copy Markdown
Author

@vmihailenco, PTAL

Catalin4513 and others added 22 commits July 6, 2026 15:00
…ctor

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap to reactRouterV6BrowserTracingIntegration with routes / and /todo/:id,
enable the Logs API, and delete the old App.tsx that wrapped every action in
startNewTrace. Traces now come only from pageload and navigation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'Delivered to Uptrace ✓' read as if the user had just sent something on plain
page load. Reframe to connection health — 'Connected to Uptrace ✓' / 'Connecting
to Uptrace…' / 'Can't reach Uptrace …' — which reads correctly the instant the
pageload trace is sent and after demo actions alike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the sync_todos nested-span demo (button, syncTodos + wait helpers) and all
its doc references. The lifecycle todo.open span and the Throw test error button
already cover the span and error signals; Sync was extra surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove react-router-dom, the /todo/:id TodoDetail page, and the react-router
tracing integration; the app is a single page again. Swap back to
browserTracingIntegration (pageload trace only), make the todo text a plain span,
read the trace badge once on load, and drop the now-dead detail/btn-quiet CSS.
Docs updated to describe a single-page, pageload-only trace model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A trace id with a 'View in Uptrace' link is useless when the data never reached
Uptrace — the link would open nothing. TraceBadge now watches the delivery
status and, on a failed send, shows the trace id with 'not delivered — nothing
to view' instead of a dead link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…vered'

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The link was shown optimistically during the idle/sending window, then vanished
when the first pageload envelope came back failed. Gate it on the last *settled*
delivery result (ok/failed) instead of the live state: no link until delivery
confirms ok, and no per-action flicker from the transient sending state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Catalin4513 and others added 30 commits July 10, 2026 10:00
The suite only passed when a gitignored .env supplied VITE_SENTRY_DSN, so it
failed on a fresh clone and in CI (8 of 14 tests): Sentry.init() with no DSN
disables the SDK, which then records no spans, mints no trace ids and sends no
envelopes -- exactly what the id and envelope assertions read.

Give the Playwright dev server a dummy DSN instead of borrowing the developer's.
It is a fixture, not a credential: nothing listens at that host, envelope-reading
tests intercept the POSTs in the browser, and no test asserts delivery succeeded.
Vite prioritizes prefixed process.env vars, so the suite is now deterministic
whether .env is absent, an unfilled template, or a real DSN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
createTodo() ran inside the setTodos updater, and React double-invokes updaters
in StrictMode to surface impurity -- which this updater has plenty of: it leaves
a breadcrumb, starts and ends a span, pushes a signal and bumps an id counter. So
every Add sent its telemetry twice in dev, the mode the README tells people to
run, and the demo's own spans showed up doubled in Uptrace.

Compute the todo (and its span) first, then hand the plain value to the updater,
as done() already does with completeTodo. The existing test read only the last
signal, so it passed while two spans were sent; the new one counts them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three defects, one cause: each component reasoned about delivery on its own.

- DeliveryStatus rendered only on state === 'failed', but every send emits
  'sending' first, so the "can't reach Uptrace" warning blinked out on each
  action while delivery was still broken. TraceBadge already guarded against
  this; the guard had not been carried over.
- Inspector and the todo rows linked into Uptrace regardless of delivery, so a
  broken DSN showed "Can't reach Uptrace" next to a link offering to view the
  signal there -- one that opens nothing.
- The failure message had dropped the ingest host in a604e15, leaving host as
  dead state in every snapshot. A wrong DSN host is the usual cause, so name it:
  "Can't reach Uptrace at localhost:14318".

Extract the settled-ok/failed logic into useSettledDelivery() and read it from
all four components, rather than writing it a fourth time: the blink bug came
from exactly that copying.

The link tests now accept envelopes (a reachable Uptrace) -- the condition under
which a link is meant to appear at all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every unmatched path renders the same catch-all route, so navigating from one to
another keeps NotFound mounted and only changes its pathname -- the component is
never remounted, contrary to what the comment claimed. The boolean `reported` ref
therefore stayed true forever, and a second unknown path was silently never
reported to Uptrace.

Key the guard to the path instead of the mount. It still absorbs StrictMode's
double-invoke (the effect re-runs with the pathname unchanged) while reporting
each genuinely new unmatched path.

The test navigates client-side between two unmatched paths; using page.goto()
for the second would remount the component and hide the bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two ways the reported signal diverged from the sent one:

- sendRequest's catch path captured the exception but pushed only an http record,
  so a request that never reached the server surfaced in the Inspector as a span
  while an error was quietly on its way to Uptrace. Push the error too, as the
  non-OK path above it does, keeping the exception's real name (a failed fetch is
  a TypeError). Only /api/fail was tested, and it takes the other path.
- completeTodo paired the span id of the span it just sent with a trace id read
  from the scope. The deep link pairs the two, so they must come from the same
  span; every other signal already reads both from one spanContext(). No flow
  today makes them diverge -- the todo panel only exists on Home -- so this is a
  latent break, and signal-ids.spec.ts pins the invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
instrument.ts appended every pageload/navigation event id to an unbounded
window.recordedTransactions, and signals.ts mirrored every signal onto an
unbounded window.__signals. Both are test scaffolding, both shipped in the
production build, and neither is read by the app (the Inspector reads the store).

Guard both with import.meta.env.DEV so Vite strips them from the build; the tests
run against the dev server and still see them. instrument.ts is the file this
example holds up as the integration to copy, so it should not carry test hooks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y sends

README and AGENTS.md said the HTTP panel produces an `http.client` span on the
current trace. It produces two spans: a hand-made `GET /api/…` request span, with
the SDK's auto-instrumented `http.client` span nested inside it.

The wrapper is load-bearing, so document it rather than remove it -- it carries
parentSpan: pageRoot + forceTransaction, which keeps the request inside the page's
single-root tree (the auto span alone would be a sibling root once the pageload
span has ended, and Uptrace keeps one root per trace), and it supplies the span id
the Inspector links and the duration it shows.

The comment on sendRequest made the same wrong claim, crediting the auto span for
the one the panel does not link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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