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