UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

266 lines (265 loc) 10 kB
import { i as resolveGlobalSingleton } from "./global-singleton-Dc_stLtU.js"; import { n as isTruthyEnvValue } from "./env-M3R40TOb.js"; import { n as appendRegularFileSync } from "./regular-file-Equ4rrfE.js"; import { t as isDiagnosticFlagEnabled } from "./diagnostic-flags-qVPAqdVk.js"; import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { AsyncLocalStorage } from "node:async_hooks"; import { randomUUID } from "node:crypto"; import { performance } from "node:perf_hooks"; //#region src/infra/diagnostics-timeline.ts const OPENCLAW_DIAGNOSTICS_TIMELINE_SCHEMA_VERSION = "openclaw.diagnostics.v1"; const MAX_PENDING_TIMELINE_BYTES = 65536; const activeDiagnosticsTimelineSpan = new AsyncLocalStorage(); const timelineWriter = resolveGlobalSingleton(Symbol.for("openclaw.diagnosticsTimelineWriter"), () => { let pending; let scheduledFlush; let exiting = false; let warnedAboutWrite = false; const createdDirs = /* @__PURE__ */ new Set(); function append(path, content) { try { const dir = dirname(path); if (!createdDirs.has(dir)) { mkdirSync(dir, { recursive: true }); createdDirs.add(dir); } appendRegularFileSync({ filePath: path, content }); } catch (error) { if (!warnedAboutWrite) { warnedAboutWrite = true; console.warn(`[diagnostics] failed to write timeline event: ${String(error)}`); } } } function flush() { if (scheduledFlush) { clearImmediate(scheduledFlush); scheduledFlush = void 0; } const batch = pending; pending = void 0; if (batch) append(batch.path, batch.content); } process.once("exit", () => { exiting = true; flush(); }); return { flush, write(path, content) { const bytes = Buffer.byteLength(content, "utf8"); if (pending && (pending.path !== path || pending.bytes + bytes > MAX_PENDING_TIMELINE_BYTES)) flush(); if (exiting || bytes > MAX_PENDING_TIMELINE_BYTES) { append(path, content); return; } if (pending) { pending.content += content; pending.bytes += bytes; } else pending = { path, content, bytes }; scheduledFlush ??= setImmediate(flush).unref(); } }; }); /** Makes all previously emitted timeline events visible before reading or closing their files. */ function flushDiagnosticsTimeline() { timelineWriter.flush(); } /** Returns true when diagnostics flags and a JSONL output path both allow timeline writes. */ function isDiagnosticsTimelineEnabled(options = {}) { const { config, env = process.env } = options; return (isDiagnosticFlagEnabled("timeline", config, env) || isDiagnosticFlagEnabled("diagnostics.timeline", config, env) || isTruthyEnvValue(env.OPENCLAW_DIAGNOSTICS)) && typeof env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH === "string" && env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH.trim().length > 0; } function normalizeNumber(value) { if (typeof value !== "number" || !Number.isFinite(value)) return; return Math.max(0, Math.round(value * 1e3) / 1e3); } function normalizeAttributes(attributes) { if (!attributes) return; const normalized = {}; for (const [key, value] of Object.entries(attributes)) { if (typeof value === "number") { if (Number.isFinite(value)) normalized[key] = normalizeNumber(value) ?? 0; continue; } if (typeof value === "string" || typeof value === "boolean" || value === null) normalized[key] = value; } return Object.keys(normalized).length > 0 ? normalized : void 0; } function serializeTimelineEvent(event, env) { const attributes = normalizeAttributes(event.attributes); const normalized = { schemaVersion: OPENCLAW_DIAGNOSTICS_TIMELINE_SCHEMA_VERSION, type: event.type, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(), name: event.name, ...env.OPENCLAW_DIAGNOSTICS_RUN_ID ? { runId: env.OPENCLAW_DIAGNOSTICS_RUN_ID } : {}, ...env.OPENCLAW_DIAGNOSTICS_ENV ? { envName: env.OPENCLAW_DIAGNOSTICS_ENV } : {}, pid: process.pid, ...event.runId ? { runId: event.runId } : {}, ...event.envName ? { envName: event.envName } : {}, ...typeof event.pid === "number" ? { pid: event.pid } : {}, ...event.phase ? { phase: event.phase } : {}, ...event.spanId ? { spanId: event.spanId } : {}, ...event.parentSpanId ? { parentSpanId: event.parentSpanId } : {}, ...typeof event.durationMs === "number" ? { durationMs: normalizeNumber(event.durationMs) } : {}, ...event.errorName ? { errorName: event.errorName } : {}, ...event.errorMessage ? { errorMessage: event.errorMessage } : {}, ...typeof event.p50Ms === "number" ? { p50Ms: normalizeNumber(event.p50Ms) } : {}, ...typeof event.p95Ms === "number" ? { p95Ms: normalizeNumber(event.p95Ms) } : {}, ...typeof event.p99Ms === "number" ? { p99Ms: normalizeNumber(event.p99Ms) } : {}, ...typeof event.maxMs === "number" ? { maxMs: normalizeNumber(event.maxMs) } : {}, ...event.activeSpanName ? { activeSpanName: event.activeSpanName } : {}, ...event.provider ? { provider: event.provider } : {}, ...event.operation ? { operation: event.operation } : {}, ...typeof event.ok === "boolean" ? { ok: event.ok } : {}, ...typeof event.status === "number" ? { status: normalizeNumber(event.status) } : {}, ...event.command ? { command: event.command } : {}, ...event.exitCode !== void 0 ? { exitCode: event.exitCode } : {}, ...event.signal !== void 0 ? { signal: event.signal } : {}, ...attributes ? { attributes } : {} }; return `${JSON.stringify(normalized)}\n`; } /** Queues one normalized event; bounded batches append on the next event-loop turn. */ function emitDiagnosticsTimelineEvent(event, options = {}) { const env = options.env ?? process.env; if (!isDiagnosticsTimelineEnabled(options)) return; const path = env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH?.trim(); if (!path) return; timelineWriter.write(path, serializeTimelineEvent(event, env)); } /** Replays a completed span after its activation config becomes available. */ function emitCompletedDiagnosticsTimelineSpan(name, durationMs, options = {}) { if (!isDiagnosticsTimelineEnabled(options)) return; const spanId = randomUUID(); emitDiagnosticsTimelineEvent({ type: "span.start", name, phase: options.phase, spanId, parentSpanId: options.parentSpanId, attributes: options.attributes }, options); emitDiagnosticsTimelineEvent({ type: "span.end", name, phase: options.phase, spanId, parentSpanId: options.parentSpanId, durationMs, attributes: options.attributes }, options); } /** Returns the currently active span so callers can preserve parentage across memoized work. */ function getActiveDiagnosticsTimelineSpan() { return activeDiagnosticsTimelineSpan.getStore(); } function startDiagnosticsTimelineSpan(name, options) { const env = options.env ?? process.env; if (!isDiagnosticsTimelineEnabled({ config: options.config, env })) return; const activeSpan = getActiveDiagnosticsTimelineSpan(); const phase = options.phase ?? activeSpan?.phase; const parentSpanId = options.parentSpanId ?? activeSpan?.spanId; const span = { 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(span, run) { 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) { 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, error) { 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 }); } /** Measures async work as a start/end timeline span, emitting an error span before rethrowing. */ async function measureDiagnosticsTimelineSpan(name, run, options = {}) { const span = startDiagnosticsTimelineSpan(name, options); if (!span) return await run(); try { const result = await runInDiagnosticsTimelineSpan(span, () => run()); emitFinishedDiagnosticsTimelineSpan(span); return result; } catch (error) { emitFailedDiagnosticsTimelineSpan(span, error); throw error; } } /** Measures sync work as a start/end timeline span, emitting an error span before rethrowing. */ function measureDiagnosticsTimelineSpanSync(name, run, options = {}) { const span = startDiagnosticsTimelineSpan(name, options); if (!span) return run(); try { const result = runInDiagnosticsTimelineSpan(span, run); emitFinishedDiagnosticsTimelineSpan(span); return result; } catch (error) { emitFailedDiagnosticsTimelineSpan(span, error); throw error; } } //#endregion export { isDiagnosticsTimelineEnabled as a, getActiveDiagnosticsTimelineSpan as i, emitDiagnosticsTimelineEvent as n, measureDiagnosticsTimelineSpan as o, flushDiagnosticsTimeline as r, measureDiagnosticsTimelineSpanSync as s, emitCompletedDiagnosticsTimelineSpan as t };