@ereo/trace 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +447 -0
- package/dist/cli-reporter.d.ts +37 -0
- package/dist/cli-reporter.d.ts.map +1 -0
- package/dist/client.d.ts +59 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +193 -0
- package/dist/collector.d.ts +29 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1367 -0
- package/dist/instrumentors/auth.d.ts +15 -0
- package/dist/instrumentors/auth.d.ts.map +1 -0
- package/dist/instrumentors/build.d.ts +18 -0
- package/dist/instrumentors/build.d.ts.map +1 -0
- package/dist/instrumentors/data.d.ts +31 -0
- package/dist/instrumentors/data.d.ts.map +1 -0
- package/dist/instrumentors/database.d.ts +26 -0
- package/dist/instrumentors/database.d.ts.map +1 -0
- package/dist/instrumentors/errors.d.ts +17 -0
- package/dist/instrumentors/errors.d.ts.map +1 -0
- package/dist/instrumentors/forms.d.ts +22 -0
- package/dist/instrumentors/forms.d.ts.map +1 -0
- package/dist/instrumentors/index.d.ts +15 -0
- package/dist/instrumentors/index.d.ts.map +1 -0
- package/dist/instrumentors/islands.d.ts +19 -0
- package/dist/instrumentors/islands.d.ts.map +1 -0
- package/dist/instrumentors/request.d.ts +24 -0
- package/dist/instrumentors/request.d.ts.map +1 -0
- package/dist/instrumentors/routing.d.ts +24 -0
- package/dist/instrumentors/routing.d.ts.map +1 -0
- package/dist/instrumentors/rpc.d.ts +16 -0
- package/dist/instrumentors/rpc.d.ts.map +1 -0
- package/dist/instrumentors/signals.d.ts +21 -0
- package/dist/instrumentors/signals.d.ts.map +1 -0
- package/dist/noop.d.ts +24 -0
- package/dist/noop.d.ts.map +1 -0
- package/dist/noop.js +45 -0
- package/dist/ring-buffer.d.ts +27 -0
- package/dist/ring-buffer.d.ts.map +1 -0
- package/dist/span.d.ts +45 -0
- package/dist/span.d.ts.map +1 -0
- package/dist/tracer.d.ts +42 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/transport.d.ts +69 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/viewer.d.ts +20 -0
- package/dist/viewer.d.ts.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# @ereo/trace
|
|
2
|
+
|
|
3
|
+
Full-stack developer observability for the EreoJS framework. Traces requests across all 11 framework layers with zero-config instrumentation, a CLI reporter, standalone viewer, and client-side span correlation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @ereo/trace
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createTracer, traceMiddleware, createCLIReporter } from '@ereo/trace';
|
|
15
|
+
|
|
16
|
+
// 1. Create a tracer
|
|
17
|
+
const tracer = createTracer();
|
|
18
|
+
|
|
19
|
+
// 2. Add trace middleware (must be first)
|
|
20
|
+
server.use(traceMiddleware(tracer));
|
|
21
|
+
|
|
22
|
+
// 3. See traces in your terminal
|
|
23
|
+
createCLIReporter(tracer);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Terminal output:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
GET /api/users 200 45.2ms
|
|
30
|
+
|-- routing 1.2ms matched /api/users
|
|
31
|
+
|-- auth 3.1ms jwt -> ok
|
|
32
|
+
|-- data 38.4ms
|
|
33
|
+
| |-- user 12.1ms db
|
|
34
|
+
| `-- posts 18.3ms db
|
|
35
|
+
`-- render 2.5ms
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
- **11 Framework Layers** - Request, routing, data, forms, signals, RPC, database, auth, islands, build, errors
|
|
41
|
+
- **Zero-Config Middleware** - Single middleware instruments the full request lifecycle
|
|
42
|
+
- **CLI Reporter** - Color-coded tree output with duration highlighting
|
|
43
|
+
- **Standalone Viewer** - Self-contained HTML waterfall chart at `/__ereo/traces`
|
|
44
|
+
- **Client Correlation** - Browser spans merge into server traces via WebSocket
|
|
45
|
+
- **Production No-Op** - 592B tree-shakeable import drops all tracing code
|
|
46
|
+
- **Live Streaming** - Real-time trace events via WebSocket to DevTools and CLI
|
|
47
|
+
|
|
48
|
+
## Concepts
|
|
49
|
+
|
|
50
|
+
A **trace** represents one complete operation (usually an HTTP request). Each trace contains **spans** — timed segments of work organized by **layer** (routing, data, auth, etc.). Spans form a tree: the root span wraps the request, child spans represent sub-operations.
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Trace: GET /api/users
|
|
54
|
+
├── Span: request (root)
|
|
55
|
+
│ ├── Span: routing (child)
|
|
56
|
+
│ ├── Span: auth (child)
|
|
57
|
+
│ └── Span: data (child)
|
|
58
|
+
│ ├── Span: db.query (grandchild)
|
|
59
|
+
│ └── Span: db.query (grandchild)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Trace Middleware
|
|
63
|
+
|
|
64
|
+
The request middleware creates the root trace span and propagates context to all downstream instrumentors. It must be the **first** middleware in your stack.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { traceMiddleware } from '@ereo/trace';
|
|
68
|
+
|
|
69
|
+
server.use(traceMiddleware(tracer, {
|
|
70
|
+
// Paths to skip (defaults shown)
|
|
71
|
+
exclude: ['/_ereo/', '/__ereo/', '/favicon.ico'],
|
|
72
|
+
// Record request headers (default: false)
|
|
73
|
+
recordHeaders: false,
|
|
74
|
+
}));
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The middleware:
|
|
78
|
+
- Creates a root span with method, pathname, and status code
|
|
79
|
+
- Attaches the tracer and active span to request context
|
|
80
|
+
- Reads/writes `X-Ereo-Trace-Id` headers for client correlation
|
|
81
|
+
- Records errors and sets status to 500 on uncaught exceptions
|
|
82
|
+
|
|
83
|
+
## Instrumentors
|
|
84
|
+
|
|
85
|
+
Every instrumentor takes a parent span and creates either a child span (for timed operations) or an event (for lightweight annotations).
|
|
86
|
+
|
|
87
|
+
### Routing (Layer 2)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { traceRouteMatch, recordRouteMatch } from '@ereo/trace';
|
|
91
|
+
|
|
92
|
+
// Option A: Wrap route matching (creates child span)
|
|
93
|
+
const match = traceRouteMatch(rootSpan, () => {
|
|
94
|
+
return router.match(pathname);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Option B: Record match as event on existing span (lighter)
|
|
98
|
+
recordRouteMatch(rootSpan, match);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Data / Loaders (Layer 3)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { traceLoader, recordLoaderMetrics, traceCacheOperation } from '@ereo/trace';
|
|
105
|
+
|
|
106
|
+
// Trace a single loader
|
|
107
|
+
const users = await traceLoader(rootSpan, 'users', async () => {
|
|
108
|
+
return db.user.findMany();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Record metrics from pipeline execution
|
|
112
|
+
recordLoaderMetrics(rootSpan, [
|
|
113
|
+
{ key: 'user', duration: 12.1, cacheHit: false, source: 'db' },
|
|
114
|
+
{ key: 'posts', duration: 18.3, cacheHit: true },
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
// Record cache events
|
|
118
|
+
traceCacheOperation(rootSpan, 'get', 'user:123', true);
|
|
119
|
+
traceCacheOperation(rootSpan, 'set', 'user:123');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Forms (Layer 4)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { traceFormSubmit, recordFormValidation } from '@ereo/trace';
|
|
126
|
+
|
|
127
|
+
// Trace form submission
|
|
128
|
+
const result = await traceFormSubmit(rootSpan, 'checkout', async () => {
|
|
129
|
+
return processOrder(formData);
|
|
130
|
+
}, { fieldCount: 8 });
|
|
131
|
+
|
|
132
|
+
// Record validation timing
|
|
133
|
+
recordFormValidation(rootSpan, 'checkout', {
|
|
134
|
+
errorCount: 2,
|
|
135
|
+
validationMs: 5.3,
|
|
136
|
+
errorSources: ['sync', 'schema'],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Signals (Layer 5)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { recordSignalUpdate, recordSignalBatch } from '@ereo/trace';
|
|
144
|
+
|
|
145
|
+
// Record individual signal update
|
|
146
|
+
recordSignalUpdate(span, 'count', { subscriberCount: 3, batched: false });
|
|
147
|
+
|
|
148
|
+
// Record batched updates
|
|
149
|
+
recordSignalBatch(span, ['count', 'total', 'items'], { subscriberCount: 8 });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### RPC (Layer 6)
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { traceRPCCall, recordRPCValidation } from '@ereo/trace';
|
|
156
|
+
|
|
157
|
+
// Trace an RPC procedure call
|
|
158
|
+
const result = await traceRPCCall(rootSpan, 'users.list', 'query', async () => {
|
|
159
|
+
return db.user.findMany();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Record input validation timing
|
|
163
|
+
recordRPCValidation(rootSpan, 'users.create', 1.5, true);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Database (Layer 7)
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { tracedAdapter, traceQuery } from '@ereo/trace';
|
|
170
|
+
|
|
171
|
+
// Option A: Wrap entire adapter with proxy (auto-instruments query/execute/get/all/run)
|
|
172
|
+
const db = tracedAdapter(rawAdapter, () => getActiveSpan(ctx));
|
|
173
|
+
|
|
174
|
+
const users = await db.query('SELECT * FROM users WHERE role = ?', ['admin']);
|
|
175
|
+
// Automatically records: db.operation, db.statement, db.param_count, db.row_count
|
|
176
|
+
|
|
177
|
+
// Option B: Manual instrumentation for individual queries
|
|
178
|
+
const posts = await traceQuery(rootSpan, 'select', 'SELECT * FROM posts', async () => {
|
|
179
|
+
return rawAdapter.query('SELECT * FROM posts');
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Auth (Layer 8)
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { traceAuthCheck } from '@ereo/trace';
|
|
187
|
+
|
|
188
|
+
// Trace auth checks (sync or async)
|
|
189
|
+
const session = await traceAuthCheck(rootSpan, 'requireAuth', async () => {
|
|
190
|
+
return verifySession(request);
|
|
191
|
+
}, { provider: 'jwt', roles: ['admin'] });
|
|
192
|
+
// Records: auth.operation, auth.provider, auth.roles, auth.result ('ok' or 'denied')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Islands / Hydration (Layer 9)
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { traceHydration, recordHydration } from '@ereo/trace';
|
|
199
|
+
|
|
200
|
+
// Option A: Wrap hydration (creates child span)
|
|
201
|
+
await traceHydration(rootSpan, 'Counter', 'idle', async () => {
|
|
202
|
+
await hydrateComponent(Counter, props);
|
|
203
|
+
}, { propsSize: 256 });
|
|
204
|
+
|
|
205
|
+
// Option B: Record hydration as event (lighter)
|
|
206
|
+
recordHydration(rootSpan, 'Sidebar', 'visible', 25.5);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Build (Layer 10)
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { traceBuild, traceBuildStage } from '@ereo/trace';
|
|
213
|
+
|
|
214
|
+
// Create a build trace
|
|
215
|
+
const buildSpan = traceBuild(tracer, 'production build');
|
|
216
|
+
|
|
217
|
+
// Trace individual stages
|
|
218
|
+
await traceBuildStage(buildSpan, 'route-discovery', async () => {
|
|
219
|
+
return discoverRoutes('./src/routes');
|
|
220
|
+
}, { filesCount: 42 });
|
|
221
|
+
|
|
222
|
+
await traceBuildStage(buildSpan, 'bundle', async () => {
|
|
223
|
+
return bundle({ target: 'browser' });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
buildSpan.end();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Errors (Layer 11)
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { traceError, withErrorCapture } from '@ereo/trace';
|
|
233
|
+
|
|
234
|
+
// Record an error on a span
|
|
235
|
+
traceError(rootSpan, error, 'loader');
|
|
236
|
+
// Records: error.message, error.class, error.phase, error.stack (truncated to 500 chars)
|
|
237
|
+
|
|
238
|
+
// Wrap a function with automatic error capture
|
|
239
|
+
const html = await withErrorCapture(rootSpan, 'render', async () => {
|
|
240
|
+
return renderToString(<App />);
|
|
241
|
+
});
|
|
242
|
+
// Catches errors, records them on the span, then rethrows
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Working with Spans Directly
|
|
246
|
+
|
|
247
|
+
All instrumentors use the `Span` interface under the hood. You can create custom spans for any operation:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Create child spans from any parent
|
|
251
|
+
const span = parentSpan.child('my-operation', 'custom');
|
|
252
|
+
span.setAttribute('key', 'value');
|
|
253
|
+
span.event('checkpoint', { detail: 'info' });
|
|
254
|
+
span.end();
|
|
255
|
+
|
|
256
|
+
// Auto-managed spans
|
|
257
|
+
const result = tracer.withSpan('compute', 'custom', (span) => {
|
|
258
|
+
span.setAttribute('items', 1000);
|
|
259
|
+
return heavyComputation();
|
|
260
|
+
});
|
|
261
|
+
// Span ends automatically, errors are captured
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## CLI Reporter
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { createCLIReporter } from '@ereo/trace';
|
|
268
|
+
|
|
269
|
+
const unsubscribe = createCLIReporter(tracer, {
|
|
270
|
+
colors: true, // ANSI colors (default: true)
|
|
271
|
+
layers: ['data', 'database'], // Show only specific layers (default: all)
|
|
272
|
+
minDuration: 1, // Hide spans under 1ms (default: 0)
|
|
273
|
+
verbose: false, // Show span attributes (default: false)
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Stop reporting
|
|
277
|
+
unsubscribe();
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Standalone Viewer
|
|
281
|
+
|
|
282
|
+
A self-contained HTML page with a waterfall chart, filtering, and span detail inspection.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { createViewerHandler, createTraceWebSocket } from '@ereo/trace';
|
|
286
|
+
|
|
287
|
+
// Serve the viewer at /__ereo/traces
|
|
288
|
+
const viewerHandler = createViewerHandler(tracer);
|
|
289
|
+
|
|
290
|
+
// WebSocket for live updates
|
|
291
|
+
const traceWs = createTraceWebSocket(tracer);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Traces API
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { createTracesAPIHandler } from '@ereo/trace';
|
|
298
|
+
|
|
299
|
+
const apiHandler = createTracesAPIHandler(tracer);
|
|
300
|
+
// GET /__devtools/api/traces → all traces
|
|
301
|
+
// GET /__devtools/api/traces?id=xxx → single trace
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Export Traces
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { exportTracesHTML } from '@ereo/trace';
|
|
308
|
+
|
|
309
|
+
// Export as standalone HTML file
|
|
310
|
+
const html = exportTracesHTML(tracer);
|
|
311
|
+
Bun.write('traces.html', html);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Client-Side Tracing
|
|
315
|
+
|
|
316
|
+
Browser spans are created on the client and merged into server traces via WebSocket.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// In your client entry point
|
|
320
|
+
import { initClientTracing, getClientTracer } from '@ereo/trace/client';
|
|
321
|
+
|
|
322
|
+
// Initialize (reads trace ID from server, patches fetch, connects WebSocket)
|
|
323
|
+
initClientTracing();
|
|
324
|
+
|
|
325
|
+
// Create browser-side spans
|
|
326
|
+
const tracer = getClientTracer();
|
|
327
|
+
const span = tracer.startSpan('page-render', 'islands');
|
|
328
|
+
span.setAttribute('component', 'Dashboard');
|
|
329
|
+
// ... do work ...
|
|
330
|
+
span.end();
|
|
331
|
+
tracer.submitSpan(span);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The client tracer automatically:
|
|
335
|
+
- Reads `__EREO_TRACE_ID__` from `window` (injected by server during SSR)
|
|
336
|
+
- Patches `window.fetch` to propagate `X-Ereo-Trace-Id` headers
|
|
337
|
+
- Connects to `/__ereo/trace-ws` for span submission
|
|
338
|
+
- Batches spans by trace ID for efficient transport
|
|
339
|
+
- Updates the current trace ID from server response headers
|
|
340
|
+
|
|
341
|
+
## Context Integration
|
|
342
|
+
|
|
343
|
+
Instrumentors retrieve the tracer and active span from request context:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { setTracer, getTracer, setActiveSpan, getActiveSpan } from '@ereo/trace';
|
|
347
|
+
|
|
348
|
+
// In middleware (done automatically by traceMiddleware)
|
|
349
|
+
setTracer(context, tracer);
|
|
350
|
+
setActiveSpan(context, rootSpan);
|
|
351
|
+
|
|
352
|
+
// In downstream handlers
|
|
353
|
+
const tracer = getTracer(context);
|
|
354
|
+
const parentSpan = getActiveSpan(context);
|
|
355
|
+
const child = parentSpan?.child('my-work', 'custom');
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Production: No-Op Tracer
|
|
359
|
+
|
|
360
|
+
In production builds, alias `@ereo/trace` to `@ereo/trace/noop` to eliminate all tracing overhead. The no-op export is ~592 bytes and all methods are empty stubs.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { noopTracer, noopSpan } from '@ereo/trace/noop';
|
|
364
|
+
|
|
365
|
+
// All methods are no-ops
|
|
366
|
+
noopTracer.startTrace('test', 'request'); // returns noopSpan
|
|
367
|
+
noopSpan.setAttribute('key', 'value'); // no-op
|
|
368
|
+
noopSpan.end(); // no-op
|
|
369
|
+
|
|
370
|
+
// withSpan still runs your function
|
|
371
|
+
noopTracer.withSpan('op', 'data', (span) => {
|
|
372
|
+
return computeResult(); // executes normally, span is no-op
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Build configuration example:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// In your bundler config
|
|
380
|
+
{
|
|
381
|
+
alias: {
|
|
382
|
+
'@ereo/trace': '@ereo/trace/noop',
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Configuration
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const tracer = createTracer({
|
|
391
|
+
maxTraces: 200, // Max completed traces in ring buffer (default: 200)
|
|
392
|
+
maxSpansPerTrace: 500, // Max spans per trace before capping (default: 500)
|
|
393
|
+
minDuration: 0, // Drop traces shorter than this (ms, default: 0)
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Event Streaming
|
|
398
|
+
|
|
399
|
+
Subscribe to real-time trace lifecycle events:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
const unsubscribe = tracer.subscribe((event) => {
|
|
403
|
+
switch (event.type) {
|
|
404
|
+
case 'trace:start': // New trace begun
|
|
405
|
+
case 'trace:end': // Trace finalized
|
|
406
|
+
case 'span:start': // Span created
|
|
407
|
+
case 'span:end': // Span ended
|
|
408
|
+
case 'span:event': // Event recorded on a span
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Serialization
|
|
414
|
+
|
|
415
|
+
For WebSocket and API transport, traces are serialized (Map to Record) and deserialized:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { serializeTrace, deserializeTrace, serializeEvent } from '@ereo/trace';
|
|
419
|
+
|
|
420
|
+
const json = serializeTrace(trace); // Map<SpanId, SpanData> → Record<string, SpanData>
|
|
421
|
+
const trace = deserializeTrace(json); // Record → Map
|
|
422
|
+
const wsEvent = serializeEvent(event); // Serializes trace:start/end events
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Design Decisions
|
|
426
|
+
|
|
427
|
+
| Decision | Rationale |
|
|
428
|
+
|----------|-----------|
|
|
429
|
+
| Per-trace span stacks | Isolates concurrent requests on the same tracer instance |
|
|
430
|
+
| Closure-based child factory | `span.child()` always creates spans in the correct trace |
|
|
431
|
+
| Ring buffer storage | FIFO eviction keeps memory bounded without manual cleanup |
|
|
432
|
+
| Event-level annotations | `recordSignalUpdate` / `traceCacheOperation` avoid span overhead for frequent operations |
|
|
433
|
+
| Separate client entry | `@ereo/trace/client` keeps browser code out of server bundles |
|
|
434
|
+
| No-op export | Production builds pay zero cost — all tracing code is eliminated |
|
|
435
|
+
| `performance.now()` timing | Sub-millisecond precision for measuring spans within a process |
|
|
436
|
+
|
|
437
|
+
## Documentation
|
|
438
|
+
|
|
439
|
+
For full documentation, visit [https://ereojs.dev/docs/trace](https://ereojs.dev/docs/trace)
|
|
440
|
+
|
|
441
|
+
## Part of EreoJS
|
|
442
|
+
|
|
443
|
+
This package is part of the [EreoJS](https://github.com/ereojs/ereo) monorepo - a modern full-stack framework built for Bun.
|
|
444
|
+
|
|
445
|
+
## License
|
|
446
|
+
|
|
447
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ereo/trace - CLI Reporter
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to TraceStreamEvent and prints formatted terminal output
|
|
5
|
+
* for completed requests.
|
|
6
|
+
*
|
|
7
|
+
* Output format:
|
|
8
|
+
* POST /api/users/123 200 45.2ms
|
|
9
|
+
* |-- routing 1.2ms matched /api/users/[id]
|
|
10
|
+
* |-- auth 3.1ms requireAuth -> ok
|
|
11
|
+
* |-- data 38.4ms
|
|
12
|
+
* | |-- user 12.1ms db
|
|
13
|
+
* | |-- posts 18.3ms db (parallel)
|
|
14
|
+
* | `-- comments 8.0ms cache hit
|
|
15
|
+
* `-- render 2.5ms streaming
|
|
16
|
+
*/
|
|
17
|
+
import type { Tracer, SpanLayer } from './types';
|
|
18
|
+
/** CLI reporter configuration */
|
|
19
|
+
export interface CLIReporterOptions {
|
|
20
|
+
/** Show verbose output with attributes (default: false) */
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
/** Filter to specific layers (default: all) */
|
|
23
|
+
layers?: SpanLayer[];
|
|
24
|
+
/** Minimum duration to show a span in ms (default: 0) */
|
|
25
|
+
minDuration?: number;
|
|
26
|
+
/** Use colors in output (default: true) */
|
|
27
|
+
colors?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a CLI reporter that subscribes to the tracer and prints traces.
|
|
31
|
+
*
|
|
32
|
+
* @param tracer - Tracer to subscribe to
|
|
33
|
+
* @param options - Display options
|
|
34
|
+
* @returns Unsubscribe function
|
|
35
|
+
*/
|
|
36
|
+
export declare function createCLIReporter(tracer: Tracer, options?: CLIReporterOptions): () => void;
|
|
37
|
+
//# sourceMappingURL=cli-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-reporter.d.ts","sourceRoot":"","sources":["../src/cli-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAA+B,SAAS,EAAE,MAAM,SAAS,CAAC;AAe9E,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAgKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,IAAI,CAmB9F"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ereo/trace/client - Browser entry
|
|
3
|
+
*
|
|
4
|
+
* Reads trace ID from server response, creates client-side spans,
|
|
5
|
+
* and sends them back to the server via WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* This module is designed for browser environments only.
|
|
8
|
+
*/
|
|
9
|
+
import type { SpanData, SpanId, SpanLayer, TraceId } from './types';
|
|
10
|
+
/** Client span for browser-side tracing */
|
|
11
|
+
declare class ClientSpan {
|
|
12
|
+
readonly id: SpanId;
|
|
13
|
+
readonly traceId: TraceId;
|
|
14
|
+
readonly parentId: SpanId | null;
|
|
15
|
+
private name;
|
|
16
|
+
private layer;
|
|
17
|
+
private startTime;
|
|
18
|
+
private endTime;
|
|
19
|
+
private attributes;
|
|
20
|
+
private status;
|
|
21
|
+
private events;
|
|
22
|
+
private children;
|
|
23
|
+
private ended;
|
|
24
|
+
constructor(traceId: TraceId, parentId: SpanId | null, name: string, layer: SpanLayer);
|
|
25
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
26
|
+
event(name: string, attrs?: Record<string, string | number | boolean>): void;
|
|
27
|
+
end(): void;
|
|
28
|
+
error(err: unknown): void;
|
|
29
|
+
addChild(childId: SpanId): void;
|
|
30
|
+
toData(): SpanData;
|
|
31
|
+
}
|
|
32
|
+
/** Client trace manager */
|
|
33
|
+
declare class ClientTracer {
|
|
34
|
+
private ws;
|
|
35
|
+
private pendingSpans;
|
|
36
|
+
private currentTraceId;
|
|
37
|
+
private connected;
|
|
38
|
+
/** Initialize client tracing */
|
|
39
|
+
init(): void;
|
|
40
|
+
private connect;
|
|
41
|
+
private interceptFetch;
|
|
42
|
+
/** Create a client-side span */
|
|
43
|
+
startSpan(name: string, layer: SpanLayer, parentId?: SpanId): ClientSpan;
|
|
44
|
+
/** Submit a completed span to the server */
|
|
45
|
+
submitSpan(span: ClientSpan): void;
|
|
46
|
+
/** Set the current trace ID (e.g., after SPA navigation) */
|
|
47
|
+
setTraceId(traceId: TraceId): void;
|
|
48
|
+
/** Get current trace ID */
|
|
49
|
+
getTraceId(): TraceId | null;
|
|
50
|
+
private flushPending;
|
|
51
|
+
/** Disconnect and clean up */
|
|
52
|
+
destroy(): void;
|
|
53
|
+
}
|
|
54
|
+
/** Get or create the client tracer */
|
|
55
|
+
export declare function getClientTracer(): ClientTracer;
|
|
56
|
+
/** Initialize client-side tracing */
|
|
57
|
+
export declare function initClientTracing(): void;
|
|
58
|
+
export { ClientTracer, ClientSpan };
|
|
59
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAa,MAAM,EAAE,SAAS,EAAc,OAAO,EAAE,MAAM,SAAS,CAAC;AAgB3F,2CAA2C;AAC3C,cAAM,UAAU;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,UAAU,CAAiD;IACnE,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS;IASrF,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IAIjE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAI5E,GAAG,IAAI,IAAI;IAMX,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAMzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI/B,MAAM,IAAI,QAAQ;CAgBnB;AAED,2BAA2B;AAC3B,cAAM,YAAY;IAChB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,SAAS,CAAS;IAE1B,gCAAgC;IAChC,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,OAAO;IAwBf,OAAO,CAAC,cAAc;IA8BtB,gCAAgC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU;IAKxE,4CAA4C;IAC5C,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IASlC,4DAA4D;IAC5D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIlC,2BAA2B;IAC3B,UAAU,IAAI,OAAO,GAAG,IAAI;IAI5B,OAAO,CAAC,YAAY;IA0BpB,8BAA8B;IAC9B,OAAO,IAAI,IAAI;CAQhB;AAKD,sCAAsC;AACtC,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC"}
|