Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d161ccb
feat(local): Add browser session switching
MathurAditya724 Oct 15, 2026
8124a15
feat(local): Add a searchable session picker
MathurAditya724 Oct 15, 2026
ffbcc0e
fix(local): Give receiver actions room in the sidebar
MathurAditya724 Oct 15, 2026
ae71daa
fix(local): Keep secondary receiver actions compact
MathurAditya724 Oct 15, 2026
71289e1
fix(local): Align receiver actions in one flex row
MathurAditya724 Oct 15, 2026
6abe933
feat(local): Discover sessions from receiver connections
MathurAditya724 Oct 15, 2026
df0d845
feat(local): Print hosted viewer links in command output
MathurAditya724 Oct 15, 2026
15701e2
feat(local): Add appearance controls to Settings
MathurAditya724 Oct 15, 2026
d4f5dde
fix(local): Complete receiver connection and session recovery flows
MathurAditya724 Oct 15, 2026
b7d7952
test(local): Close receiver sockets during fixture teardown
MathurAditya724 Oct 15, 2026
382d7c8
fix(local): Clarify retained envelope counts in Settings
MathurAditya724 Oct 15, 2026
f05a41b
fix(local): Polish viewer and integrate exception details
MathurAditya724 Oct 15, 2026
04aedaf
fix(local): Clear stale receiver errors while editing
MathurAditya724 Oct 15, 2026
bf766b3
fix(local): Preserve scoped sessions when retrying connections
MathurAditya724 Oct 15, 2026
515ae1e
fix(local): Retry transient daemon ownership reads
MathurAditya724 Oct 15, 2026
5f9feec
fix(local): Preserve sessions on unchanged receiver submissions
MathurAditya724 Oct 16, 2026
481b9b2
fix(local): Make capture and viewer lifecycle actions consistent
MathurAditya724 Oct 16, 2026
9531251
feat(local): Allow validated runtime retention from hosted viewers
MathurAditya724 Oct 16, 2026
ada1980
fix(local): Keep only five recent receiver connections
MathurAditya724 Oct 16, 2026
da78ed8
fix(local): Keep archived retention read-only during replay
MathurAditya724 Oct 16, 2026
716b1c0
fix(local): Restore clear recovery and preserve disconnection
MathurAditya724 Oct 16, 2026
d415547
chore(local): Merge main and resolve session viewer conflicts
MathurAditya724 Oct 26, 2026
6cac395
fix(local): Harden discovery and preserve streamed telemetry
MathurAditya724 Oct 26, 2026
484be12
test(local): Await retention request completion in viewer test
MathurAditya724 Oct 26, 2026
b45530e
feat(local): Clarify capture workflows and event inspection
MathurAditya724 Oct 26, 2026
fe714cc
test(local): Bound replay setup and scope retention queries
MathurAditya724 Oct 26, 2026
d5e5863
fix(local): Preserve receiver and inspector state across actions
MathurAditya724 Oct 26, 2026
48be2cb
feat(local): Add telemetry insights and envelope replay
MathurAditya724 Oct 26, 2026
eb6ab17
test(local): Preserve raw envelopes on Windows checkouts
MathurAditya724 Oct 26, 2026
0b924dd
fix(local): Retain AI spans identified by origin
MathurAditya724 Oct 26, 2026
771367e
test(local): Budget the full capture replay scenario
MathurAditya724 Oct 26, 2026
fbdbea3
fix(local): Keep attachments inert and preserve viewer state
MathurAditya724 Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SDK envelope item lengths count raw bytes. Preserve fixture bytes on every OS.
apps/local/fixtures/*.txt -text
95 changes: 95 additions & 0 deletions apps/local/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Sentry Local viewer

The viewer connects to a Local receiver and inspects captured SDK envelopes.
Start the receiver and UI in separate terminals from the repository root:

```sh
pnpm --filter sentry run cli local serve
pnpm --filter local dev
```

Open the URL printed by Vite and connect to `http://localhost:8969`.
The receiver can also be a named Local session or an explicitly selected remote
receiver. Capture lifecycle and retention are managed through the existing
Local controls.

## Replay example captures

With a receiver running, load all bundled examples:

```sh
pnpm --filter local sample
```

The examples cover correlated errors/logs/traces, repeated database queries,
resource loading, Web Vitals, an AI conversation and tool execution, feedback,
SDK reports, legacy and continuous profiles, standalone span-v2 telemetry,
and text/JSON/HTML/image attachments. All names and content are synthetic.
They follow the SDK envelope shapes used in Spotlight's fixture corpus.

Replay any directory of raw SDK envelopes, including Spotlight's `_fixtures`
directory, recursively:

```sh
pnpm --filter local sample /path/to/spotlight/packages/spotlight/_fixtures \
--endpoint http://localhost:8969/stream --compression gzip

pnpm --filter local sample --interval 200 --repeat 3
```

`--fixtures` is an alternative to the positional path. `--endpoint` takes
precedence over `SENTRY_SPOTLIGHT`; otherwise the default is
`http://localhost:8969/stream`. A named session requires its ingestion endpoint,
not its capability-protected viewer stream URL.

The loader sends `.txt`, `.bin`, and `.envelope` files in sorted order, preserving
raw bytes and captured timestamps. Bundled byte-counted envelopes disable Git
line-ending conversion; preserve the original bytes when checking out external
captures as well. `--compression` supports `none`, `gzip`, and
`br`; `--interval` is milliseconds between envelopes and `--repeat` is a finite
number of passes. It stops with a nonzero exit status when a request is rejected
or times out. The command does not clear existing capture history. Raise the
receiver's envelope retention limit when replaying a large corpus.

## Investigation views

- Errors include exception causes, captured source lines and local variables,
breadcrumbs, request/context data, tags, modules, VS Code links for absolute
local paths, and a copyable Markdown summary for AI assistants.
- AI inspection reads standard `gen_ai.*` and Vercel AI fields from both embedded
transaction spans and standalone spans. Prompts, responses, token usage and
tool arguments/results appear when the SDK captured them. Child AI steps stay
linked to their parent interaction.
- Queries and resources group exact operation/description pairs. Sort by count,
duration or captured resource size and open an execution in its trace.
- Web Vitals groups visits by page and shows individual captured measurements.
LCP, INP, CLS, FCP and TTFB use the [Web Vitals thresholds](https://web.dev/articles/vitals);
FID is labelled as a legacy metric. Local samples are not field percentiles.
- Profiles support Sentry sample/stack/frame payloads in legacy `profile` items
and continuous `profile_chunk` items. Select a thread, compare inclusive/self
time and samples, and explore flame, treemap or sunburst views. Observed time
uses adjacent samples on each thread; no duration is invented for the last
sample. Charts limit displayed depth/nodes for responsiveness; JSON preserves
the original payload.
- SDKs lists observed names, versions, platforms and last-seen metadata, followed
by session/client reports. Feedback links to associated errors in the capture.
- Attachments show safe text/JSON/image/video previews and downloads. Captured
HTML, SVG and code remain inert; the viewer never executes them or fetches
captured resource URLs.

Use severity/logger filters and sorting in event/log views. The Columns control
chooses which metadata appears beside each message. In a trace, search spans,
show only matches, collapse branches, or adjust the tree width.

## Tests

```sh
pnpm --filter local test
pnpm --filter local typecheck
pnpm --filter local build
```

Integration tests start an actual receiver, replay the bundled capture through
HTTP/SSE, then navigate the analysis and inspector views. Replay tests cover
binary bytes, gzip/Brotli, recursive directories, repeated passes and rejected
requests. Unknown payloads remain inspectable as JSON.
3 changes: 3 additions & 0 deletions apps/local/fixtures/01-checkout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "transaction", "length": 2554}
{"event_id":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","type":"transaction","transaction":"GET /checkout","platform":"javascript","start_timestamp":1730000000,"timestamp":1730000002,"contexts":{"trace":{"trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","span_id":"1111111111111111","op":"pageload"},"profile":{"profile_id":"ffffffffffffffffffffffffffffffff","profiler_id":"demo-profiler"}},"measurements":{"lcp":{"value":3200,"unit":"millisecond"},"inp":{"value":125,"unit":"millisecond"},"cls":{"value":0.04,"unit":"none"},"fcp":{"value":900,"unit":"millisecond"},"ttfb":{"value":180,"unit":"millisecond"}},"spans":[{"span_id":"2222222222222222","parent_span_id":"1111111111111111","op":"db.sql.query","description":"SELECT id, total FROM orders WHERE user_id = ?","start_timestamp":1730000000.1,"timestamp":1730000000.3,"data":{"db.system":"postgresql"}},{"span_id":"3333333333333333","parent_span_id":"1111111111111111","op":"db.sql.query","description":"SELECT id, total FROM orders WHERE user_id = ?","start_timestamp":1730000000.4,"timestamp":1730000000.8,"data":{"db.system":"postgresql"}},{"span_id":"4444444444444444","parent_span_id":"1111111111111111","op":"resource.script","description":"http://localhost:3000/checkout.js","start_timestamp":1730000000.01,"timestamp":1730000000.2,"data":{"http.response_content_length":20480}},{"span_id":"5555555555555555","parent_span_id":"1111111111111111","op":"resource.img","description":"http://localhost:3000/cart.png","start_timestamp":1730000000.02,"timestamp":1730000000.4,"data":{"http.response_content_length":8192}},{"span_id":"6666666666666666","parent_span_id":"1111111111111111","op":"gen_ai.chat","description":"Order assistant","start_timestamp":1730000000.8,"timestamp":1730000001.6,"data":{"gen_ai.request.model":"demo-model","gen_ai.system":"demo-provider","gen_ai.usage.input_tokens":84,"gen_ai.usage.output_tokens":26,"gen_ai.system_instructions":"Help the user find their order.","gen_ai.request.messages":"[{\"role\": \"user\", \"content\": \"Where is order 42?\"}]","gen_ai.response.text":"Order 42 ships tomorrow.","gen_ai.response.tool_calls":"[{\"id\": \"call-demo\", \"name\": \"lookup_order\", \"arguments\": {\"order_id\": 42}}]"}},{"span_id":"7777777777777777","parent_span_id":"6666666666666666","op":"gen_ai.execute_tool","description":"lookup_order","start_timestamp":1730000001,"timestamp":1730000001.2,"data":{"gen_ai.tool.name":"lookup_order","gen_ai.tool.call.id":"call-demo","gen_ai.tool.input":"{\"order_id\": 42}","gen_ai.tool.output":"{\"status\": \"shipping tomorrow\"}"}}]}
3 changes: 3 additions & 0 deletions apps/local/fixtures/02-error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "event", "length": 1131}
{"event_id":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","level":"error","platform":"javascript","timestamp":1730000001.8,"contexts":{"trace":{"trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","span_id":"2222222222222222"},"browser":{"name":"Firefox","version":"130"},"runtime":{"name":"node","version":"22"}},"request":{"method":"POST","url":"http://localhost:3000/checkout","headers":{"content-type":"application/json"}},"tags":{"environment":"local","feature":"checkout"},"extra":{"cart_items":2},"modules":{"checkout-demo":"1.0.0"},"breadcrumbs":[{"timestamp":1730000000.1,"category":"navigation","message":"Opened checkout"},{"timestamp":1730000001.6,"category":"http","message":"POST /checkout returned 500","level":"error","data":{"status_code":500}}],"exception":{"values":[{"type":"TypeError","value":"Payment method is missing","stacktrace":{"frames":[{"function":"submitOrder","abs_path":"/app/checkout.ts","filename":"checkout.ts","lineno":42,"colno":7,"context_line":"throw new TypeError(\"Payment method is missing\");","pre_context":["if (!paymentMethod) {"],"post_context":["}"],"vars":{"cartItems":2,"paymentMethod":null}}]}}]}}
3 changes: 3 additions & 0 deletions apps/local/fixtures/03-logs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "b738e253681bd7b552a2beec92fc38bf", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "log", "length": 495}
{"items":[{"timestamp":1730000000.5,"body":"Loaded two orders","level":"info","trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","span_id":"2222222222222222","attributes":{"sentry.logger.name":{"type":"string","value":"orders"},"order.count":{"type":"integer","value":2}}},{"timestamp":1730000001.8,"body":"Payment validation failed","level":"error","trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","span_id":"2222222222222222","attributes":{"sentry.logger.name":{"type":"string","value":"payments"}}}]}
3 changes: 3 additions & 0 deletions apps/local/fixtures/04-feedback.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "9dd5c245c456bb1da2b82673d5b3a9d4", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "feedback", "length": 291}
{"timestamp":1730000002.1,"contexts":{"feedback":{"message":"The checkout button did not complete my order.","name":"Example User","contact_email":"user@example.com","url":"http://localhost:3000/checkout","source":"feedback_widget","associated_event_id":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}}}
3 changes: 3 additions & 0 deletions apps/local/fixtures/05-profile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "ffffffffffffffffffffffffffffffff", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "profile", "length": 1118}
{"event_id":"ffffffffffffffffffffffffffffffff","platform":"javascript","transactions":[{"id":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],"profile":{"frames":[{"function":"main","filename":"checkout.ts","in_app":true},{"function":"submitOrder","filename":"checkout.ts","lineno":40,"in_app":true},{"function":"queryDatabase","filename":"db.ts","lineno":12,"in_app":true},{"function":"read","module":"node:fs","in_app":false}],"stacks":[[0],[1,0],[2,1,0],[3,2,1,0]],"samples":[{"elapsed_since_start_ns":"0","thread_id":"main","stack_id":0},{"elapsed_since_start_ns":"10000000","thread_id":"main","stack_id":1},{"elapsed_since_start_ns":"20000000","thread_id":"main","stack_id":2},{"elapsed_since_start_ns":"30000000","thread_id":"main","stack_id":3},{"elapsed_since_start_ns":"40000000","thread_id":"main","stack_id":2},{"elapsed_since_start_ns":"50000000","thread_id":"main","stack_id":2},{"elapsed_since_start_ns":"60000000","thread_id":"main","stack_id":1},{"elapsed_since_start_ns":"70000000","thread_id":"main","stack_id":0}],"thread_metadata":{"main":{"name":"Main thread"}}}}
Comment thread
MathurAditya724 marked this conversation as resolved.
3 changes: 3 additions & 0 deletions apps/local/fixtures/06-profile-chunk.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "57c18c15911867b6a10701947b1a9a6c", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "profile_chunk", "length": 1208}
{"chunk_id":"cccccccccccccccccccccccccccccccc","profiler_id":"demo-profiler","platform":"javascript","profile":{"frames":[{"function":"main","filename":"checkout.ts","in_app":true},{"function":"submitOrder","filename":"checkout.ts","lineno":40,"in_app":true},{"function":"queryDatabase","filename":"db.ts","lineno":12,"in_app":true},{"function":"read","module":"node:fs","in_app":false}],"stacks":[[0],[1,0],[2,1,0],[3,2,1,0]],"samples":[{"timestamp":1730000000.0,"thread_id":"main","stack_id":0},{"timestamp":1730000000.01,"thread_id":"main","stack_id":1},{"timestamp":1730000000.02,"thread_id":"main","stack_id":2},{"timestamp":1730000000.03,"thread_id":"main","stack_id":3},{"timestamp":1730000000.04,"thread_id":"main","stack_id":0},{"timestamp":1730000000.05,"thread_id":"main","stack_id":1},{"timestamp":1730000000.06,"thread_id":"main","stack_id":2},{"timestamp":1730000000.07,"thread_id":"main","stack_id":3},{"timestamp":1730000000.08,"thread_id":"main","stack_id":0},{"timestamp":1730000000.09,"thread_id":"main","stack_id":1},{"timestamp":1730000000.1,"thread_id":"main","stack_id":2},{"timestamp":1730000000.11,"thread_id":"main","stack_id":3}],"thread_metadata":{"main":{"name":"Main thread"}}}}
5 changes: 5 additions & 0 deletions apps/local/fixtures/07-sdk-reports.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"event_id": "199741d5b434c4a604635de1884cec77", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "session", "length": 100}
{"sid":"dddddddddddddddddddddddddddddddd","started":"2024-10-27T03:33:20Z","status":"ok","errors":0}
{"type": "client_report", "length": 108}
{"timestamp":1730000000,"discarded_events":[{"reason":"sample_rate","category":"transaction","quantity":3}]}
Binary file added apps/local/fixtures/08-attachments.bin
Binary file not shown.
3 changes: 3 additions & 0 deletions apps/local/fixtures/09-span-v2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id": "15e02eb45f3d4a0d7f566cf8a189f0ea", "sent_at": "2024-10-27T03:33:20Z", "sdk": {"name": "sentry.javascript.node", "version": "10.0.0"}}
{"type": "span", "length": 352}
{"version":2,"items":[{"span_id":"8888888888888888","trace_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","parent_span_id":"1111111111111111","name":"SELECT count(*) FROM orders","start_timestamp":1730000001.5,"end_timestamp":1730000001.7,"attributes":{"sentry.op":{"type":"string","value":"db.sql.query"},"db.system":{"type":"string","value":"postgresql"}}}]}
10 changes: 8 additions & 2 deletions apps/local/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
"preview": "vite preview",
"test": "vitest run",
"test:integration": "vitest run src/App.integration.test.tsx",
"typecheck": "tsc -b"
"typecheck": "tsc -b",
"sample": "tsx scripts/replay.ts"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"dependencies": {
"@hookform/resolvers": "^5.9.1",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-popover": "^1.3.1",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-tooltip": "^1.4.1",
"@shikijs/core": "^3.23.0",
"@shikijs/engine-javascript": "^3.23.0",
"@shikijs/langs": "^3.23.0",
Expand All @@ -33,10 +37,12 @@
"nuqs": "^2.10.1",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-hook-form": "^7.90.0",
"react-router": "^7.18.3",
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.3.3",
"tslib": "^2.8.1"
"tslib": "^2.8.1",
"valibot": "catalog:"
},
"devDependencies": {
"@spotlightjs/spotlight": "^4.11.7",
Expand Down
8 changes: 0 additions & 8 deletions apps/local/public/sentry-cli-light.svg

This file was deleted.

Loading
Loading