effect-start 0.31.0 → 0.32.0
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/Fetch.d.ts +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js +1 -1
- package/dist/Fetch.js.map +1 -1
- package/dist/Password.d.ts +26 -0
- package/dist/Password.d.ts.map +1 -0
- package/dist/Password.js +83 -0
- package/dist/Password.js.map +1 -0
- package/dist/RouteSse.d.ts +1 -2
- package/dist/RouteSse.d.ts.map +1 -1
- package/dist/RouteSse.js +2 -2
- package/dist/RouteSse.js.map +1 -1
- package/dist/_StreamExtra.d.ts.map +1 -1
- package/dist/_StreamExtra.js +0 -1
- package/dist/_StreamExtra.js.map +1 -1
- package/dist/studio/Studio.d.ts +1 -1
- package/dist/studio/Studio.d.ts.map +1 -1
- package/dist/studio/Studio.js +5 -4
- package/dist/studio/Studio.js.map +1 -1
- package/dist/studio/StudioErrors.d.ts.map +1 -1
- package/dist/studio/StudioErrors.js +2 -2
- package/dist/studio/StudioErrors.js.map +1 -1
- package/dist/studio/StudioLogger.d.ts.map +1 -1
- package/dist/studio/StudioLogger.js +1 -3
- package/dist/studio/StudioLogger.js.map +1 -1
- package/dist/studio/StudioStore.d.ts +23 -17
- package/dist/studio/StudioStore.d.ts.map +1 -1
- package/dist/studio/StudioStore.js +48 -44
- package/dist/studio/StudioStore.js.map +1 -1
- package/dist/studio/StudioTracer.d.ts.map +1 -1
- package/dist/studio/StudioTracer.js +10 -4
- package/dist/studio/StudioTracer.js.map +1 -1
- package/dist/studio/routes/errors/route.d.ts +1 -1
- package/dist/studio/routes/errors/route.d.ts.map +1 -1
- package/dist/studio/routes/errors/route.js +2 -2
- package/dist/studio/routes/errors/route.js.map +1 -1
- package/dist/studio/routes/fiberDetail.d.ts +1 -1
- package/dist/studio/routes/fiberDetail.js +4 -4
- package/dist/studio/routes/fiberDetail.js.map +1 -1
- package/dist/studio/routes/fibers/route.d.ts +2 -2
- package/dist/studio/routes/fibers/route.js +4 -4
- package/dist/studio/routes/fibers/route.js.map +1 -1
- package/dist/studio/routes/logs/route.d.ts +1 -1
- package/dist/studio/routes/logs/route.d.ts.map +1 -1
- package/dist/studio/routes/logs/route.js +2 -2
- package/dist/studio/routes/logs/route.js.map +1 -1
- package/dist/studio/routes/traceDetail.d.ts +1 -1
- package/dist/studio/routes/traceDetail.js +1 -1
- package/dist/studio/routes/traceDetail.js.map +1 -1
- package/dist/studio/routes/traces/route.d.ts +2 -2
- package/dist/studio/routes/traces/route.d.ts.map +1 -1
- package/dist/studio/routes/traces/route.js +5 -5
- package/dist/studio/routes/traces/route.js.map +1 -1
- package/dist/studio/routes/tree.d.ts +8 -8
- package/dist/studio/ui/Traces.d.ts +1 -0
- package/dist/studio/ui/Traces.d.ts.map +1 -1
- package/dist/studio/ui/Traces.js +32 -11
- package/dist/studio/ui/Traces.js.map +1 -1
- package/package.json +1 -1
- package/src/Fetch.ts +2 -2
- package/src/Password.ts +130 -0
- package/src/RouteSse.ts +4 -4
- package/src/_StreamExtra.ts +0 -1
- package/src/studio/Studio.ts +6 -5
- package/src/studio/StudioErrors.ts +2 -3
- package/src/studio/StudioLogger.ts +2 -3
- package/src/studio/StudioStore.ts +159 -115
- package/src/studio/StudioTracer.ts +11 -5
- package/src/studio/routes/errors/route.tsx +3 -1
- package/src/studio/routes/fiberDetail.tsx +4 -4
- package/src/studio/routes/fibers/route.tsx +4 -4
- package/src/studio/routes/logs/route.tsx +3 -1
- package/src/studio/routes/traceDetail.tsx +1 -1
- package/src/studio/routes/traces/route.tsx +9 -6
- package/src/studio/ui/Traces.tsx +41 -13
|
@@ -114,6 +114,8 @@ export interface StudioMetricSnapshot {
|
|
|
114
114
|
export type StudioEvent =
|
|
115
115
|
| { readonly _tag: "SpanStart"; readonly span: StudioSpan }
|
|
116
116
|
| { readonly _tag: "SpanEnd"; readonly span: StudioSpan }
|
|
117
|
+
| { readonly _tag: "TraceStart"; readonly traceId: bigint }
|
|
118
|
+
| { readonly _tag: "TraceEnd"; readonly traceId: bigint }
|
|
117
119
|
| { readonly _tag: "Log"; readonly log: StudioLog }
|
|
118
120
|
| { readonly _tag: "Error"; readonly error: StudioError }
|
|
119
121
|
| { readonly _tag: "MetricsSnapshot"; readonly metrics: Array<StudioMetricSnapshot> }
|
|
@@ -327,179 +329,221 @@ function deserializeError(row: ErrorRow): StudioError {
|
|
|
327
329
|
}
|
|
328
330
|
}
|
|
329
331
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
332
|
+
const withSql = <A, E>(
|
|
333
|
+
f: (sql: SqlClient.SqlClient) => Effect.Effect<A, E>,
|
|
334
|
+
): Effect.Effect<A, E, SqlClient.SqlClient> => Effect.flatMap(SqlClient.SqlClient, f)
|
|
335
|
+
|
|
336
|
+
export function insertSpan(span: StudioSpan) {
|
|
337
|
+
return withSql((sql) =>
|
|
338
|
+
sql`INSERT INTO Span ${sql({
|
|
339
|
+
spanId: span.spanId,
|
|
340
|
+
traceId: span.traceId,
|
|
341
|
+
fiberId: span.fiberId ?? null,
|
|
342
|
+
name: span.name,
|
|
343
|
+
kind: span.kind,
|
|
344
|
+
parentSpanId: span.parentSpanId ?? null,
|
|
345
|
+
startTime: span.startTime.toString(),
|
|
346
|
+
endTime: span.endTime?.toString() ?? null,
|
|
347
|
+
durationMs: span.durationMs ?? null,
|
|
348
|
+
status: span.status,
|
|
349
|
+
attributes: JSON.stringify(span.attributes),
|
|
350
|
+
events: JSON.stringify(serializeBigint(span.events)),
|
|
351
|
+
})}`,
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function updateSpan(span: StudioSpan) {
|
|
356
|
+
return withSql(
|
|
357
|
+
(sql) => sql`UPDATE Span SET
|
|
358
|
+
endTime = ${span.endTime?.toString() ?? null},
|
|
359
|
+
durationMs = ${span.durationMs ?? null},
|
|
360
|
+
status = ${span.status},
|
|
361
|
+
attributes = ${JSON.stringify(span.attributes)},
|
|
362
|
+
events = ${JSON.stringify(serializeBigint(span.events))}
|
|
363
|
+
WHERE spanId = ${span.spanId}`,
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function insertLog(log: StudioLog) {
|
|
368
|
+
return withSql((sql) =>
|
|
369
|
+
sql`INSERT INTO Log ${sql({
|
|
370
|
+
id: log.id,
|
|
371
|
+
level: log.level,
|
|
372
|
+
message: log.message,
|
|
373
|
+
fiberId: log.fiberId,
|
|
374
|
+
cause: log.cause ?? null,
|
|
375
|
+
spans: JSON.stringify(log.spans),
|
|
376
|
+
annotations: JSON.stringify(log.annotations),
|
|
377
|
+
})}`,
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function insertError(error: StudioError) {
|
|
382
|
+
return withSql((sql) =>
|
|
383
|
+
sql`INSERT INTO Error ${sql({
|
|
384
|
+
id: error.id,
|
|
385
|
+
fiberId: error.fiberId,
|
|
386
|
+
interrupted: error.interrupted ? 1 : 0,
|
|
387
|
+
prettyPrint: error.prettyPrint,
|
|
388
|
+
details: JSON.stringify(error.details),
|
|
389
|
+
})}`,
|
|
390
|
+
)
|
|
377
391
|
}
|
|
378
392
|
|
|
379
393
|
export function upsertFiber(
|
|
380
|
-
sql: SqlClient.SqlClient,
|
|
381
394
|
id: string,
|
|
382
395
|
parentId: string | undefined,
|
|
383
396
|
spanName: string | undefined,
|
|
384
397
|
traceId: bigint | undefined,
|
|
385
398
|
annotations: Record<string, unknown>,
|
|
386
399
|
) {
|
|
387
|
-
return sql
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export function evict(sql: SqlClient.SqlClient, table: string, capacity: number) {
|
|
397
|
-
return Effect.gen(function* () {
|
|
398
|
-
const [{ cnt }] = yield* sql<{ cnt: number }>`SELECT count(*) as cnt FROM ${sql(table)}`
|
|
399
|
-
if (cnt > capacity) {
|
|
400
|
-
const excess = cnt - capacity
|
|
401
|
-
yield* sql`DELETE FROM ${sql(table)} WHERE rowid IN (SELECT rowid FROM ${sql(table)} ORDER BY rowid LIMIT ${excess})`
|
|
402
|
-
}
|
|
403
|
-
})
|
|
400
|
+
return withSql((sql) =>
|
|
401
|
+
sql`INSERT OR REPLACE INTO Fiber ${sql({
|
|
402
|
+
id,
|
|
403
|
+
parentId: parentId ?? null,
|
|
404
|
+
spanName: spanName ?? null,
|
|
405
|
+
traceId: traceId ?? null,
|
|
406
|
+
annotations: JSON.stringify(annotations),
|
|
407
|
+
})}`,
|
|
408
|
+
)
|
|
404
409
|
}
|
|
405
410
|
|
|
406
|
-
export function
|
|
411
|
+
export function evict(table: string, capacity: number) {
|
|
412
|
+
return withSql((sql) =>
|
|
413
|
+
Effect.gen(function* () {
|
|
414
|
+
const [{ cnt }] = yield* sql<{ cnt: number }>`SELECT count(*) as cnt FROM ${sql(table)}`
|
|
415
|
+
if (cnt > capacity) {
|
|
416
|
+
const excess = cnt - capacity
|
|
417
|
+
yield* sql`DELETE FROM ${sql(table)} WHERE rowid IN (SELECT rowid FROM ${sql(table)} ORDER BY rowid LIMIT ${excess})`
|
|
418
|
+
}
|
|
419
|
+
}),
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function runWrite(effect: Effect.Effect<unknown, SqlClient.SqlError, SqlClient.SqlClient>) {
|
|
424
|
+
const sql = store.sql
|
|
425
|
+
if (!sql) return
|
|
407
426
|
writeQueue = writeQueue
|
|
408
|
-
.then(() =>
|
|
427
|
+
.then(() =>
|
|
428
|
+
Effect.runPromise(
|
|
429
|
+
Effect.withTracerEnabled(
|
|
430
|
+
Effect.provideService(effect, SqlClient.SqlClient, sql),
|
|
431
|
+
false,
|
|
432
|
+
),
|
|
433
|
+
).then(() => undefined),
|
|
434
|
+
)
|
|
409
435
|
.catch(() => undefined)
|
|
410
436
|
}
|
|
411
437
|
|
|
412
438
|
let writeQueue = Promise.resolve<void>(undefined)
|
|
413
439
|
|
|
414
|
-
const noTrace = <A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> =>
|
|
440
|
+
const noTrace = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
415
441
|
Effect.withTracerEnabled(effect, false)
|
|
416
442
|
|
|
417
|
-
export function allSpans(
|
|
443
|
+
export function allSpans() {
|
|
418
444
|
return noTrace(
|
|
419
|
-
|
|
420
|
-
|
|
445
|
+
withSql((sql) =>
|
|
446
|
+
Effect.map(sql<SpanRow>`SELECT * FROM Span ORDER BY rowid`, (rows) =>
|
|
447
|
+
rows.map(deserializeSpan),
|
|
448
|
+
),
|
|
421
449
|
),
|
|
422
450
|
)
|
|
423
451
|
}
|
|
424
452
|
|
|
425
|
-
export function allLogs(
|
|
453
|
+
export function allLogs() {
|
|
426
454
|
return noTrace(
|
|
427
|
-
|
|
455
|
+
withSql((sql) =>
|
|
456
|
+
Effect.map(sql<LogRow>`SELECT * FROM Log ORDER BY rowid`, (rows) => rows.map(deserializeLog)),
|
|
457
|
+
),
|
|
428
458
|
)
|
|
429
459
|
}
|
|
430
460
|
|
|
431
|
-
export function allErrors(
|
|
461
|
+
export function allErrors() {
|
|
432
462
|
return noTrace(
|
|
433
|
-
|
|
434
|
-
|
|
463
|
+
withSql((sql) =>
|
|
464
|
+
Effect.map(sql<ErrorRow>`SELECT * FROM Error ORDER BY rowid`, (rows) =>
|
|
465
|
+
rows.map(deserializeError),
|
|
466
|
+
),
|
|
435
467
|
),
|
|
436
468
|
)
|
|
437
469
|
}
|
|
438
470
|
|
|
439
|
-
export function spansByTraceId(
|
|
471
|
+
export function spansByTraceId(traceId: bigint) {
|
|
440
472
|
return noTrace(
|
|
441
|
-
|
|
442
|
-
|
|
473
|
+
withSql((sql) =>
|
|
474
|
+
Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE traceId = ${traceId} ORDER BY rowid`, (rows) =>
|
|
475
|
+
rows.map(deserializeSpan),
|
|
476
|
+
),
|
|
443
477
|
),
|
|
444
478
|
)
|
|
445
479
|
}
|
|
446
480
|
|
|
447
|
-
export function spansByFiberId(
|
|
481
|
+
export function spansByFiberId(fiberId: string) {
|
|
448
482
|
return noTrace(
|
|
449
|
-
|
|
450
|
-
|
|
483
|
+
withSql((sql) =>
|
|
484
|
+
Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
|
|
485
|
+
rows.map(deserializeSpan),
|
|
486
|
+
),
|
|
451
487
|
),
|
|
452
488
|
)
|
|
453
489
|
}
|
|
454
490
|
|
|
455
|
-
export function logsByFiberId(
|
|
491
|
+
export function logsByFiberId(fiberId: string) {
|
|
456
492
|
return noTrace(
|
|
457
|
-
|
|
458
|
-
|
|
493
|
+
withSql((sql) =>
|
|
494
|
+
Effect.map(sql<LogRow>`SELECT * FROM Log WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
|
|
495
|
+
rows.map(deserializeLog),
|
|
496
|
+
),
|
|
459
497
|
),
|
|
460
498
|
)
|
|
461
499
|
}
|
|
462
500
|
|
|
463
|
-
export function getFiber(
|
|
501
|
+
export function getFiber(fiberId: string) {
|
|
464
502
|
return noTrace(
|
|
465
|
-
|
|
466
|
-
|
|
503
|
+
withSql((sql) =>
|
|
504
|
+
Effect.map(sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`, (rows) =>
|
|
505
|
+
rows.length > 0 ? rows[0] : undefined,
|
|
506
|
+
),
|
|
467
507
|
),
|
|
468
508
|
)
|
|
469
509
|
}
|
|
470
510
|
|
|
471
|
-
export function getParentChain(
|
|
511
|
+
export function getParentChain(fiberId: string) {
|
|
472
512
|
return noTrace(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
513
|
+
withSql((sql) =>
|
|
514
|
+
Effect.gen(function* () {
|
|
515
|
+
const chain: Array<string> = []
|
|
516
|
+
const visited = new Set<string>()
|
|
517
|
+
let current = fiberId
|
|
518
|
+
while (true) {
|
|
519
|
+
const rows = yield* sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${current}`
|
|
520
|
+
if (rows.length === 0 || !rows[0].parentId) break
|
|
521
|
+
const parentId = rows[0].parentId
|
|
522
|
+
if (visited.has(parentId)) break
|
|
523
|
+
chain.push(parentId)
|
|
524
|
+
visited.add(parentId)
|
|
525
|
+
current = parentId
|
|
526
|
+
}
|
|
527
|
+
return chain.reverse()
|
|
528
|
+
}),
|
|
529
|
+
),
|
|
488
530
|
)
|
|
489
531
|
}
|
|
490
532
|
|
|
491
|
-
export function getFiberContext(
|
|
533
|
+
export function getFiberContext(fiberId: string) {
|
|
492
534
|
return noTrace(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
rows
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
535
|
+
withSql((sql) =>
|
|
536
|
+
Effect.map(
|
|
537
|
+
sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`,
|
|
538
|
+
(rows): FiberContext | undefined =>
|
|
539
|
+
rows.length > 0
|
|
540
|
+
? {
|
|
541
|
+
spanName: rows[0].spanName ?? undefined,
|
|
542
|
+
traceId: rows[0].traceId != null ? BigInt(rows[0].traceId) : undefined,
|
|
543
|
+
annotations: JSON.parse(rows[0].annotations),
|
|
544
|
+
}
|
|
545
|
+
: undefined,
|
|
546
|
+
),
|
|
503
547
|
),
|
|
504
548
|
)
|
|
505
549
|
}
|
|
@@ -62,11 +62,14 @@ const make = (store: StudioStore.StudioStoreShape): Tracer.Tracer =>
|
|
|
62
62
|
|
|
63
63
|
StudioStore.runWrite(
|
|
64
64
|
Effect.zipRight(
|
|
65
|
-
StudioStore.insertSpan(
|
|
66
|
-
StudioStore.evict(
|
|
65
|
+
StudioStore.insertSpan(studioSpan),
|
|
66
|
+
StudioStore.evict("Span", store.spanCapacity),
|
|
67
67
|
),
|
|
68
68
|
)
|
|
69
69
|
publish(store, { _tag: "SpanStart", span: studioSpan })
|
|
70
|
+
if (parentSpanId === undefined) {
|
|
71
|
+
publish(store, { _tag: "TraceStart", traceId })
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
const attrs = new Map<string, unknown>(Object.entries(attributes))
|
|
72
75
|
const spanLinks = [...links]
|
|
@@ -97,17 +100,20 @@ const make = (store: StudioStore.StudioStoreShape): Tracer.Tracer =>
|
|
|
97
100
|
studioSpan.endTime = endTime
|
|
98
101
|
studioSpan.durationMs = Number(endTime - studioSpan.startTime) / 1_000_000
|
|
99
102
|
studioSpan.status = Exit.isSuccess(exit) ? "ok" : "error"
|
|
100
|
-
StudioStore.runWrite(StudioStore.updateSpan(
|
|
103
|
+
StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
|
|
101
104
|
publish(store, { _tag: "SpanEnd", span: studioSpan })
|
|
105
|
+
if (parentSpanId === undefined) {
|
|
106
|
+
publish(store, { _tag: "TraceEnd", traceId })
|
|
107
|
+
}
|
|
102
108
|
},
|
|
103
109
|
attribute(key, value) {
|
|
104
110
|
attrs.set(key, value)
|
|
105
111
|
;(studioSpan.attributes as Record<string, unknown>)[key] = value
|
|
106
|
-
StudioStore.runWrite(StudioStore.updateSpan(
|
|
112
|
+
StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
|
|
107
113
|
},
|
|
108
114
|
event(name, startTime, attributes) {
|
|
109
115
|
studioSpan.events.push({ name, startTime, attributes })
|
|
110
|
-
StudioStore.runWrite(StudioStore.updateSpan(
|
|
116
|
+
StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
|
|
111
117
|
},
|
|
112
118
|
addLinks(newLinks) {
|
|
113
119
|
spanLinks.push(...newLinks)
|
|
@@ -12,7 +12,7 @@ export default Route.get(
|
|
|
12
12
|
const url = new URL(ctx.request.url)
|
|
13
13
|
const search = url.searchParams.get("errorSearch") || ""
|
|
14
14
|
const tag = url.searchParams.get("errorTag") || ""
|
|
15
|
-
const allErrors = yield* StudioStore.allErrors(
|
|
15
|
+
const allErrors = yield* StudioStore.allErrors()
|
|
16
16
|
const tagSet = new Set<string>()
|
|
17
17
|
for (const error of allErrors) {
|
|
18
18
|
for (const d of error.details) {
|
|
@@ -67,6 +67,8 @@ export default Route.get(
|
|
|
67
67
|
<Errors.ErrorLine error={e} />
|
|
68
68
|
))}
|
|
69
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<div data-init={`@get('${prefix}/errors')`} />
|
|
70
72
|
</form>
|
|
71
73
|
</Shell.Shell>
|
|
72
74
|
)
|
|
@@ -12,8 +12,8 @@ export default Route.get(
|
|
|
12
12
|
const fiberId = ctx.pathParams.id
|
|
13
13
|
const fiberName = fiberId.startsWith("#") ? fiberId : `#${fiberId}`
|
|
14
14
|
|
|
15
|
-
const fiberLogs = yield* StudioStore.logsByFiberId(
|
|
16
|
-
const fiberSpans = yield* StudioStore.spansByFiberId(
|
|
15
|
+
const fiberLogs = yield* StudioStore.logsByFiberId(fiberName)
|
|
16
|
+
const fiberSpans = yield* StudioStore.spansByFiberId(fiberName)
|
|
17
17
|
|
|
18
18
|
const fiberNum = parseInt(fiberName.slice(1), 10)
|
|
19
19
|
const counter = StudioStore.fiberIdCounter()
|
|
@@ -30,8 +30,8 @@ export default Route.get(
|
|
|
30
30
|
else if (fiberNum < counter) alive = "dead"
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const parents = yield* StudioStore.getParentChain(
|
|
34
|
-
const fiberContext = yield* StudioStore.getFiberContext(
|
|
33
|
+
const parents = yield* StudioStore.getParentChain(fiberName)
|
|
34
|
+
const fiberContext = yield* StudioStore.getFiberContext(fiberName)
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
|
|
@@ -8,8 +8,8 @@ import * as Shell from "../../ui/Shell.tsx"
|
|
|
8
8
|
|
|
9
9
|
export default Route.get(
|
|
10
10
|
Route.html(function* () {
|
|
11
|
-
const logs = yield* StudioStore.allLogs(
|
|
12
|
-
const spans = yield* StudioStore.allSpans(
|
|
11
|
+
const logs = yield* StudioStore.allLogs()
|
|
12
|
+
const spans = yield* StudioStore.allSpans()
|
|
13
13
|
const fibers = Fibers.collectFibers(logs, spans)
|
|
14
14
|
return (
|
|
15
15
|
<Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
|
|
@@ -28,8 +28,8 @@ export default Route.get(
|
|
|
28
28
|
Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd" || e._tag === "Log"),
|
|
29
29
|
Stream.mapEffect(() =>
|
|
30
30
|
Effect.gen(function* () {
|
|
31
|
-
const logs = yield* StudioStore.allLogs(
|
|
32
|
-
const spans = yield* StudioStore.allSpans(
|
|
31
|
+
const logs = yield* StudioStore.allLogs()
|
|
32
|
+
const spans = yield* StudioStore.allSpans()
|
|
33
33
|
const fibers = Fibers.collectFibers(logs, spans)
|
|
34
34
|
const html = Html.renderToString(
|
|
35
35
|
<Fibers.FiberList fibers={fibers} prefix={StudioStore.store.prefix} />,
|
|
@@ -12,7 +12,7 @@ export default Route.get(
|
|
|
12
12
|
const url = new URL(ctx.request.url)
|
|
13
13
|
const level = url.searchParams.get("logLevel") || ""
|
|
14
14
|
const search = url.searchParams.get("logSearch") || ""
|
|
15
|
-
let logs = yield* StudioStore.allLogs(
|
|
15
|
+
let logs = yield* StudioStore.allLogs()
|
|
16
16
|
if (level) logs = logs.filter((l) => l.level === level)
|
|
17
17
|
if (search) {
|
|
18
18
|
const lower = search.toLowerCase()
|
|
@@ -53,6 +53,8 @@ export default Route.get(
|
|
|
53
53
|
<Logs.LogLine log={l} />
|
|
54
54
|
))}
|
|
55
55
|
</div>
|
|
56
|
+
|
|
57
|
+
<div data-init={`@get('${prefix}/logs')`} />
|
|
56
58
|
</form>
|
|
57
59
|
</Shell.Shell>
|
|
58
60
|
)
|
|
@@ -18,7 +18,7 @@ export default Route.get(
|
|
|
18
18
|
</Shell.Shell>
|
|
19
19
|
)
|
|
20
20
|
}
|
|
21
|
-
const spans = yield* StudioStore.spansByTraceId(
|
|
21
|
+
const spans = yield* StudioStore.spansByTraceId(traceId)
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<Shell.Shell prefix={StudioStore.store.prefix} active="traces">
|
|
@@ -12,7 +12,7 @@ export default Route.get(
|
|
|
12
12
|
Route.html(function* (ctx) {
|
|
13
13
|
const url = new URL(ctx.request.url)
|
|
14
14
|
const search = url.searchParams.get("traceSearch") || ""
|
|
15
|
-
const allSpans = yield* StudioStore.allSpans(
|
|
15
|
+
const allSpans = yield* StudioStore.allSpans()
|
|
16
16
|
const names = Array.from(new Set(allSpans.map((s) => s.name))).sort()
|
|
17
17
|
let spans = allSpans
|
|
18
18
|
if (search) {
|
|
@@ -52,14 +52,17 @@ export default Route.get(
|
|
|
52
52
|
}),
|
|
53
53
|
Route.sse(
|
|
54
54
|
Stream.fromPubSub(StudioStore.store.events).pipe(
|
|
55
|
-
Stream.filter((e) => e._tag === "
|
|
56
|
-
Stream.mapEffect(() =>
|
|
55
|
+
Stream.filter((e) => e._tag === "TraceEnd"),
|
|
56
|
+
Stream.mapEffect((e) =>
|
|
57
57
|
Effect.gen(function* () {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
58
|
+
const traceSpans = yield* StudioStore.spansByTraceId(e.traceId)
|
|
59
|
+
const traceHtml = Html.renderToString(
|
|
60
|
+
<Traces.TraceGroup id={e.traceId} spans={traceSpans} />,
|
|
61
|
+
)
|
|
62
|
+
|
|
60
63
|
return {
|
|
61
64
|
event: "datastar-patch-elements",
|
|
62
|
-
data: `selector
|
|
65
|
+
data: `selector .tl-header\nmode after\nelements ${traceHtml}`,
|
|
63
66
|
}
|
|
64
67
|
}),
|
|
65
68
|
),
|
package/src/studio/ui/Traces.tsx
CHANGED
|
@@ -47,6 +47,20 @@ interface TreeSpan {
|
|
|
47
47
|
ancestorHasNextSibling: Array<boolean>
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function sortByStartTime(a: StudioStore.StudioSpan, b: StudioStore.StudioSpan): number {
|
|
51
|
+
if (a.startTime < b.startTime) return -1
|
|
52
|
+
if (a.startTime > b.startTime) return 1
|
|
53
|
+
return 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function pickRootSpan(spans: Array<StudioStore.StudioSpan>): StudioStore.StudioSpan {
|
|
57
|
+
const spanIds = new Set(spans.map((span) => span.spanId))
|
|
58
|
+
return (
|
|
59
|
+
spans.find((span) => !span.parentSpanId || !spanIds.has(span.parentSpanId)) ??
|
|
60
|
+
spans.slice().sort(sortByStartTime)[0]
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
|
|
51
65
|
const byId = new Map<bigint, StudioStore.StudioSpan>()
|
|
52
66
|
const childrenOf = new Map<bigint, Array<StudioStore.StudioSpan>>()
|
|
@@ -69,23 +83,31 @@ function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
|
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
Number(a.startTime - b.startTime)
|
|
74
|
-
|
|
75
|
-
roots.sort(sortByStart)
|
|
86
|
+
roots.sort(sortByStartTime)
|
|
76
87
|
for (const children of childrenOf.values()) {
|
|
77
|
-
children.sort(
|
|
88
|
+
children.sort(sortByStartTime)
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
const result: Array<TreeSpan> = []
|
|
92
|
+
const visited = new Set<bigint>()
|
|
81
93
|
|
|
82
94
|
function walk(
|
|
83
95
|
span: StudioStore.StudioSpan,
|
|
84
96
|
depth: number,
|
|
85
97
|
isLast: boolean,
|
|
86
98
|
ancestors: Array<boolean>,
|
|
99
|
+
lineage: Set<bigint>,
|
|
87
100
|
) {
|
|
88
|
-
|
|
101
|
+
if (lineage.has(span.spanId) || visited.has(span.spanId)) return
|
|
102
|
+
|
|
103
|
+
const nextLineage = new Set(lineage)
|
|
104
|
+
nextLineage.add(span.spanId)
|
|
105
|
+
|
|
106
|
+
const children = (childrenOf.get(span.spanId) ?? []).filter(
|
|
107
|
+
(child) => !nextLineage.has(child.spanId) && !visited.has(child.spanId),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
visited.add(span.spanId)
|
|
89
111
|
result.push({
|
|
90
112
|
span,
|
|
91
113
|
depth,
|
|
@@ -94,12 +116,18 @@ function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
|
|
|
94
116
|
ancestorHasNextSibling: [...ancestors],
|
|
95
117
|
})
|
|
96
118
|
for (let i = 0; i < children.length; i++) {
|
|
97
|
-
walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast])
|
|
119
|
+
walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast], nextLineage)
|
|
98
120
|
}
|
|
99
121
|
}
|
|
100
122
|
|
|
101
123
|
for (let i = 0; i < roots.length; i++) {
|
|
102
|
-
walk(roots[i], 0, i === roots.length - 1, [])
|
|
124
|
+
walk(roots[i], 0, i === roots.length - 1, [], new Set())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const remaining = spans.filter((span) => !visited.has(span.spanId)).sort(sortByStartTime)
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
130
|
+
walk(remaining[i], 0, i === remaining.length - 1, [], new Set())
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
return result
|
|
@@ -225,17 +253,17 @@ export function groupByTraceId(
|
|
|
225
253
|
return groups
|
|
226
254
|
}
|
|
227
255
|
|
|
228
|
-
export function TraceGroup(options: { spans: Array<StudioStore.StudioSpan> }) {
|
|
256
|
+
export function TraceGroup(options: { id?: bigint; spans: Array<StudioStore.StudioSpan> }) {
|
|
229
257
|
if (options.spans.length === 0) return null
|
|
230
|
-
const root = options.spans
|
|
231
|
-
const traceId = root.traceId
|
|
258
|
+
const root = pickRootSpan(options.spans)
|
|
259
|
+
const traceId = options.id ?? root.traceId
|
|
232
260
|
const totalMs = root.durationMs ?? 0
|
|
233
261
|
const rootStart = root.startTime
|
|
234
262
|
const hasError = options.spans.some((s) => s.status === "error")
|
|
235
263
|
const status = hasError ? "error" : root.status
|
|
236
264
|
|
|
237
265
|
return (
|
|
238
|
-
<details class="tl-row">
|
|
266
|
+
<details id={`trace-${traceId}`} class="tl-row">
|
|
239
267
|
<summary class="tl-summary tl-cols">
|
|
240
268
|
<span class="tl-cell tl-cell-status">
|
|
241
269
|
<span
|
|
@@ -324,7 +352,7 @@ export function TraceDetail(options: { prefix: string; spans: Array<StudioStore.
|
|
|
324
352
|
if (options.spans.length === 0) {
|
|
325
353
|
return <div class="empty">Trace not found</div>
|
|
326
354
|
}
|
|
327
|
-
const root = options.spans
|
|
355
|
+
const root = pickRootSpan(options.spans)
|
|
328
356
|
const traceId = root.traceId
|
|
329
357
|
const totalMs = root.durationMs ?? 0
|
|
330
358
|
const rootStart = root.startTime
|