refactor: share diagnostics timeline span helpers

This commit is contained in:
Vincent Koc
2026-05-29 23:35:09 +02:00
parent 49cc613021
commit 196ea61ec4

View File

@@ -70,6 +70,13 @@ export type ActiveDiagnosticsTimelineSpan = {
attributes?: DiagnosticsTimelineAttributes;
};
type StartedDiagnosticsTimelineSpan = ActiveDiagnosticsTimelineSpan & {
config?: OpenClawConfig;
env: NodeJS.ProcessEnv;
startedAt: number;
omitErrorMessage?: boolean;
};
let warnedAboutTimelineWrite = false;
const createdTimelineDirs = new Set<string>();
const activeDiagnosticsTimelineSpan = new AsyncLocalStorage<ActiveDiagnosticsTimelineSpan>();
@@ -192,72 +199,107 @@ export function getActiveDiagnosticsTimelineSpan(): ActiveDiagnosticsTimelineSpa
return activeDiagnosticsTimelineSpan.getStore();
}
function startDiagnosticsTimelineSpan(
name: string,
options: DiagnosticsTimelineSpanOptions,
): StartedDiagnosticsTimelineSpan | undefined {
const env = options.env ?? process.env;
if (!isDiagnosticsTimelineEnabled({ config: options.config, env })) {
return undefined;
}
const activeSpan = getActiveDiagnosticsTimelineSpan();
const phase = options.phase ?? activeSpan?.phase;
const parentSpanId = options.parentSpanId ?? activeSpan?.spanId;
const span: StartedDiagnosticsTimelineSpan = {
name,
env,
...(options.config ? { config: options.config } : {}),
spanId: randomUUID(),
startedAt: performance.now(),
...(phase ? { phase } : {}),
...(parentSpanId ? { parentSpanId } : {}),
...(options.attributes ? { attributes: options.attributes } : {}),
...(options.omitErrorMessage ? { omitErrorMessage: true } : {}),
};
emitDiagnosticsTimelineEvent(
{
type: "span.start",
name: span.name,
phase: span.phase,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
attributes: span.attributes,
},
{ config: span.config, env: span.env },
);
return span;
}
function runInDiagnosticsTimelineSpan<T>(span: StartedDiagnosticsTimelineSpan, run: () => T): T {
return activeDiagnosticsTimelineSpan.run(
{
name: span.name,
...(span.phase ? { phase: span.phase } : {}),
spanId: span.spanId,
...(span.parentSpanId ? { parentSpanId: span.parentSpanId } : {}),
...(span.attributes ? { attributes: span.attributes } : {}),
},
run,
);
}
function emitFinishedDiagnosticsTimelineSpan(span: StartedDiagnosticsTimelineSpan): void {
emitDiagnosticsTimelineEvent(
{
type: "span.end",
name: span.name,
phase: span.phase,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
durationMs: performance.now() - span.startedAt,
attributes: span.attributes,
},
{ config: span.config, env: span.env },
);
}
function emitFailedDiagnosticsTimelineSpan(
span: StartedDiagnosticsTimelineSpan,
error: unknown,
): void {
emitDiagnosticsTimelineEvent(
{
type: "span.error",
name: span.name,
phase: span.phase,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
durationMs: performance.now() - span.startedAt,
attributes: span.attributes,
errorName: error instanceof Error ? error.name : typeof error,
...(span.omitErrorMessage
? {}
: { errorMessage: error instanceof Error ? error.message : String(error) }),
},
{ config: span.config, env: span.env },
);
}
export async function measureDiagnosticsTimelineSpan<T>(
name: string,
run: () => Promise<T> | T,
options: DiagnosticsTimelineSpanOptions = {},
): Promise<T> {
const env = options.env ?? process.env;
if (!isDiagnosticsTimelineEnabled({ config: options.config, env })) {
const span = startDiagnosticsTimelineSpan(name, options);
if (!span) {
return await run();
}
const activeSpan = getActiveDiagnosticsTimelineSpan();
const spanId = randomUUID();
const phase = options.phase ?? activeSpan?.phase;
const parentSpanId = options.parentSpanId ?? activeSpan?.spanId;
const startedAt = performance.now();
emitDiagnosticsTimelineEvent(
{
type: "span.start",
name,
phase,
spanId,
parentSpanId,
attributes: options.attributes,
},
{ config: options.config, env },
);
try {
const result = await activeDiagnosticsTimelineSpan.run(
{
name,
...(phase ? { phase } : {}),
spanId,
...(parentSpanId ? { parentSpanId } : {}),
...(options.attributes ? { attributes: options.attributes } : {}),
},
() => run(),
);
emitDiagnosticsTimelineEvent(
{
type: "span.end",
name,
phase,
spanId,
parentSpanId,
durationMs: performance.now() - startedAt,
attributes: options.attributes,
},
{ config: options.config, env },
);
const result = await runInDiagnosticsTimelineSpan(span, () => run());
emitFinishedDiagnosticsTimelineSpan(span);
return result;
} catch (error) {
emitDiagnosticsTimelineEvent(
{
type: "span.error",
name,
phase,
spanId,
parentSpanId,
durationMs: performance.now() - startedAt,
attributes: options.attributes,
errorName: error instanceof Error ? error.name : typeof error,
...(options.omitErrorMessage
? {}
: { errorMessage: error instanceof Error ? error.message : String(error) }),
},
{ config: options.config, env },
);
emitFailedDiagnosticsTimelineSpan(span, error);
throw error;
}
}
@@ -267,67 +309,16 @@ export function measureDiagnosticsTimelineSpanSync<T>(
run: () => T,
options: DiagnosticsTimelineSpanOptions = {},
): T {
const env = options.env ?? process.env;
if (!isDiagnosticsTimelineEnabled({ config: options.config, env })) {
const span = startDiagnosticsTimelineSpan(name, options);
if (!span) {
return run();
}
const activeSpan = getActiveDiagnosticsTimelineSpan();
const spanId = randomUUID();
const phase = options.phase ?? activeSpan?.phase;
const parentSpanId = options.parentSpanId ?? activeSpan?.spanId;
const startedAt = performance.now();
emitDiagnosticsTimelineEvent(
{
type: "span.start",
name,
phase,
spanId,
parentSpanId,
attributes: options.attributes,
},
{ config: options.config, env },
);
try {
const result = activeDiagnosticsTimelineSpan.run(
{
name,
...(phase ? { phase } : {}),
spanId,
...(parentSpanId ? { parentSpanId } : {}),
...(options.attributes ? { attributes: options.attributes } : {}),
},
run,
);
emitDiagnosticsTimelineEvent(
{
type: "span.end",
name,
phase,
spanId,
parentSpanId,
durationMs: performance.now() - startedAt,
attributes: options.attributes,
},
{ config: options.config, env },
);
const result = runInDiagnosticsTimelineSpan(span, run);
emitFinishedDiagnosticsTimelineSpan(span);
return result;
} catch (error) {
emitDiagnosticsTimelineEvent(
{
type: "span.error",
name,
phase,
spanId,
parentSpanId,
durationMs: performance.now() - startedAt,
attributes: options.attributes,
errorName: error instanceof Error ? error.name : typeof error,
...(options.omitErrorMessage
? {}
: { errorMessage: error instanceof Error ? error.message : String(error) }),
},
{ config: options.config, env },
);
emitFailedDiagnosticsTimelineSpan(span, error);
throw error;
}
}