|
| 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