Skip to content

Commit ce52ac6

Browse files
committed
docs: added a request tracing doc
1 parent 336cde6 commit ce52ac6

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

docs/request-tracing.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# Request Tracing
2+
3+
See exactly where time is spent on every request.
4+
5+
---
6+
7+
## What is Request Tracing?
8+
9+
Most web frameworks are black boxes. When a page loads slowly, you're left guessing: was it the database query? The HTML rendering? Some middleware you forgot about? You end up sprinkling `console.log` timers everywhere trying to narrow it down.
10+
11+
Pyra takes a different approach. Every request that hits your server gets a detailed receipt — a breakdown of exactly where time was spent. You can see which route matched, which middleware ran, how long your data loading took, how long rendering took, and how long it took to inject assets into the final HTML.
12+
13+
This is not a plugin or a third-party add-on. Request tracing is built into Pyra from day one. In development, it's always on. In production, you choose whether and how to enable it.
14+
15+
Think of it like an itemized bill at a restaurant. Instead of just seeing "dinner: $85," you see the price of each dish. Instead of just seeing "request: 120ms," you see the cost of each step in the pipeline.
16+
17+
---
18+
19+
## How It Works
20+
21+
Every time a request arrives at your Pyra server, a `RequestTracer` is created behind the scenes. It gets a unique ID (like `req-1`, `req-2`, and so on) and immediately starts recording.
22+
23+
As the request moves through each stage of the pipeline, the tracer logs the start and end of that stage:
24+
25+
1. **route-match** — finding which route matches the incoming URL
26+
2. **middleware** — running middleware functions (each gets its own named stage)
27+
3. **load** — executing the route's `load()` function (fetching data)
28+
4. **render** — turning the component tree into HTML on the server
29+
5. **inject-assets** — adding script tags, stylesheets, and hydration code to the HTML
30+
6. **handler** — for API routes, executing the method handler (`GET`, `POST`, etc.)
31+
7. **static** — for static file requests, reading from disk
32+
33+
When the response is sent, the tracer finalizes with the HTTP status code and the completed trace is stored in memory.
34+
35+
---
36+
37+
## What You See in Development
38+
39+
In development mode, every request is traced automatically. You don't need to configure anything.
40+
41+
Your terminal shows a compact log line for each request with a timing breakdown:
42+
43+
```
44+
GET /blog/hello-world 200 12.3ms (route-match:0.1ms middleware:2.1ms load:5.4ms render:3.8ms inject-assets:0.9ms)
45+
```
46+
47+
At a glance, you can see that the request returned a `200` in 12.3 milliseconds total, and how much time each stage contributed.
48+
49+
For a more detailed view, Pyra outputs a tree-style log with bottleneck highlighting:
50+
51+
```
52+
GET /blog/hello-world 200 12.3ms
53+
├─ route-match 0.1ms
54+
├─ middleware 2.1ms src/routes/middleware.ts
55+
├─ load 5.4ms
56+
├─ render 3.8ms
57+
└─ inject-assets 0.9ms
58+
```
59+
60+
The highlighting rules are simple:
61+
62+
- A stage taking **more than 50%** of the total time is highlighted in **yellow** — it's a warning
63+
- A stage taking **more than 80%** of the total time is highlighted in **red** — it's a bottleneck
64+
- Status codes are color-coded: 2xx green, 3xx cyan, 4xx yellow, 5xx red
65+
66+
If a stage errors out, the error message is displayed inline so you can see exactly which step failed.
67+
68+
---
69+
70+
## Server-Timing Headers
71+
72+
Pyra adds a `Server-Timing` header to every traced response. This is a W3C standard that browsers understand natively.
73+
74+
What does that mean for you? Open Chrome DevTools, click a request in the Network tab, and look at the Timing section. You'll see Pyra's pipeline stages listed right there — route matching, middleware, loading, rendering — with their durations. No browser extensions, no extra setup.
75+
76+
The header looks like this:
77+
78+
```
79+
Server-Timing: route-match;dur=0.1, middleware;dur=2.1;desc="src/routes/middleware.ts", load;dur=5.4, render;dur=3.8, inject-assets;dur=0.9
80+
```
81+
82+
Each stage is listed with its duration in milliseconds and an optional description (like the middleware file path) for context.
83+
84+
---
85+
86+
## The Dev Dashboard
87+
88+
During development, Pyra serves a built-in dashboard at `/_pyra` in your browser. This gives you a visual overview of recent request traces and route performance.
89+
90+
The dashboard is backed by JSON API endpoints you can also query directly:
91+
92+
| Endpoint | What it returns |
93+
|----------|----------------|
94+
| `/_pyra/api/traces` | List of recent request traces |
95+
| `/_pyra/api/traces/stats` | Aggregate performance stats per route (avg, p50, p95, p99) |
96+
| `/_pyra/api/traces/:id` | Full detail for a single trace by ID (e.g., `req-42`) |
97+
98+
These return JSON, so you can also use them from `curl` or scripts if you prefer working outside the browser:
99+
100+
```bash
101+
curl http://localhost:3000/_pyra/api/traces/stats
102+
```
103+
104+
---
105+
106+
## Route Statistics
107+
108+
Pyra doesn't just show individual traces — it aggregates them to give you a picture of how each route performs over time.
109+
110+
For every route, Pyra tracks:
111+
112+
| Metric | What it means |
113+
|--------|--------------|
114+
| **count** | How many requests this route has handled |
115+
| **avgMs** | The average response time |
116+
| **p50** | The median — half of requests were faster, half slower |
117+
| **p95** | 95% of requests were faster than this. Useful for catching occasional spikes. |
118+
| **p99** | 99% of requests were faster than this. If this is high, some users are having a bad time. |
119+
| **lastMs** | The response time of the most recent request |
120+
121+
These stats are a great way to find routes that need optimization without profiling every individual request. A route with a low average but a high p95 means it's usually fast but sometimes spikes — worth investigating.
122+
123+
---
124+
125+
## Tracing in Production
126+
127+
By default, tracing is **completely off** in production. No performance overhead, no trace data collected.
128+
129+
You can change this in your `pyra.config.ts`:
130+
131+
```ts
132+
import { defineConfig } from 'pyrajs-shared';
133+
134+
export default defineConfig({
135+
trace: {
136+
// 'off' — no tracing (default)
137+
// 'header' — only trace when X-Pyra-Trace: 1 header is present
138+
// 'on' — trace every request
139+
production: 'header',
140+
141+
// How many traces to keep in memory (ring buffer)
142+
bufferSize: 200,
143+
},
144+
});
145+
```
146+
147+
### `'off'` (default)
148+
149+
No tracing happens. The right choice if you don't need production tracing or rely on external monitoring tools.
150+
151+
### `'header'` (recommended for debugging)
152+
153+
Tracing only kicks in when a request includes the `X-Pyra-Trace: 1` header. This is the sweet spot: zero overhead on normal traffic, but you can selectively trace specific requests when debugging.
154+
155+
```bash
156+
curl -H "X-Pyra-Trace: 1" https://myapp.com/blog/hello-world
157+
```
158+
159+
The response includes the `Server-Timing` header with a full stage-by-stage breakdown. Check it in your terminal or in DevTools.
160+
161+
### `'on'`
162+
163+
Every request is traced, just like development. Useful for staging environments or internal tools where you want full visibility. Be mindful that it adds a small amount of overhead and memory usage.
164+
165+
### Buffer size
166+
167+
Traces are stored in a ring buffer in memory. When the buffer fills up, the oldest traces are dropped. The default is 200 traces.
168+
169+
```ts
170+
trace: {
171+
bufferSize: 500, // Keep more history
172+
}
173+
```
174+
175+
Each trace is a small object, but storing thousands will use more memory. For most apps, 200 is plenty.
176+
177+
---
178+
179+
## Why This Matters
180+
181+
When your page is slow, Pyra tells you exactly why:
182+
183+
- **`load` is the bottleneck?** Your data fetching is slow — optimize database queries or API calls.
184+
- **`render` is the bottleneck?** Your component tree is too large or doing expensive work during render.
185+
- **`middleware` is the bottleneck?** One of your middleware functions is doing heavy processing — check the stage names to find which one.
186+
- **`route-match` is slow?** You likely have a very large number of routes (this is rare).
187+
188+
No guessing. No adding timing code manually. No installing third-party APM services just to understand where your server spends its time. The information is always there in development, and available on demand in production.
189+
190+
---
191+
192+
## Tips
193+
194+
- **Watch the terminal during development.** The per-request log lines quickly reveal if a route is slower than expected.
195+
- **Use the `/_pyra` dashboard** for a visual overview when you want to compare routes side by side.
196+
- **Set `production: 'header'`** so you can debug specific production requests without tracing everything.
197+
- **Check p95 and p99 in route stats.** A fast average with a high p95 means most users are fine, but some are hitting a slow path.
198+
- **If `load` is consistently slow**, focus on your `load()` function — caching, query optimization, or reducing data volume will help.
199+
- **If `render` is consistently slow**, simplify your component tree or move computation into `load()` so it happens before rendering.
200+
- **Traces are lightweight.** In development, let them run and glance at the output as you work.

0 commit comments

Comments
 (0)