Skip to content

Commit a241cd8

Browse files
authored
Add stream request observability
Adds request observability for streams, seeded demo traces and logs, documentation, tests, and review feedback fixes.
1 parent 37dc86b commit a241cd8

43 files changed

Lines changed: 5473 additions & 47 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/busy-onions-obey.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@prisma/studio-core": minor
3+
---
4+
5+
Add stream request observability

‎AGENTS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ These instructions apply to the `@prisma/studio-core` package.
3939
- `pnpm lint`
4040
- `pnpm test`
4141
- `pnpm test:data`
42+
- `pnpm test:data:mysql` when `STUDIO_MYSQL_TEST_URL` or local Vitess/MySQL is available
43+
- `STUDIO_INCLUDE_HEAVY_LOCAL_TESTS=1 pnpm test` only when intentionally exercising heavyweight local suites excluded from the default aggregate
4244
- `pnpm test:checkpoint`
4345
- `pnpm build`
4446
- `pnpm check:exports`

‎Architecture/navigation-url-state.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This architecture governs:
1111
- active Studio view (`table`, `schema`, `console`, `sql`, `stream`, `queries`)
1212
- active schema/table/stream
1313
- active stream follow mode
14+
- active stream request-observability sheet lookup
1415
- active stream aggregation-panel visibility
1516
- active stream aggregation range while the aggregation panel is open
1617
- pagination URL state
@@ -42,6 +43,7 @@ Only keys declared in [`ui/hooks/nuqs.ts`](../ui/hooks/nuqs.ts) are allowed:
4243
- `table`
4344
- `stream`
4445
- `streamFollow`
46+
- `streamObserve`
4547
- `aggregations`
4648
- `streamAggregationRange`
4749
- `filter`
@@ -61,6 +63,7 @@ Notes:
6163
- `pageIndex` remains URL-backed for table navigation.
6264
- `pageSize` remains a supported hash key for compatibility, but table rendering now takes its authoritative rows-per-page preference from `studioUiCollection.tablePageSize` in [`Architecture/ui-state.md`](ui-state.md).
6365
- `streamFollow` stores the active stream follow mode (`paused`, `live`, or `tail`).
66+
- `streamObserve` stores the active request-observability lookup for supported Streams profiles. Values serialize as `req:<requestId>`, `trace:<traceId>`, or `span:<spanId>`.
6467
- `aggregations` is an open-only flag for the active stream aggregation strip; when present it MUST be serialized as a bare key with no explicit value.
6568
- `streamAggregationRange` stores the active stream aggregation range, but MUST only be serialized while `aggregations` is present.
6669

@@ -81,6 +84,7 @@ Adding a new URL key requires updating `StateKey` in `nuqs.ts` first.
8184
- `queries`: no standalone default; only meaningful when the current adapter provides query insights
8285
- `stream`: no default; only meaningful when `view=stream`
8386
- `streamFollow`: no global default in `useNavigation`; the active stream view MUST resolve an absent value to `tail` and materialize that into the hash
87+
- `streamObserve`: no global default in `useNavigation`; the active stream view MUST treat an absent or malformed value as a closed request-observability sheet
8488
- `aggregations`: no global default in `useNavigation`; the active stream view MUST treat an absent flag as closed and MUST NOT materialize that closed state into the hash
8589
- `streamAggregationRange`: no standalone default; the active stream view MUST clear it whenever `aggregations` is absent, and MUST materialize its default range only after the aggregation panel is opened
8690

‎Architecture/non-standard-ui.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ It deliberately excludes:
145145
- The storage breakdowns also need collapsible ledger-style accounting boxes whose headers surface the section totals when folded shut, plus faint shared-cap annotations that sit beside right-aligned byte values and one shared cap marker spanning both Routing and Exact cache rows, which is not a stock ShadCN pattern.
146146
- No stock ShadCN pattern covers that descriptor-driven observability layout, especially when the UI must distinguish logical bytes from physical storage signals, separate search coverage from historical run indexes, hide unconfigured routing rows, and keep the remaining cost caveats explicit instead of inventing unavailable totals.
147147

148+
### Stream Request Observability Sheet
149+
150+
- Canonical components:
151+
- [`ui/studio/views/stream/StreamObserveSheet.tsx`](../ui/studio/views/stream/StreamObserveSheet.tsx)
152+
- [`ui/studio/views/stream/StreamObserveTimelineSection.tsx`](../ui/studio/views/stream/StreamObserveTimelineSection.tsx)
153+
- [`ui/studio/views/stream/StreamObserveTraceSection.tsx`](../ui/studio/views/stream/StreamObserveTraceSection.tsx)
154+
- [`ui/studio/views/stream/StreamObserveEventSection.tsx`](../ui/studio/views/stream/StreamObserveEventSection.tsx)
155+
- [`ui/hooks/use-stream-observe-request.ts`](../ui/hooks/use-stream-observe-request.ts)
156+
- Closest standard ShadCN alternatives:
157+
- `Sheet`
158+
- `ToggleGroup`
159+
- `Table`
160+
- `Badge`
161+
- `Skeleton`
162+
- Why it stays non-standard:
163+
- The request detail surface needs to combine a merged event/span timeline, a trace waterfall with proportional span bars, expandable raw span details, service-call edges, root-cause event fields, source stream labels, and coverage warnings in one compact sheet.
164+
- ShadCN provides the surrounding primitives, but no stock component models that request-correlation workflow or the proportional waterfall rows.
165+
- The section selector still uses `ToggleGroup`, the shell uses `Sheet`, and the status chips use `Badge`; only the request-specific timeline and waterfall composition remain custom.
166+
148167
### Queries Live Table And Detail Sheet
149168

150169
- Canonical component:
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Request Observability Architecture
2+
3+
This document is normative for Studio's request observability surface over Prisma Streams `evlog` and `otel-traces` streams.
4+
5+
The feature is a stream-detail drilldown, not a standalone Studio view. It lets users expand an observability event or span row, open a request detail sheet, and inspect the correlated event, trace timeline, trace waterfall, errors, service calls, and partial-result warnings returned by Prisma Streams.
6+
7+
## Scope
8+
9+
This architecture governs:
10+
11+
- detection of observability-capable stream profiles
12+
- URL-backed request lookup state
13+
- loading request correlation data from Prisma Streams
14+
- rendering the request detail sheet from a single correlation response
15+
- demo seeding for local request-observability validation
16+
17+
## Canonical Components
18+
19+
- [`ui/hooks/use-stream-observe-request.ts`](../ui/hooks/use-stream-observe-request.ts)
20+
- [`ui/studio/views/stream/StreamObserveSheet.tsx`](../ui/studio/views/stream/StreamObserveSheet.tsx)
21+
- [`ui/studio/views/stream/StreamObserveTimelineSection.tsx`](../ui/studio/views/stream/StreamObserveTimelineSection.tsx)
22+
- [`ui/studio/views/stream/StreamObserveTraceSection.tsx`](../ui/studio/views/stream/StreamObserveTraceSection.tsx)
23+
- [`ui/studio/views/stream/StreamObserveEventSection.tsx`](../ui/studio/views/stream/StreamObserveEventSection.tsx)
24+
- [`ui/studio/views/stream/StreamView.tsx`](../ui/studio/views/stream/StreamView.tsx)
25+
- [`demo/ppg-dev/seed-streams.ts`](../demo/ppg-dev/seed-streams.ts)
26+
- [`demo/ppg-dev/seed-streams-scale.ts`](../demo/ppg-dev/seed-streams-scale.ts)
27+
28+
## Non-Negotiable Rules
29+
30+
- Request observability MUST only appear for streams whose resolved profile is `evlog` or `otel-traces`.
31+
- Stream profile detection and request-pair descriptors MUST come from Streams metadata normalized by `useStreams` and `useStreamDetails`; feature code MUST NOT infer observability support from stream names.
32+
- The active lookup MUST be URL-backed through `streamObserve` and `useNavigation`; components MUST NOT write or parse `window.location.hash` directly.
33+
- `streamObserve` values MUST serialize as `req:<requestId>`, `trace:<traceId>`, or `span:<spanId>`.
34+
- Expanded event rows MAY expose the request-detail action only when the decoded event body has a usable request ID, trace ID, or span ID for the active profile.
35+
- Correlation loading MUST go through `useStreamObserveRequest`; view components MUST NOT call `/v1/observe/request` directly.
36+
- The request sheet MUST treat the Streams response as authoritative and surface `coverage.warnings` when present.
37+
- Missing counterpart streams MUST be explained in the sheet instead of rendering an empty trace or event section as complete.
38+
- The UI MUST use ShadCN primitives for the sheet, badges, buttons, skeletons, and section selector. The waterfall and timeline are custom request-observability composites and are documented in [`non-standard-ui.md`](non-standard-ui.md).
39+
40+
## API Contract
41+
42+
Studio expects the configured Streams base URL to expose:
43+
44+
- `POST {streamsUrl}/v1/observe/request`
45+
46+
The hook sends:
47+
48+
```json
49+
{
50+
"streams": {
51+
"events": "app-events",
52+
"traces": "app-traces"
53+
},
54+
"lookup": {
55+
"requestId": "req_123"
56+
},
57+
"include": {
58+
"events": true,
59+
"trace": true,
60+
"timeline": true
61+
},
62+
"limits": {
63+
"events": 50,
64+
"spans": 2000
65+
}
66+
}
67+
```
68+
69+
`lookup` contains exactly one of `requestId`, `traceId`, or `spanId`. If one counterpart stream is unavailable, Studio omits that stream and sets the matching include flag to `false`.
70+
71+
The response is normalized into:
72+
73+
- `lookup`: resolved request, trace, and span IDs
74+
- `summary`: title, method/path, service, environment, duration, status, level, and error summary fields
75+
- `evlog`: the primary event plus match count
76+
- `trace`: deduplicated spans, tree, critical path, errors, service map, and partial-state metadata
77+
- `timeline`: merged event/span timeline items
78+
- `coverage`: searched sides and warnings
79+
80+
The sheet renders three sections from that one response:
81+
82+
- `Timeline`: merged event, span-start, span-event, and exception items
83+
- `Trace`: waterfall rows, span details, errors, and service calls
84+
- `Event`: primary evlog event, root-cause fields, and raw JSON
85+
86+
## Pairing Model
87+
88+
When the active stream is `evlog`, Studio uses that stream as the event stream. Its trace counterpart MUST come from `details.observability.request.tracesStream`.
89+
When the active stream is `otel-traces`, Studio uses that stream as the trace stream. Its event counterpart MUST come from `details.observability.request.eventsStream`.
90+
91+
If the descriptor is absent, Studio may still open the sheet for the active stream side and MUST explain the missing event or trace side. Studio MUST NOT choose the first stream with the opposite profile.
92+
93+
## Demo Contract
94+
95+
`pnpm demo:ppg` seeds two local profiled streams:
96+
97+
- `app-events` with the `evlog` profile
98+
- `app-traces` with the `otel-traces` profile
99+
100+
The seed data MUST include successful requests, failed requests with root-cause fields, slow requests, event-only requests, trace-only requests, and at least one deeper multi-service trace that exercises nested service calls, repeated network spans, and downstream worker/service spans. The demo also starts a ticker that appends fresh correlated requests so `Tail` mode and request-detail refresh can be exercised locally.
101+
102+
The demo MUST create both streams with `Content-Type: application/json` before profile installation, and the installed profiles MUST declare their request-observability counterparts.
103+
104+
`pnpm demo:ppg:seed-scale -- --streams-url <url>` appends deterministic scale data to the same two streams. It MUST use the shared seed builder so local performance checks exercise the same profile shape as `pnpm demo:ppg`.
105+
106+
## Forbidden Patterns
107+
108+
- matching observability streams by hard-coded stream names in the UI
109+
- storing request-detail data in component-local state outside React Query
110+
- adding request-observability methods to the database adapter
111+
- hiding coverage warnings or partial trace state
112+
- inventing request IDs from arbitrary payload text
113+
- adding a standalone request-observability route before the stream row workflow needs one
114+
115+
## Testing Requirements
116+
117+
Request observability changes MUST include tests for:
118+
119+
- lookup param serialization and parsing
120+
- event-row lookup extraction for both `evlog` and `otel-traces`
121+
- descriptor-based counterpart stream resolution without first-profile fallback
122+
- `useStreamObserveRequest` request body, disabled state, failure state, and response normalization
123+
- sheet loading, warning, timeline, trace, event, missing-stream, and close behavior
124+
- stream-row affordance visibility and URL-backed sheet opening
125+
- demo seed shape, profiled stream creation, and scale-seed batch generation

‎Architecture/stream-event-view.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This architecture governs:
2222
- URL-backed stream follow mode selection
2323
- URL-backed stream search term state
2424
- URL-backed stream routing-key selection state
25+
- URL-backed request-observability lookup state
2526
- URL-backed aggregation-panel visibility and aggregation range selection
2627
- batched reveal of newly arrived events
2728
- transient highlighting of newly revealed event rows
@@ -34,6 +35,7 @@ This architecture governs:
3435
- [`ui/hooks/use-stream-events.ts`](../ui/hooks/use-stream-events.ts)
3536
- [`ui/hooks/use-stream-details.ts`](../ui/hooks/use-stream-details.ts)
3637
- [`ui/hooks/use-stream-aggregations.ts`](../ui/hooks/use-stream-aggregations.ts)
38+
- [`ui/hooks/use-stream-observe-request.ts`](../ui/hooks/use-stream-observe-request.ts) (`useStreamObserveRequest`)
3739
- [`ui/hooks/use-ui-state.ts`](../ui/hooks/use-ui-state.ts)
3840
- [`ui/hooks/use-navigation.tsx`](../ui/hooks/use-navigation.tsx)
3941
- [`ui/studio/views/stream/StreamView.tsx`](../ui/studio/views/stream/StreamView.tsx)
@@ -158,6 +160,9 @@ The stream view MUST treat that latest metadata count separately from `visibleEv
158160
- while the suggestion panel is open, background stream refreshes MUST NOT rewrite the suggestion content underneath the user's keyboard navigation; only explicit input changes may do that
159161
- keyboard navigation inside the suggestion panel MUST keep exactly one suggestion visually selected at a time and MUST scroll the active row into view as the highlight moves
160162
- when `useStreamDetails` exposes one or more aggregation rollups, the header MUST render a sibling icon-only aggregation toggle button with an accessible label instead of a numbered text pill
163+
- when the active stream profile is `evlog` or `otel-traces`, an expanded event row MAY render a request-detail action if the decoded event body has a request ID, trace ID, or span ID usable by that profile
164+
- clicking the request-detail action MUST write the serialized lookup into `streamObserve` through `useNavigation`; it MUST NOT keep the request sheet open state only in component-local state
165+
- when `streamObserve` contains a valid lookup for a supported profile, the stream page MUST render the request-observability sheet and resolve counterpart streams from `useStreamDetails().details.observability`
161166
- the aggregation toggle open/closed state MUST be URL-backed through `useNavigation`
162167
- that header count SHOULD fall back to the rollup-definition count from `useStreamDetails`, but once aggregate window data has loaded it MUST prefer the resolved aggregation-series count so metrics-style rollups report their real card count
163168
- the list remains bounded by `visibleEventCount` until the user reveals newer events
@@ -263,6 +268,7 @@ Stream navigation chrome MUST be URL-backed through `useNavigation` with keys su
263268

264269
- `streamFollow`
265270
- `streamRoutingKey`
271+
- `streamObserve`
266272
- `aggregations`
267273
- `streamAggregationRange`
268274
- `search`
@@ -287,6 +293,7 @@ The infinite-scroll `pageCount` and `visibleEventCount` are view-local transient
287293
- introducing stream-event URL pagination params
288294
- allowing more than one expanded row at a time
289295
- fetching aggregation rollups or aggregate windows directly inside `StreamView` without going through the dedicated hooks
296+
- fetching request-observability correlation directly inside `StreamView` without going through `useStreamObserveRequest`
290297
- deriving fake indexed fields from arbitrary payload properties
291298

292299
## Testing Requirements
@@ -306,6 +313,7 @@ Changes to this architecture MUST include tests for:
306313
- expanded-row match highlighting for stream search
307314
- aggregation-rollup request normalization in `useStreamAggregations`
308315
- stream-view aggregation toggle plus range switching, including range cleanup when the panel closes
316+
- stream-view request-observability affordance and URL-backed sheet state
309317
- infinite-scroll page growth behavior for both older history and newly revealed events
310318
- stream-view transient highlighting for newly revealed rows, including automatic clearance
311319
- stream navigation into `view=stream`

0 commit comments

Comments
 (0)