@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/dist/index.js
ADDED
|
@@ -0,0 +1,1367 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/noop.ts
|
|
3
|
+
var NOOP_SPAN = {
|
|
4
|
+
id: "",
|
|
5
|
+
traceId: "",
|
|
6
|
+
setAttribute() {},
|
|
7
|
+
event() {},
|
|
8
|
+
end() {},
|
|
9
|
+
error() {},
|
|
10
|
+
child() {
|
|
11
|
+
return NOOP_SPAN;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
class NoopTracer {
|
|
16
|
+
startTrace() {
|
|
17
|
+
return NOOP_SPAN;
|
|
18
|
+
}
|
|
19
|
+
startSpan() {
|
|
20
|
+
return NOOP_SPAN;
|
|
21
|
+
}
|
|
22
|
+
activeSpan() {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
withSpan(_name, _layer, fn) {
|
|
26
|
+
return fn(NOOP_SPAN);
|
|
27
|
+
}
|
|
28
|
+
getTraces() {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
getTrace() {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
subscribe() {
|
|
35
|
+
return () => {};
|
|
36
|
+
}
|
|
37
|
+
mergeClientSpans() {}
|
|
38
|
+
}
|
|
39
|
+
var noopTracer = new NoopTracer;
|
|
40
|
+
var noopSpan = NOOP_SPAN;
|
|
41
|
+
// src/span.ts
|
|
42
|
+
var idCounter = 0;
|
|
43
|
+
function randomHex(len) {
|
|
44
|
+
let s = "";
|
|
45
|
+
while (s.length < len) {
|
|
46
|
+
s += Math.random().toString(16).slice(2);
|
|
47
|
+
}
|
|
48
|
+
return s.slice(0, len);
|
|
49
|
+
}
|
|
50
|
+
function generateSpanId() {
|
|
51
|
+
idCounter++;
|
|
52
|
+
const time = Date.now().toString(16).slice(-8);
|
|
53
|
+
const counter = (idCounter & 65535).toString(16).padStart(4, "0");
|
|
54
|
+
const random = randomHex(4);
|
|
55
|
+
return `${time}${counter}${random}`;
|
|
56
|
+
}
|
|
57
|
+
function generateTraceId() {
|
|
58
|
+
return randomHex(32);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class SpanImpl {
|
|
62
|
+
id;
|
|
63
|
+
traceId;
|
|
64
|
+
_parentId;
|
|
65
|
+
_name;
|
|
66
|
+
_layer;
|
|
67
|
+
_status = "ok";
|
|
68
|
+
_startTime;
|
|
69
|
+
_endTime = 0;
|
|
70
|
+
_attributes = {};
|
|
71
|
+
_events = [];
|
|
72
|
+
_children = [];
|
|
73
|
+
_ended = false;
|
|
74
|
+
_onEnd;
|
|
75
|
+
_childFactory;
|
|
76
|
+
_onEvent;
|
|
77
|
+
constructor(traceId, parentId, name, layer, onEnd, childFactory, onEvent) {
|
|
78
|
+
this.id = generateSpanId();
|
|
79
|
+
this.traceId = traceId;
|
|
80
|
+
this._parentId = parentId;
|
|
81
|
+
this._name = name;
|
|
82
|
+
this._layer = layer;
|
|
83
|
+
this._startTime = performance.now();
|
|
84
|
+
this._onEnd = onEnd;
|
|
85
|
+
this._childFactory = childFactory;
|
|
86
|
+
this._onEvent = onEvent;
|
|
87
|
+
}
|
|
88
|
+
setAttribute(key, value) {
|
|
89
|
+
if (this._ended)
|
|
90
|
+
return;
|
|
91
|
+
this._attributes[key] = value;
|
|
92
|
+
}
|
|
93
|
+
event(name, attributes) {
|
|
94
|
+
if (this._ended)
|
|
95
|
+
return;
|
|
96
|
+
const evt = { name, time: performance.now(), attributes };
|
|
97
|
+
this._events.push(evt);
|
|
98
|
+
this._onEvent?.(this.id, evt);
|
|
99
|
+
}
|
|
100
|
+
end() {
|
|
101
|
+
if (this._ended)
|
|
102
|
+
return;
|
|
103
|
+
this._ended = true;
|
|
104
|
+
this._endTime = performance.now();
|
|
105
|
+
this._onEnd(this);
|
|
106
|
+
}
|
|
107
|
+
error(err) {
|
|
108
|
+
if (this._ended)
|
|
109
|
+
return;
|
|
110
|
+
this._status = "error";
|
|
111
|
+
if (err instanceof Error) {
|
|
112
|
+
this._attributes["error.message"] = err.message;
|
|
113
|
+
this._attributes["error.name"] = err.name;
|
|
114
|
+
if (err.stack) {
|
|
115
|
+
this._attributes["error.stack"] = err.stack.slice(0, 500);
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
this._attributes["error.message"] = String(err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
child(name, layer) {
|
|
122
|
+
const childSpan = this._childFactory(name, layer, this.id);
|
|
123
|
+
this._children.push(childSpan.id);
|
|
124
|
+
return childSpan;
|
|
125
|
+
}
|
|
126
|
+
get ended() {
|
|
127
|
+
return this._ended;
|
|
128
|
+
}
|
|
129
|
+
toData() {
|
|
130
|
+
return {
|
|
131
|
+
id: this.id,
|
|
132
|
+
traceId: this.traceId,
|
|
133
|
+
parentId: this._parentId,
|
|
134
|
+
name: this._name,
|
|
135
|
+
layer: this._layer,
|
|
136
|
+
status: this._status,
|
|
137
|
+
startTime: this._startTime,
|
|
138
|
+
endTime: this._endTime || performance.now(),
|
|
139
|
+
duration: (this._endTime || performance.now()) - this._startTime,
|
|
140
|
+
attributes: { ...this._attributes },
|
|
141
|
+
events: [...this._events],
|
|
142
|
+
children: [...this._children]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/ring-buffer.ts
|
|
148
|
+
class RingBuffer {
|
|
149
|
+
capacity;
|
|
150
|
+
buffer;
|
|
151
|
+
index = 0;
|
|
152
|
+
count = 0;
|
|
153
|
+
lookup = new Map;
|
|
154
|
+
constructor(capacity) {
|
|
155
|
+
this.capacity = capacity;
|
|
156
|
+
this.buffer = new Array(capacity);
|
|
157
|
+
}
|
|
158
|
+
push(item) {
|
|
159
|
+
const evicted = this.buffer[this.index];
|
|
160
|
+
if (evicted) {
|
|
161
|
+
this.lookup.delete(evicted.id);
|
|
162
|
+
}
|
|
163
|
+
this.buffer[this.index] = item;
|
|
164
|
+
this.lookup.set(item.id, item);
|
|
165
|
+
this.index = (this.index + 1) % this.capacity;
|
|
166
|
+
if (this.count < this.capacity)
|
|
167
|
+
this.count++;
|
|
168
|
+
}
|
|
169
|
+
get(id) {
|
|
170
|
+
return this.lookup.get(id);
|
|
171
|
+
}
|
|
172
|
+
toArray() {
|
|
173
|
+
const result = [];
|
|
174
|
+
if (this.count < this.capacity) {
|
|
175
|
+
for (let i = 0;i < this.count; i++) {
|
|
176
|
+
const item = this.buffer[i];
|
|
177
|
+
if (item)
|
|
178
|
+
result.push(item);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
for (let i = 0;i < this.capacity; i++) {
|
|
182
|
+
const item = this.buffer[(this.index + i) % this.capacity];
|
|
183
|
+
if (item)
|
|
184
|
+
result.push(item);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
get size() {
|
|
190
|
+
return this.count;
|
|
191
|
+
}
|
|
192
|
+
clear() {
|
|
193
|
+
this.buffer = new Array(this.capacity);
|
|
194
|
+
this.index = 0;
|
|
195
|
+
this.count = 0;
|
|
196
|
+
this.lookup.clear();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/tracer.ts
|
|
201
|
+
class TracerImpl {
|
|
202
|
+
traces;
|
|
203
|
+
activeTraces = new Map;
|
|
204
|
+
spanStacks = new Map;
|
|
205
|
+
subscribers = new Set;
|
|
206
|
+
config;
|
|
207
|
+
constructor(config = {}) {
|
|
208
|
+
this.config = {
|
|
209
|
+
maxTraces: config.maxTraces ?? 200,
|
|
210
|
+
maxSpansPerTrace: config.maxSpansPerTrace ?? 500,
|
|
211
|
+
layers: config.layers ?? [],
|
|
212
|
+
minDuration: config.minDuration ?? 0
|
|
213
|
+
};
|
|
214
|
+
this.traces = new RingBuffer(this.config.maxTraces);
|
|
215
|
+
}
|
|
216
|
+
startTrace(name, layer, metadata) {
|
|
217
|
+
const traceId = generateTraceId();
|
|
218
|
+
const span = this.createSpan(traceId, null, name, layer);
|
|
219
|
+
const activeTrace = {
|
|
220
|
+
id: traceId,
|
|
221
|
+
rootSpanId: span.id,
|
|
222
|
+
startTime: performance.now(),
|
|
223
|
+
spans: new Map,
|
|
224
|
+
metadata: {
|
|
225
|
+
origin: "server",
|
|
226
|
+
...metadata
|
|
227
|
+
},
|
|
228
|
+
activeSpanCount: 1
|
|
229
|
+
};
|
|
230
|
+
this.activeTraces.set(traceId, activeTrace);
|
|
231
|
+
this.spanStacks.set(traceId, [span]);
|
|
232
|
+
const traceData = this.buildTraceData(activeTrace);
|
|
233
|
+
this.emit({ type: "trace:start", trace: traceData });
|
|
234
|
+
return span;
|
|
235
|
+
}
|
|
236
|
+
startSpan(name, layer) {
|
|
237
|
+
const parentSpan = this.findActiveSpan();
|
|
238
|
+
if (!parentSpan) {
|
|
239
|
+
return this.startTrace(name, layer);
|
|
240
|
+
}
|
|
241
|
+
const traceId = parentSpan.traceId;
|
|
242
|
+
const activeTrace = this.activeTraces.get(traceId);
|
|
243
|
+
if (activeTrace)
|
|
244
|
+
activeTrace.activeSpanCount++;
|
|
245
|
+
const span = this.createSpan(traceId, parentSpan.id, name, layer);
|
|
246
|
+
const stack = this.spanStacks.get(traceId);
|
|
247
|
+
if (stack)
|
|
248
|
+
stack.push(span);
|
|
249
|
+
return span;
|
|
250
|
+
}
|
|
251
|
+
activeSpan() {
|
|
252
|
+
return this.findActiveSpan();
|
|
253
|
+
}
|
|
254
|
+
withSpan(name, layer, fn) {
|
|
255
|
+
const span = this.startSpan(name, layer);
|
|
256
|
+
try {
|
|
257
|
+
const result = fn(span);
|
|
258
|
+
if (result instanceof Promise) {
|
|
259
|
+
return result.then((v) => {
|
|
260
|
+
span.end();
|
|
261
|
+
return v;
|
|
262
|
+
}).catch((err) => {
|
|
263
|
+
span.error(err);
|
|
264
|
+
span.end();
|
|
265
|
+
throw err;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
span.end();
|
|
269
|
+
return result;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
span.error(err);
|
|
272
|
+
span.end();
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
getTraces() {
|
|
277
|
+
return this.traces.toArray();
|
|
278
|
+
}
|
|
279
|
+
getTrace(id) {
|
|
280
|
+
return this.traces.get(id);
|
|
281
|
+
}
|
|
282
|
+
subscribe(cb) {
|
|
283
|
+
this.subscribers.add(cb);
|
|
284
|
+
return () => {
|
|
285
|
+
this.subscribers.delete(cb);
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
mergeClientSpans(traceId, spans) {
|
|
289
|
+
const trace = this.traces.get(traceId);
|
|
290
|
+
if (!trace)
|
|
291
|
+
return;
|
|
292
|
+
for (const span of spans) {
|
|
293
|
+
if (trace.spans.size >= this.config.maxSpansPerTrace)
|
|
294
|
+
break;
|
|
295
|
+
trace.spans.set(span.id, span);
|
|
296
|
+
}
|
|
297
|
+
for (const span of spans) {
|
|
298
|
+
if (span.endTime > trace.endTime) {
|
|
299
|
+
trace.endTime = span.endTime;
|
|
300
|
+
trace.duration = trace.endTime - trace.startTime;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
findActiveSpan() {
|
|
305
|
+
let latest = null;
|
|
306
|
+
for (const stack of this.spanStacks.values()) {
|
|
307
|
+
const top = stack[stack.length - 1];
|
|
308
|
+
if (top)
|
|
309
|
+
latest = top;
|
|
310
|
+
}
|
|
311
|
+
return latest;
|
|
312
|
+
}
|
|
313
|
+
createSpan(traceId, parentId, name, layer) {
|
|
314
|
+
const childFactory = (childName, childLayer, pId) => {
|
|
315
|
+
const activeTrace = this.activeTraces.get(traceId);
|
|
316
|
+
if (activeTrace)
|
|
317
|
+
activeTrace.activeSpanCount++;
|
|
318
|
+
const child = this.createSpan(traceId, pId, childName, childLayer);
|
|
319
|
+
const stack = this.spanStacks.get(traceId);
|
|
320
|
+
if (stack)
|
|
321
|
+
stack.push(child);
|
|
322
|
+
return child;
|
|
323
|
+
};
|
|
324
|
+
const onEnd = (span2) => {
|
|
325
|
+
const stack = this.spanStacks.get(traceId);
|
|
326
|
+
if (stack) {
|
|
327
|
+
const idx = stack.indexOf(span2);
|
|
328
|
+
if (idx !== -1)
|
|
329
|
+
stack.splice(idx, 1);
|
|
330
|
+
}
|
|
331
|
+
const spanData = span2.toData();
|
|
332
|
+
const activeTrace = this.activeTraces.get(traceId);
|
|
333
|
+
if (activeTrace) {
|
|
334
|
+
if (activeTrace.spans.size < this.config.maxSpansPerTrace) {
|
|
335
|
+
activeTrace.spans.set(spanData.id, spanData);
|
|
336
|
+
}
|
|
337
|
+
activeTrace.activeSpanCount--;
|
|
338
|
+
this.emit({ type: "span:end", span: spanData });
|
|
339
|
+
if (activeTrace.activeSpanCount <= 0) {
|
|
340
|
+
this.finalizeTrace(activeTrace);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const onEvent = (spanId, event) => {
|
|
345
|
+
this.emit({ type: "span:event", spanId, event });
|
|
346
|
+
};
|
|
347
|
+
const span = new SpanImpl(traceId, parentId, name, layer, onEnd, childFactory, onEvent);
|
|
348
|
+
this.emit({ type: "span:start", span: span.toData() });
|
|
349
|
+
return span;
|
|
350
|
+
}
|
|
351
|
+
finalizeTrace(active) {
|
|
352
|
+
this.activeTraces.delete(active.id);
|
|
353
|
+
this.spanStacks.delete(active.id);
|
|
354
|
+
let endTime = active.startTime;
|
|
355
|
+
for (const span of active.spans.values()) {
|
|
356
|
+
if (span.endTime > endTime)
|
|
357
|
+
endTime = span.endTime;
|
|
358
|
+
}
|
|
359
|
+
const duration = endTime - active.startTime;
|
|
360
|
+
if (this.config.minDuration > 0 && duration < this.config.minDuration) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const traceData = {
|
|
364
|
+
id: active.id,
|
|
365
|
+
rootSpanId: active.rootSpanId,
|
|
366
|
+
startTime: active.startTime,
|
|
367
|
+
endTime,
|
|
368
|
+
duration,
|
|
369
|
+
spans: active.spans,
|
|
370
|
+
metadata: active.metadata
|
|
371
|
+
};
|
|
372
|
+
this.traces.push(traceData);
|
|
373
|
+
this.emit({ type: "trace:end", trace: traceData });
|
|
374
|
+
}
|
|
375
|
+
buildTraceData(active) {
|
|
376
|
+
return {
|
|
377
|
+
id: active.id,
|
|
378
|
+
rootSpanId: active.rootSpanId,
|
|
379
|
+
startTime: active.startTime,
|
|
380
|
+
endTime: 0,
|
|
381
|
+
duration: 0,
|
|
382
|
+
spans: active.spans,
|
|
383
|
+
metadata: active.metadata
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
emit(event) {
|
|
387
|
+
for (const cb of this.subscribers) {
|
|
388
|
+
try {
|
|
389
|
+
cb(event);
|
|
390
|
+
} catch {}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function createTracer(config) {
|
|
395
|
+
return new TracerImpl(config);
|
|
396
|
+
}
|
|
397
|
+
// src/context.ts
|
|
398
|
+
var TRACER_KEY = "__ereo_tracer";
|
|
399
|
+
var ACTIVE_SPAN_KEY = "__ereo_active_span";
|
|
400
|
+
function setTracer(context, tracer) {
|
|
401
|
+
context.set(TRACER_KEY, tracer);
|
|
402
|
+
}
|
|
403
|
+
function getTracer(context) {
|
|
404
|
+
return context.get(TRACER_KEY);
|
|
405
|
+
}
|
|
406
|
+
function setActiveSpan(context, span) {
|
|
407
|
+
context.set(ACTIVE_SPAN_KEY, span);
|
|
408
|
+
}
|
|
409
|
+
function getActiveSpan(context) {
|
|
410
|
+
return context.get(ACTIVE_SPAN_KEY);
|
|
411
|
+
}
|
|
412
|
+
// src/cli-reporter.ts
|
|
413
|
+
var RESET = "\x1B[0m";
|
|
414
|
+
var BOLD = "\x1B[1m";
|
|
415
|
+
var DIM = "\x1B[2m";
|
|
416
|
+
var GREEN = "\x1B[32m";
|
|
417
|
+
var YELLOW = "\x1B[33m";
|
|
418
|
+
var RED = "\x1B[31m";
|
|
419
|
+
var CYAN = "\x1B[36m";
|
|
420
|
+
var MAGENTA = "\x1B[35m";
|
|
421
|
+
var BLUE = "\x1B[34m";
|
|
422
|
+
var GRAY = "\x1B[90m";
|
|
423
|
+
var WHITE = "\x1B[37m";
|
|
424
|
+
function durationColor(ms) {
|
|
425
|
+
if (ms < 50)
|
|
426
|
+
return GREEN;
|
|
427
|
+
if (ms < 200)
|
|
428
|
+
return YELLOW;
|
|
429
|
+
return RED;
|
|
430
|
+
}
|
|
431
|
+
function formatDuration(ms) {
|
|
432
|
+
if (ms < 1)
|
|
433
|
+
return `${(ms * 1000).toFixed(0)}us`;
|
|
434
|
+
if (ms < 1000)
|
|
435
|
+
return `${ms.toFixed(1)}ms`;
|
|
436
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
437
|
+
}
|
|
438
|
+
function statusColor(code) {
|
|
439
|
+
if (code < 300)
|
|
440
|
+
return GREEN;
|
|
441
|
+
if (code < 400)
|
|
442
|
+
return CYAN;
|
|
443
|
+
if (code < 500)
|
|
444
|
+
return YELLOW;
|
|
445
|
+
return RED;
|
|
446
|
+
}
|
|
447
|
+
function layerColor(layer) {
|
|
448
|
+
switch (layer) {
|
|
449
|
+
case "request":
|
|
450
|
+
return WHITE;
|
|
451
|
+
case "routing":
|
|
452
|
+
return CYAN;
|
|
453
|
+
case "data":
|
|
454
|
+
return BLUE;
|
|
455
|
+
case "database":
|
|
456
|
+
return MAGENTA;
|
|
457
|
+
case "auth":
|
|
458
|
+
return YELLOW;
|
|
459
|
+
case "rpc":
|
|
460
|
+
return CYAN;
|
|
461
|
+
case "forms":
|
|
462
|
+
return GREEN;
|
|
463
|
+
case "islands":
|
|
464
|
+
return MAGENTA;
|
|
465
|
+
case "build":
|
|
466
|
+
return BLUE;
|
|
467
|
+
case "errors":
|
|
468
|
+
return RED;
|
|
469
|
+
case "signals":
|
|
470
|
+
return GREEN;
|
|
471
|
+
case "custom":
|
|
472
|
+
return GRAY;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function spanSummary(span) {
|
|
476
|
+
const parts = [];
|
|
477
|
+
if (span.attributes["route.pattern"]) {
|
|
478
|
+
parts.push(`matched ${span.attributes["route.pattern"]}`);
|
|
479
|
+
}
|
|
480
|
+
if (span.attributes["cache.hit"] === true) {
|
|
481
|
+
parts.push("cache hit");
|
|
482
|
+
} else if (span.attributes["db.system"]) {
|
|
483
|
+
parts.push(`${span.attributes["db.system"]}`);
|
|
484
|
+
}
|
|
485
|
+
if (span.attributes["auth.result"]) {
|
|
486
|
+
parts.push(`${span.attributes["auth.provider"] || "auth"} -> ${span.attributes["auth.result"]}`);
|
|
487
|
+
}
|
|
488
|
+
if (span.attributes["rpc.procedure"]) {
|
|
489
|
+
parts.push(`${span.attributes["rpc.type"] || "query"}`);
|
|
490
|
+
}
|
|
491
|
+
if (span.status === "error" && span.attributes["error.message"]) {
|
|
492
|
+
parts.push(`${span.attributes["error.message"]}`);
|
|
493
|
+
}
|
|
494
|
+
if (span.attributes["db.statement"]) {
|
|
495
|
+
const stmt = String(span.attributes["db.statement"]);
|
|
496
|
+
parts.push(stmt.length > 50 ? stmt.slice(0, 50) + "..." : stmt);
|
|
497
|
+
}
|
|
498
|
+
return parts.join(" ");
|
|
499
|
+
}
|
|
500
|
+
function printTrace(trace, options) {
|
|
501
|
+
const { verbose, layers, minDuration, colors } = options;
|
|
502
|
+
const c = (color, text) => colors ? `${color}${text}${RESET}` : text;
|
|
503
|
+
const rootSpan = trace.spans.get(trace.rootSpanId);
|
|
504
|
+
if (!rootSpan)
|
|
505
|
+
return;
|
|
506
|
+
const method = String(trace.metadata.method || "GET");
|
|
507
|
+
const pathname = String(trace.metadata.pathname || "/");
|
|
508
|
+
const statusCode = Number(trace.metadata.statusCode || rootSpan.attributes["http.status_code"] || 200);
|
|
509
|
+
const duration = trace.duration;
|
|
510
|
+
const methodStr = c(BOLD, method.padEnd(7));
|
|
511
|
+
const pathStr = pathname;
|
|
512
|
+
const statusStr = c(statusColor(statusCode), String(statusCode));
|
|
513
|
+
const durationStr = c(durationColor(duration), formatDuration(duration));
|
|
514
|
+
console.log(` ${methodStr}${pathStr} ${statusStr} ${durationStr}`);
|
|
515
|
+
const children = getChildSpans(trace, rootSpan.id);
|
|
516
|
+
printChildren(trace, children, " ", options);
|
|
517
|
+
console.log();
|
|
518
|
+
}
|
|
519
|
+
function getChildSpans(trace, parentId) {
|
|
520
|
+
const children = [];
|
|
521
|
+
for (const span of trace.spans.values()) {
|
|
522
|
+
if (span.parentId === parentId && span.id !== parentId) {
|
|
523
|
+
children.push(span);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return children.sort((a, b) => a.startTime - b.startTime);
|
|
527
|
+
}
|
|
528
|
+
function printChildren(trace, children, prefix, options) {
|
|
529
|
+
const { layers, minDuration, colors } = options;
|
|
530
|
+
const c = (color, text) => colors ? `${color}${text}${RESET}` : text;
|
|
531
|
+
const filtered = children.filter((span) => {
|
|
532
|
+
if (layers.length > 0 && !layers.includes(span.layer))
|
|
533
|
+
return false;
|
|
534
|
+
if (span.duration < minDuration)
|
|
535
|
+
return false;
|
|
536
|
+
return true;
|
|
537
|
+
});
|
|
538
|
+
for (let i = 0;i < filtered.length; i++) {
|
|
539
|
+
const span = filtered[i];
|
|
540
|
+
const isLast = i === filtered.length - 1;
|
|
541
|
+
const connector = isLast ? "`-- " : "|-- ";
|
|
542
|
+
const childPrefix = isLast ? " " : "| ";
|
|
543
|
+
const name = c(layerColor(span.layer), span.name.padEnd(16));
|
|
544
|
+
const dur = c(durationColor(span.duration), formatDuration(span.duration).padStart(8));
|
|
545
|
+
const summary = c(DIM, spanSummary(span));
|
|
546
|
+
console.log(`${prefix}${c(GRAY, connector)}${name}${dur} ${summary}`);
|
|
547
|
+
const grandchildren = getChildSpans(trace, span.id);
|
|
548
|
+
if (grandchildren.length > 0) {
|
|
549
|
+
printChildren(trace, grandchildren, prefix + childPrefix, options);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function createCLIReporter(tracer, options = {}) {
|
|
554
|
+
const resolved = {
|
|
555
|
+
verbose: options.verbose ?? false,
|
|
556
|
+
layers: options.layers ?? [],
|
|
557
|
+
minDuration: options.minDuration ?? 0,
|
|
558
|
+
colors: options.colors ?? true
|
|
559
|
+
};
|
|
560
|
+
return tracer.subscribe((event) => {
|
|
561
|
+
if (event.type === "trace:end") {
|
|
562
|
+
const rootSpan = event.trace.spans.get(event.trace.rootSpanId);
|
|
563
|
+
if (rootSpan && rootSpan.attributes["http.status_code"]) {
|
|
564
|
+
event.trace.metadata.statusCode = Number(rootSpan.attributes["http.status_code"]);
|
|
565
|
+
}
|
|
566
|
+
printTrace(event.trace, resolved);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
// src/transport.ts
|
|
571
|
+
function serializeTrace(trace) {
|
|
572
|
+
const spans = {};
|
|
573
|
+
for (const [id, span] of trace.spans) {
|
|
574
|
+
spans[id] = span;
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
id: trace.id,
|
|
578
|
+
rootSpanId: trace.rootSpanId,
|
|
579
|
+
startTime: trace.startTime,
|
|
580
|
+
endTime: trace.endTime,
|
|
581
|
+
duration: trace.duration,
|
|
582
|
+
spans,
|
|
583
|
+
metadata: trace.metadata
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function deserializeTrace(data) {
|
|
587
|
+
const spans = new Map;
|
|
588
|
+
for (const [id, span] of Object.entries(data.spans)) {
|
|
589
|
+
spans.set(id, span);
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
id: data.id,
|
|
593
|
+
rootSpanId: data.rootSpanId,
|
|
594
|
+
startTime: data.startTime,
|
|
595
|
+
endTime: data.endTime,
|
|
596
|
+
duration: data.duration,
|
|
597
|
+
spans,
|
|
598
|
+
metadata: data.metadata
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function serializeEvent(event) {
|
|
602
|
+
if (event.type === "trace:start" || event.type === "trace:end") {
|
|
603
|
+
return { type: event.type, trace: serializeTrace(event.trace) };
|
|
604
|
+
}
|
|
605
|
+
return event;
|
|
606
|
+
}
|
|
607
|
+
function createTraceWebSocket(tracer) {
|
|
608
|
+
const clients = new Set;
|
|
609
|
+
let unsubscribe = null;
|
|
610
|
+
const startBroadcast = () => {
|
|
611
|
+
if (unsubscribe)
|
|
612
|
+
return;
|
|
613
|
+
unsubscribe = tracer.subscribe((event) => {
|
|
614
|
+
const serialized = JSON.stringify(serializeEvent(event));
|
|
615
|
+
for (const client of clients) {
|
|
616
|
+
try {
|
|
617
|
+
client.send(serialized);
|
|
618
|
+
} catch {
|
|
619
|
+
clients.delete(client);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
return {
|
|
625
|
+
upgrade(request) {
|
|
626
|
+
const url = new URL(request.url);
|
|
627
|
+
return url.pathname === "/__ereo/trace-ws";
|
|
628
|
+
},
|
|
629
|
+
websocket: {
|
|
630
|
+
open(ws) {
|
|
631
|
+
clients.add(ws);
|
|
632
|
+
if (clients.size === 1)
|
|
633
|
+
startBroadcast();
|
|
634
|
+
const traces = tracer.getTraces();
|
|
635
|
+
const initial = JSON.stringify({
|
|
636
|
+
type: "initial",
|
|
637
|
+
traces: traces.map(serializeTrace)
|
|
638
|
+
});
|
|
639
|
+
ws.send(initial);
|
|
640
|
+
},
|
|
641
|
+
close(ws) {
|
|
642
|
+
clients.delete(ws);
|
|
643
|
+
if (clients.size === 0 && unsubscribe) {
|
|
644
|
+
unsubscribe();
|
|
645
|
+
unsubscribe = null;
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
message(_ws, message) {
|
|
649
|
+
try {
|
|
650
|
+
const data = JSON.parse(typeof message === "string" ? message : message.toString());
|
|
651
|
+
if (data.type === "client:spans" && data.traceId && Array.isArray(data.spans)) {
|
|
652
|
+
tracer.mergeClientSpans(data.traceId, data.spans);
|
|
653
|
+
}
|
|
654
|
+
} catch {}
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
close() {
|
|
658
|
+
if (unsubscribe) {
|
|
659
|
+
unsubscribe();
|
|
660
|
+
unsubscribe = null;
|
|
661
|
+
}
|
|
662
|
+
clients.clear();
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function createTracesAPIHandler(tracer) {
|
|
667
|
+
return (request) => {
|
|
668
|
+
const url = new URL(request.url);
|
|
669
|
+
const traceId = url.searchParams.get("id");
|
|
670
|
+
if (traceId) {
|
|
671
|
+
const trace = tracer.getTrace(traceId);
|
|
672
|
+
if (!trace) {
|
|
673
|
+
return new Response(JSON.stringify({ error: "Trace not found" }), {
|
|
674
|
+
status: 404,
|
|
675
|
+
headers: { "Content-Type": "application/json" }
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return new Response(JSON.stringify(serializeTrace(trace)), {
|
|
679
|
+
headers: { "Content-Type": "application/json" }
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const traces = tracer.getTraces().map(serializeTrace);
|
|
683
|
+
return new Response(JSON.stringify({ traces }), {
|
|
684
|
+
headers: { "Content-Type": "application/json" }
|
|
685
|
+
});
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
// src/collector.ts
|
|
689
|
+
class TraceCollector {
|
|
690
|
+
tracer;
|
|
691
|
+
constructor(tracer) {
|
|
692
|
+
this.tracer = tracer;
|
|
693
|
+
}
|
|
694
|
+
mergeClientSpans(traceId, clientSpans) {
|
|
695
|
+
this.tracer.mergeClientSpans(traceId, clientSpans);
|
|
696
|
+
}
|
|
697
|
+
getUnifiedTrace(traceId) {
|
|
698
|
+
return this.tracer.getTrace(traceId);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function createCollector(tracer) {
|
|
702
|
+
return new TraceCollector(tracer);
|
|
703
|
+
}
|
|
704
|
+
// src/viewer.ts
|
|
705
|
+
function generateViewerHTML(traces) {
|
|
706
|
+
const serialized = traces.map(serializeTrace);
|
|
707
|
+
return `<!DOCTYPE html>
|
|
708
|
+
<html lang="en">
|
|
709
|
+
<head>
|
|
710
|
+
<meta charset="UTF-8">
|
|
711
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
712
|
+
<title>Ereo Trace Viewer</title>
|
|
713
|
+
<style>
|
|
714
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
715
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace; background: #0d1117; color: #c9d1d9; }
|
|
716
|
+
.header { padding: 16px 24px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 12px; }
|
|
717
|
+
.header h1 { font-size: 16px; font-weight: 600; }
|
|
718
|
+
.header .badge { font-size: 12px; background: #1f6feb; color: white; padding: 2px 8px; border-radius: 10px; }
|
|
719
|
+
.container { display: flex; height: calc(100vh - 53px); }
|
|
720
|
+
.trace-list { width: 380px; border-right: 1px solid #21262d; overflow-y: auto; }
|
|
721
|
+
.trace-item { padding: 10px 16px; border-bottom: 1px solid #21262d; cursor: pointer; font-size: 13px; }
|
|
722
|
+
.trace-item:hover { background: #161b22; }
|
|
723
|
+
.trace-item.active { background: #1c2128; border-left: 3px solid #1f6feb; }
|
|
724
|
+
.trace-item .method { font-weight: 600; width: 50px; display: inline-block; }
|
|
725
|
+
.trace-item .path { color: #8b949e; }
|
|
726
|
+
.trace-item .status { float: right; font-size: 12px; }
|
|
727
|
+
.trace-item .duration { float: right; margin-right: 12px; font-size: 12px; }
|
|
728
|
+
.status-2xx { color: #3fb950; }
|
|
729
|
+
.status-3xx { color: #58a6ff; }
|
|
730
|
+
.status-4xx { color: #d29922; }
|
|
731
|
+
.status-5xx { color: #f85149; }
|
|
732
|
+
.dur-fast { color: #3fb950; }
|
|
733
|
+
.dur-medium { color: #d29922; }
|
|
734
|
+
.dur-slow { color: #f85149; }
|
|
735
|
+
.detail-panel { flex: 1; overflow-y: auto; padding: 16px; }
|
|
736
|
+
.waterfall { padding: 8px 0; }
|
|
737
|
+
.span-row { display: flex; align-items: center; padding: 4px 8px; font-size: 12px; border-radius: 4px; }
|
|
738
|
+
.span-row:hover { background: #161b22; }
|
|
739
|
+
.span-name { width: 200px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
740
|
+
.span-bar-container { flex: 1; height: 20px; position: relative; margin: 0 12px; }
|
|
741
|
+
.span-bar { position: absolute; height: 16px; border-radius: 3px; top: 2px; min-width: 2px; }
|
|
742
|
+
.span-duration { width: 70px; text-align: right; flex-shrink: 0; font-size: 11px; color: #8b949e; }
|
|
743
|
+
.layer-request { background: #8b949e; }
|
|
744
|
+
.layer-routing { background: #58a6ff; }
|
|
745
|
+
.layer-data { background: #1f6feb; }
|
|
746
|
+
.layer-database { background: #bc8cff; }
|
|
747
|
+
.layer-auth { background: #d29922; }
|
|
748
|
+
.layer-rpc { background: #58a6ff; }
|
|
749
|
+
.layer-forms { background: #3fb950; }
|
|
750
|
+
.layer-islands { background: #bc8cff; }
|
|
751
|
+
.layer-build { background: #1f6feb; }
|
|
752
|
+
.layer-errors { background: #f85149; }
|
|
753
|
+
.layer-signals { background: #3fb950; }
|
|
754
|
+
.layer-custom { background: #8b949e; }
|
|
755
|
+
.span-detail { margin-top: 16px; padding: 16px; background: #161b22; border-radius: 8px; font-size: 12px; }
|
|
756
|
+
.span-detail h3 { font-size: 14px; margin-bottom: 8px; }
|
|
757
|
+
.span-detail table { width: 100%; border-collapse: collapse; }
|
|
758
|
+
.span-detail td { padding: 4px 8px; border-bottom: 1px solid #21262d; }
|
|
759
|
+
.span-detail td:first-child { color: #8b949e; width: 150px; }
|
|
760
|
+
.empty { text-align: center; padding: 48px; color: #8b949e; }
|
|
761
|
+
.filters { padding: 8px 16px; border-bottom: 1px solid #21262d; display: flex; gap: 8px; font-size: 12px; }
|
|
762
|
+
.filters select, .filters input { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
|
763
|
+
</style>
|
|
764
|
+
</head>
|
|
765
|
+
<body>
|
|
766
|
+
<div class="header">
|
|
767
|
+
<h1>Ereo Trace Viewer</h1>
|
|
768
|
+
<span class="badge">${serialized.length} traces</span>
|
|
769
|
+
</div>
|
|
770
|
+
<div class="filters">
|
|
771
|
+
<select id="filter-method"><option value="">All Methods</option><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select>
|
|
772
|
+
<input id="filter-path" type="text" placeholder="Filter by path..." />
|
|
773
|
+
<select id="filter-status"><option value="">All Status</option><option value="2xx">2xx</option><option value="4xx">4xx</option><option value="5xx">5xx</option></select>
|
|
774
|
+
</div>
|
|
775
|
+
<div class="container">
|
|
776
|
+
<div class="trace-list" id="trace-list"></div>
|
|
777
|
+
<div class="detail-panel" id="detail-panel"><div class="empty">Select a trace to view details</div></div>
|
|
778
|
+
</div>
|
|
779
|
+
<script>
|
|
780
|
+
const TRACES = ${JSON.stringify(serialized).replace(/<\//g, "<\\/")};
|
|
781
|
+
let selectedTraceId = null;
|
|
782
|
+
let selectedSpanId = null;
|
|
783
|
+
|
|
784
|
+
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
785
|
+
function durClass(ms) { return ms < 50 ? 'dur-fast' : ms < 200 ? 'dur-medium' : 'dur-slow'; }
|
|
786
|
+
function statusClass(code) {
|
|
787
|
+
if (code < 300) return 'status-2xx';
|
|
788
|
+
if (code < 400) return 'status-3xx';
|
|
789
|
+
if (code < 500) return 'status-4xx';
|
|
790
|
+
return 'status-5xx';
|
|
791
|
+
}
|
|
792
|
+
function fmtDur(ms) { return ms < 1 ? (ms*1000).toFixed(0)+'us' : ms < 1000 ? ms.toFixed(1)+'ms' : (ms/1000).toFixed(2)+'s'; }
|
|
793
|
+
|
|
794
|
+
function renderTraceList(traces) {
|
|
795
|
+
const el = document.getElementById('trace-list');
|
|
796
|
+
if (!traces.length) { el.innerHTML = '<div class="empty">No traces recorded</div>'; return; }
|
|
797
|
+
el.innerHTML = traces.map(t => {
|
|
798
|
+
const s = t.metadata.statusCode || 200;
|
|
799
|
+
const d = t.duration;
|
|
800
|
+
return '<div class="trace-item'+(t.id===selectedTraceId?' active':'')+'" data-id="'+esc(t.id)+'">' +
|
|
801
|
+
'<span class="method">'+esc(t.metadata.method||'GET')+'</span>' +
|
|
802
|
+
'<span class="path">'+esc(t.metadata.pathname||'/')+'</span>' +
|
|
803
|
+
'<span class="status '+statusClass(s)+'">'+s+'</span>' +
|
|
804
|
+
'<span class="duration '+durClass(d)+'">'+fmtDur(d)+'</span>' +
|
|
805
|
+
'</div>';
|
|
806
|
+
}).join('');
|
|
807
|
+
el.querySelectorAll('.trace-item').forEach(item => {
|
|
808
|
+
item.onclick = () => { selectedTraceId = item.dataset.id; selectedSpanId = null; render(); };
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function flattenSpans(trace) {
|
|
813
|
+
const spans = Object.values(trace.spans);
|
|
814
|
+
const root = spans.find(s => s.id === trace.rootSpanId);
|
|
815
|
+
if (!root) return spans;
|
|
816
|
+
const result = [];
|
|
817
|
+
function walk(span, depth) {
|
|
818
|
+
result.push({ ...span, depth });
|
|
819
|
+
const children = spans.filter(s => s.parentId === span.id).sort((a,b) => a.startTime - b.startTime);
|
|
820
|
+
children.forEach(c => walk(c, depth+1));
|
|
821
|
+
}
|
|
822
|
+
walk(root, 0);
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function renderDetail(trace) {
|
|
827
|
+
const panel = document.getElementById('detail-panel');
|
|
828
|
+
if (!trace) { panel.innerHTML = '<div class="empty">Select a trace to view details</div>'; return; }
|
|
829
|
+
const spans = flattenSpans(trace);
|
|
830
|
+
const minTime = trace.startTime;
|
|
831
|
+
const totalDur = trace.duration || 1;
|
|
832
|
+
let html = '<div class="waterfall">';
|
|
833
|
+
spans.forEach(s => {
|
|
834
|
+
const left = ((s.startTime - minTime) / totalDur * 100).toFixed(2);
|
|
835
|
+
const width = Math.max(0.5, (s.duration / totalDur * 100));
|
|
836
|
+
const indent = s.depth * 16;
|
|
837
|
+
html += '<div class="span-row'+(s.id===selectedSpanId?' active':'')+'" data-span="'+esc(s.id)+'">' +
|
|
838
|
+
'<div class="span-name" style="padding-left:'+indent+'px">'+esc(s.name)+'</div>' +
|
|
839
|
+
'<div class="span-bar-container"><div class="span-bar layer-'+esc(s.layer)+'" style="left:'+left+'%;width:'+width.toFixed(2)+'%"></div></div>' +
|
|
840
|
+
'<div class="span-duration '+durClass(s.duration)+'">'+fmtDur(s.duration)+'</div>' +
|
|
841
|
+
'</div>';
|
|
842
|
+
});
|
|
843
|
+
html += '</div>';
|
|
844
|
+
if (selectedSpanId) {
|
|
845
|
+
const span = trace.spans[selectedSpanId];
|
|
846
|
+
if (span) {
|
|
847
|
+
html += '<div class="span-detail"><h3>'+esc(span.name)+' ('+esc(span.layer)+')</h3><table>';
|
|
848
|
+
html += '<tr><td>Status</td><td>'+esc(span.status)+'</td></tr>';
|
|
849
|
+
html += '<tr><td>Duration</td><td>'+fmtDur(span.duration)+'</td></tr>';
|
|
850
|
+
Object.entries(span.attributes||{}).forEach(([k,v]) => { html += '<tr><td>'+esc(k)+'</td><td>'+esc(v)+'</td></tr>'; });
|
|
851
|
+
if (span.events && span.events.length) {
|
|
852
|
+
html += '<tr><td>Events</td><td>'+span.events.map(e=>esc(e.name)).join(', ')+'</td></tr>';
|
|
853
|
+
}
|
|
854
|
+
html += '</table></div>';
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
panel.innerHTML = html;
|
|
858
|
+
panel.querySelectorAll('.span-row').forEach(row => {
|
|
859
|
+
row.onclick = () => { selectedSpanId = row.dataset.span; renderDetail(trace); };
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function getFiltered() {
|
|
864
|
+
let t = TRACES;
|
|
865
|
+
const method = document.getElementById('filter-method').value;
|
|
866
|
+
const path = document.getElementById('filter-path').value;
|
|
867
|
+
const status = document.getElementById('filter-status').value;
|
|
868
|
+
if (method) t = t.filter(x => x.metadata.method === method);
|
|
869
|
+
if (path) t = t.filter(x => (x.metadata.pathname||'').includes(path));
|
|
870
|
+
if (status) {
|
|
871
|
+
const base = parseInt(status);
|
|
872
|
+
t = t.filter(x => { const s = x.metadata.statusCode||200; return s >= base && s < base+100; });
|
|
873
|
+
}
|
|
874
|
+
return t.reverse();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function render() {
|
|
878
|
+
const filtered = getFiltered();
|
|
879
|
+
renderTraceList(filtered);
|
|
880
|
+
const trace = selectedTraceId ? TRACES.find(t => t.id === selectedTraceId) : null;
|
|
881
|
+
renderDetail(trace);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
document.getElementById('filter-method').onchange = render;
|
|
885
|
+
document.getElementById('filter-path').oninput = render;
|
|
886
|
+
document.getElementById('filter-status').onchange = render;
|
|
887
|
+
render();
|
|
888
|
+
|
|
889
|
+
// Live updates via WebSocket
|
|
890
|
+
if (typeof WebSocket !== 'undefined') {
|
|
891
|
+
try {
|
|
892
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
893
|
+
const ws = new WebSocket(protocol + '//' + location.host + '/__ereo/trace-ws');
|
|
894
|
+
ws.onmessage = (e) => {
|
|
895
|
+
try {
|
|
896
|
+
const data = JSON.parse(e.data);
|
|
897
|
+
if (data.type === 'trace:end') {
|
|
898
|
+
TRACES.push(data.trace);
|
|
899
|
+
render();
|
|
900
|
+
}
|
|
901
|
+
} catch {}
|
|
902
|
+
};
|
|
903
|
+
} catch {}
|
|
904
|
+
}
|
|
905
|
+
</script>
|
|
906
|
+
</body>
|
|
907
|
+
</html>`;
|
|
908
|
+
}
|
|
909
|
+
function createViewerHandler(tracer) {
|
|
910
|
+
return () => {
|
|
911
|
+
const traces = tracer.getTraces();
|
|
912
|
+
const html = generateViewerHTML(traces);
|
|
913
|
+
return new Response(html, {
|
|
914
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
915
|
+
});
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function exportTracesHTML(tracer) {
|
|
919
|
+
return generateViewerHTML(tracer.getTraces());
|
|
920
|
+
}
|
|
921
|
+
// src/instrumentors/request.ts
|
|
922
|
+
function traceMiddleware(tracer, options = {}) {
|
|
923
|
+
const { exclude = ["/_ereo/", "/__ereo/", "/favicon.ico"], recordHeaders = false } = options;
|
|
924
|
+
return async (request, context, next) => {
|
|
925
|
+
const url = new URL(request.url);
|
|
926
|
+
for (const pattern of exclude) {
|
|
927
|
+
if (url.pathname.startsWith(pattern)) {
|
|
928
|
+
return next();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const rootSpan = tracer.startTrace(`${request.method} ${url.pathname}`, "request", {
|
|
932
|
+
origin: "server",
|
|
933
|
+
method: request.method,
|
|
934
|
+
pathname: url.pathname
|
|
935
|
+
});
|
|
936
|
+
rootSpan.setAttribute("http.method", request.method);
|
|
937
|
+
rootSpan.setAttribute("http.pathname", url.pathname);
|
|
938
|
+
rootSpan.setAttribute("http.search", url.search);
|
|
939
|
+
if (recordHeaders) {
|
|
940
|
+
const headers2 = [];
|
|
941
|
+
request.headers.forEach((value, key) => {
|
|
942
|
+
if (key.toLowerCase() !== "cookie" && key.toLowerCase() !== "authorization") {
|
|
943
|
+
headers2.push(`${key}: ${value}`);
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
rootSpan.setAttribute("http.request_headers", headers2.join("; "));
|
|
947
|
+
}
|
|
948
|
+
setTracer(context, tracer);
|
|
949
|
+
setActiveSpan(context, rootSpan);
|
|
950
|
+
const clientTraceId = request.headers.get("X-Ereo-Trace-Id");
|
|
951
|
+
if (clientTraceId) {
|
|
952
|
+
rootSpan.setAttribute("trace.client_id", clientTraceId);
|
|
953
|
+
}
|
|
954
|
+
let response;
|
|
955
|
+
try {
|
|
956
|
+
response = await next();
|
|
957
|
+
} catch (err) {
|
|
958
|
+
rootSpan.error(err);
|
|
959
|
+
rootSpan.setAttribute("http.status_code", 500);
|
|
960
|
+
rootSpan.end();
|
|
961
|
+
throw err;
|
|
962
|
+
}
|
|
963
|
+
rootSpan.setAttribute("http.status_code", response.status);
|
|
964
|
+
if (response.status >= 400) {
|
|
965
|
+
rootSpan.setAttribute("http.error", true);
|
|
966
|
+
}
|
|
967
|
+
rootSpan.end();
|
|
968
|
+
const headers = new Headers(response.headers);
|
|
969
|
+
headers.set("X-Ereo-Trace-Id", rootSpan.traceId);
|
|
970
|
+
return new Response(response.body, {
|
|
971
|
+
status: response.status,
|
|
972
|
+
statusText: response.statusText,
|
|
973
|
+
headers
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
// src/instrumentors/routing.ts
|
|
978
|
+
function traceRouteMatch(parentSpan, matchFn) {
|
|
979
|
+
const span = parentSpan.child("route.match", "routing");
|
|
980
|
+
try {
|
|
981
|
+
const result = matchFn();
|
|
982
|
+
if (result && typeof result === "object" && "route" in result) {
|
|
983
|
+
const match = result;
|
|
984
|
+
span.setAttribute("route.pattern", match.route.path);
|
|
985
|
+
span.setAttribute("route.id", match.route.id);
|
|
986
|
+
span.setAttribute("route.params", JSON.stringify(match.params));
|
|
987
|
+
if (match.layouts) {
|
|
988
|
+
span.setAttribute("route.layouts", match.layouts.map((l) => l.id).join(" > "));
|
|
989
|
+
}
|
|
990
|
+
} else {
|
|
991
|
+
span.setAttribute("route.matched", false);
|
|
992
|
+
span.event("404", { pathname: "unknown" });
|
|
993
|
+
}
|
|
994
|
+
span.end();
|
|
995
|
+
return result;
|
|
996
|
+
} catch (err) {
|
|
997
|
+
span.error(err);
|
|
998
|
+
span.end();
|
|
999
|
+
throw err;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
function recordRouteMatch(parentSpan, match) {
|
|
1003
|
+
if (match) {
|
|
1004
|
+
parentSpan.setAttribute("route.pattern", match.route.path);
|
|
1005
|
+
parentSpan.setAttribute("route.id", match.route.id);
|
|
1006
|
+
parentSpan.event("route.matched", { pattern: match.route.path });
|
|
1007
|
+
} else {
|
|
1008
|
+
parentSpan.event("route.miss");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
// src/instrumentors/data.ts
|
|
1012
|
+
function traceLoader(parentSpan, loaderKey, loaderFn) {
|
|
1013
|
+
const span = parentSpan.child(`loader:${loaderKey}`, "data");
|
|
1014
|
+
span.setAttribute("loader.key", loaderKey);
|
|
1015
|
+
try {
|
|
1016
|
+
const result = loaderFn();
|
|
1017
|
+
if (result instanceof Promise) {
|
|
1018
|
+
return result.then((v) => {
|
|
1019
|
+
span.end();
|
|
1020
|
+
return v;
|
|
1021
|
+
}).catch((err) => {
|
|
1022
|
+
span.error(err);
|
|
1023
|
+
span.end();
|
|
1024
|
+
throw err;
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
span.end();
|
|
1028
|
+
return result;
|
|
1029
|
+
} catch (err) {
|
|
1030
|
+
span.error(err);
|
|
1031
|
+
span.end();
|
|
1032
|
+
throw err;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function recordLoaderMetrics(parentSpan, metrics) {
|
|
1036
|
+
for (const metric of metrics) {
|
|
1037
|
+
const span = parentSpan.child(`loader:${metric.key}`, "data");
|
|
1038
|
+
span.setAttribute("loader.key", metric.key);
|
|
1039
|
+
span.setAttribute("loader.duration_ms", metric.duration);
|
|
1040
|
+
if (metric.cacheHit !== undefined) {
|
|
1041
|
+
span.setAttribute("cache.hit", metric.cacheHit);
|
|
1042
|
+
}
|
|
1043
|
+
if (metric.source) {
|
|
1044
|
+
span.setAttribute("loader.source", metric.source);
|
|
1045
|
+
}
|
|
1046
|
+
if (metric.waitingFor && metric.waitingFor.length > 0) {
|
|
1047
|
+
span.setAttribute("loader.waiting_for", metric.waitingFor.join(", "));
|
|
1048
|
+
}
|
|
1049
|
+
if (metric.error) {
|
|
1050
|
+
span.setAttribute("error.message", metric.error);
|
|
1051
|
+
}
|
|
1052
|
+
span.end();
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function traceCacheOperation(parentSpan, operation, key, hit) {
|
|
1056
|
+
parentSpan.event(`cache.${operation}`, {
|
|
1057
|
+
key: key.length > 100 ? key.slice(0, 100) + "..." : key,
|
|
1058
|
+
...hit !== undefined ? { hit } : {}
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
// src/instrumentors/forms.ts
|
|
1062
|
+
function traceFormSubmit(parentSpan, formName, submitFn, attrs) {
|
|
1063
|
+
const span = parentSpan.child(`form:${formName}`, "forms");
|
|
1064
|
+
span.setAttribute("form.name", formName);
|
|
1065
|
+
if (attrs?.fieldCount)
|
|
1066
|
+
span.setAttribute("form.field_count", attrs.fieldCount);
|
|
1067
|
+
try {
|
|
1068
|
+
const result = submitFn();
|
|
1069
|
+
if (result instanceof Promise) {
|
|
1070
|
+
return result.then((v) => {
|
|
1071
|
+
span.end();
|
|
1072
|
+
return v;
|
|
1073
|
+
}).catch((err) => {
|
|
1074
|
+
span.error(err);
|
|
1075
|
+
span.end();
|
|
1076
|
+
throw err;
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
span.end();
|
|
1080
|
+
return result;
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
span.error(err);
|
|
1083
|
+
span.end();
|
|
1084
|
+
throw err;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
function recordFormValidation(parentSpan, formName, attrs) {
|
|
1088
|
+
parentSpan.event("form.validation", {
|
|
1089
|
+
form: formName,
|
|
1090
|
+
error_count: attrs.errorCount,
|
|
1091
|
+
duration_ms: attrs.validationMs,
|
|
1092
|
+
...attrs.errorSources ? { sources: attrs.errorSources.join(", ") } : {}
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
// src/instrumentors/signals.ts
|
|
1096
|
+
function recordSignalUpdate(span, name, attrs) {
|
|
1097
|
+
span.event("signal.update", {
|
|
1098
|
+
name,
|
|
1099
|
+
...attrs?.subscriberCount !== undefined ? { subscribers: attrs.subscriberCount } : {},
|
|
1100
|
+
...attrs?.batched !== undefined ? { batched: attrs.batched } : {}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function recordSignalBatch(span, signalNames, attrs) {
|
|
1104
|
+
span.event("signal.batch", {
|
|
1105
|
+
count: signalNames.length,
|
|
1106
|
+
signals: signalNames.join(", "),
|
|
1107
|
+
...attrs?.subscriberCount !== undefined ? { total_subscribers: attrs.subscriberCount } : {}
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
// src/instrumentors/rpc.ts
|
|
1111
|
+
function traceRPCCall(parentSpan, procedure, type, callFn) {
|
|
1112
|
+
const span = parentSpan.child(`rpc:${procedure}`, "rpc");
|
|
1113
|
+
span.setAttribute("rpc.procedure", procedure);
|
|
1114
|
+
span.setAttribute("rpc.type", type);
|
|
1115
|
+
try {
|
|
1116
|
+
const result = callFn();
|
|
1117
|
+
if (result instanceof Promise) {
|
|
1118
|
+
return result.then((v) => {
|
|
1119
|
+
span.end();
|
|
1120
|
+
return v;
|
|
1121
|
+
}).catch((err) => {
|
|
1122
|
+
span.error(err);
|
|
1123
|
+
span.end();
|
|
1124
|
+
throw err;
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
span.end();
|
|
1128
|
+
return result;
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
span.error(err);
|
|
1131
|
+
span.end();
|
|
1132
|
+
throw err;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function recordRPCValidation(parentSpan, procedure, validationMs, valid) {
|
|
1136
|
+
parentSpan.event("rpc.validation", {
|
|
1137
|
+
procedure,
|
|
1138
|
+
duration_ms: validationMs,
|
|
1139
|
+
valid
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
// src/instrumentors/database.ts
|
|
1143
|
+
function tracedAdapter(adapter, getSpan) {
|
|
1144
|
+
const methodsToTrace = ["query", "execute", "get", "all", "run"];
|
|
1145
|
+
return new Proxy(adapter, {
|
|
1146
|
+
get(target, prop, receiver) {
|
|
1147
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1148
|
+
if (typeof prop !== "string" || typeof value !== "function" || !methodsToTrace.includes(prop)) {
|
|
1149
|
+
return value;
|
|
1150
|
+
}
|
|
1151
|
+
return async function tracedMethod(sql, params) {
|
|
1152
|
+
const parentSpan = getSpan();
|
|
1153
|
+
if (!parentSpan) {
|
|
1154
|
+
return value.call(target, sql, params);
|
|
1155
|
+
}
|
|
1156
|
+
const span = parentSpan.child(`db.${prop}`, "database");
|
|
1157
|
+
span.setAttribute("db.operation", prop);
|
|
1158
|
+
span.setAttribute("db.statement", typeof sql === "string" ? sql.slice(0, 200) : "");
|
|
1159
|
+
if (params) {
|
|
1160
|
+
span.setAttribute("db.param_count", params.length);
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
const result = await value.call(target, sql, params);
|
|
1164
|
+
if (Array.isArray(result)) {
|
|
1165
|
+
span.setAttribute("db.row_count", result.length);
|
|
1166
|
+
}
|
|
1167
|
+
span.end();
|
|
1168
|
+
return result;
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
span.error(err);
|
|
1171
|
+
span.end();
|
|
1172
|
+
throw err;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
function traceQuery(parentSpan, operation, sql, queryFn) {
|
|
1179
|
+
const span = parentSpan.child(`db.${operation}`, "database");
|
|
1180
|
+
span.setAttribute("db.operation", operation);
|
|
1181
|
+
span.setAttribute("db.statement", sql.slice(0, 200));
|
|
1182
|
+
return queryFn().then((result) => {
|
|
1183
|
+
if (Array.isArray(result)) {
|
|
1184
|
+
span.setAttribute("db.row_count", result.length);
|
|
1185
|
+
}
|
|
1186
|
+
span.end();
|
|
1187
|
+
return result;
|
|
1188
|
+
}).catch((err) => {
|
|
1189
|
+
span.error(err);
|
|
1190
|
+
span.end();
|
|
1191
|
+
throw err;
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
// src/instrumentors/auth.ts
|
|
1195
|
+
function traceAuthCheck(parentSpan, operation, checkFn, attrs) {
|
|
1196
|
+
const span = parentSpan.child(`auth:${operation}`, "auth");
|
|
1197
|
+
span.setAttribute("auth.operation", operation);
|
|
1198
|
+
if (attrs?.provider)
|
|
1199
|
+
span.setAttribute("auth.provider", attrs.provider);
|
|
1200
|
+
if (attrs?.roles)
|
|
1201
|
+
span.setAttribute("auth.roles", attrs.roles.join(", "));
|
|
1202
|
+
try {
|
|
1203
|
+
const result = checkFn();
|
|
1204
|
+
if (result instanceof Promise) {
|
|
1205
|
+
return result.then((v) => {
|
|
1206
|
+
span.setAttribute("auth.result", "ok");
|
|
1207
|
+
span.end();
|
|
1208
|
+
return v;
|
|
1209
|
+
}).catch((err) => {
|
|
1210
|
+
span.setAttribute("auth.result", "denied");
|
|
1211
|
+
if (err instanceof Response) {
|
|
1212
|
+
const location = err.headers.get("Location");
|
|
1213
|
+
if (location)
|
|
1214
|
+
span.setAttribute("auth.redirect", location);
|
|
1215
|
+
}
|
|
1216
|
+
span.error(err);
|
|
1217
|
+
span.end();
|
|
1218
|
+
throw err;
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
span.setAttribute("auth.result", "ok");
|
|
1222
|
+
span.end();
|
|
1223
|
+
return result;
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
span.setAttribute("auth.result", "denied");
|
|
1226
|
+
span.error(err);
|
|
1227
|
+
span.end();
|
|
1228
|
+
throw err;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
// src/instrumentors/islands.ts
|
|
1232
|
+
function traceHydration(parentSpan, componentName, strategy, hydrateFn, attrs) {
|
|
1233
|
+
const span = parentSpan.child(`hydrate:${componentName}`, "islands");
|
|
1234
|
+
span.setAttribute("island.component", componentName);
|
|
1235
|
+
span.setAttribute("island.strategy", strategy);
|
|
1236
|
+
if (attrs?.propsSize)
|
|
1237
|
+
span.setAttribute("island.props_size", attrs.propsSize);
|
|
1238
|
+
try {
|
|
1239
|
+
const result = hydrateFn();
|
|
1240
|
+
if (result instanceof Promise) {
|
|
1241
|
+
return result.then((v) => {
|
|
1242
|
+
span.end();
|
|
1243
|
+
return v;
|
|
1244
|
+
}).catch((err) => {
|
|
1245
|
+
span.error(err);
|
|
1246
|
+
span.end();
|
|
1247
|
+
throw err;
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
span.end();
|
|
1251
|
+
return result;
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
span.error(err);
|
|
1254
|
+
span.end();
|
|
1255
|
+
throw err;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function recordHydration(parentSpan, componentName, strategy, durationMs) {
|
|
1259
|
+
parentSpan.event("island.hydrated", {
|
|
1260
|
+
component: componentName,
|
|
1261
|
+
strategy,
|
|
1262
|
+
duration_ms: durationMs
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
// src/instrumentors/build.ts
|
|
1266
|
+
function traceBuildStage(parentSpan, stageName, stageFn, attrs) {
|
|
1267
|
+
const span = parentSpan.child(`build:${stageName}`, "build");
|
|
1268
|
+
span.setAttribute("build.stage", stageName);
|
|
1269
|
+
if (attrs?.filesCount)
|
|
1270
|
+
span.setAttribute("build.files_count", attrs.filesCount);
|
|
1271
|
+
try {
|
|
1272
|
+
const result = stageFn();
|
|
1273
|
+
if (result instanceof Promise) {
|
|
1274
|
+
return result.then((v) => {
|
|
1275
|
+
span.end();
|
|
1276
|
+
return v;
|
|
1277
|
+
}).catch((err) => {
|
|
1278
|
+
span.error(err);
|
|
1279
|
+
span.end();
|
|
1280
|
+
throw err;
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
span.end();
|
|
1284
|
+
return result;
|
|
1285
|
+
} catch (err) {
|
|
1286
|
+
span.error(err);
|
|
1287
|
+
span.end();
|
|
1288
|
+
throw err;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function traceBuild(tracer, buildName = "production build") {
|
|
1292
|
+
return tracer.startTrace(buildName, "build", { origin: "server" });
|
|
1293
|
+
}
|
|
1294
|
+
// src/instrumentors/errors.ts
|
|
1295
|
+
function traceError(span, err, phase = "unknown") {
|
|
1296
|
+
span.error(err);
|
|
1297
|
+
span.setAttribute("error.phase", phase);
|
|
1298
|
+
if (err instanceof Error) {
|
|
1299
|
+
span.setAttribute("error.class", err.constructor.name);
|
|
1300
|
+
span.event("error", {
|
|
1301
|
+
message: err.message,
|
|
1302
|
+
class: err.constructor.name,
|
|
1303
|
+
phase
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function withErrorCapture(span, phase, fn) {
|
|
1308
|
+
try {
|
|
1309
|
+
const result = fn();
|
|
1310
|
+
if (result instanceof Promise) {
|
|
1311
|
+
return result.catch((err) => {
|
|
1312
|
+
traceError(span, err, phase);
|
|
1313
|
+
throw err;
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
return result;
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
traceError(span, err, phase);
|
|
1319
|
+
throw err;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
export {
|
|
1323
|
+
withErrorCapture,
|
|
1324
|
+
tracedAdapter,
|
|
1325
|
+
traceRouteMatch,
|
|
1326
|
+
traceRPCCall,
|
|
1327
|
+
traceQuery,
|
|
1328
|
+
traceMiddleware,
|
|
1329
|
+
traceLoader,
|
|
1330
|
+
traceHydration,
|
|
1331
|
+
traceFormSubmit,
|
|
1332
|
+
traceError,
|
|
1333
|
+
traceCacheOperation,
|
|
1334
|
+
traceBuildStage,
|
|
1335
|
+
traceBuild,
|
|
1336
|
+
traceAuthCheck,
|
|
1337
|
+
setTracer,
|
|
1338
|
+
setActiveSpan,
|
|
1339
|
+
serializeTrace,
|
|
1340
|
+
recordSignalUpdate,
|
|
1341
|
+
recordSignalBatch,
|
|
1342
|
+
recordRouteMatch,
|
|
1343
|
+
recordRPCValidation,
|
|
1344
|
+
recordLoaderMetrics,
|
|
1345
|
+
recordHydration,
|
|
1346
|
+
recordFormValidation,
|
|
1347
|
+
noopTracer,
|
|
1348
|
+
noopSpan,
|
|
1349
|
+
getTracer,
|
|
1350
|
+
getActiveSpan,
|
|
1351
|
+
generateViewerHTML,
|
|
1352
|
+
generateTraceId,
|
|
1353
|
+
generateSpanId,
|
|
1354
|
+
exportTracesHTML,
|
|
1355
|
+
deserializeTrace,
|
|
1356
|
+
createViewerHandler,
|
|
1357
|
+
createTracesAPIHandler,
|
|
1358
|
+
createTracer,
|
|
1359
|
+
createTraceWebSocket,
|
|
1360
|
+
createCollector,
|
|
1361
|
+
createCLIReporter,
|
|
1362
|
+
TracerImpl,
|
|
1363
|
+
TraceCollector,
|
|
1364
|
+
SpanImpl,
|
|
1365
|
+
RingBuffer,
|
|
1366
|
+
NoopTracer
|
|
1367
|
+
};
|