feat: add diagnostics and telemetry runtime
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import type { DiagnosticsPort } from "../../application/ports/diagnostics-port.js";
|
||||
import {
|
||||
projectDiagnosticRecord,
|
||||
safeErrorKind,
|
||||
type DiagnosticRecord,
|
||||
type DiagnosticRecordInput,
|
||||
} from "../../contracts/diagnostics.js";
|
||||
import { projectTelemetryEvent } from "../../contracts/telemetry.js";
|
||||
|
||||
export const noOpDiagnostics: DiagnosticsPort = Object.freeze({
|
||||
record() {},
|
||||
});
|
||||
|
||||
export function createDiagnosticsAdapter(
|
||||
options: Readonly<{
|
||||
maxEntries?: number;
|
||||
now?: () => number;
|
||||
sink?: (record: DiagnosticRecord) => void;
|
||||
}> = {},
|
||||
) {
|
||||
const maxEntries = Math.max(1, options.maxEntries ?? 100);
|
||||
const entries: DiagnosticRecord[] = [];
|
||||
const droppedReasons = new Map<string, number>();
|
||||
|
||||
function drop(reason: string) {
|
||||
droppedReasons.set(reason, (droppedReasons.get(reason) ?? 0) + 1);
|
||||
}
|
||||
|
||||
function record(input: DiagnosticRecordInput) {
|
||||
try {
|
||||
const projected = projectDiagnosticRecord(input, options.now);
|
||||
if (!projected.success) {
|
||||
drop(projected.reason);
|
||||
return;
|
||||
}
|
||||
if (entries.length >= maxEntries) {
|
||||
entries.shift();
|
||||
drop("queue-full");
|
||||
}
|
||||
entries.push(projected.record);
|
||||
try {
|
||||
options.sink?.(projected.record);
|
||||
} catch {
|
||||
drop("sink-failure");
|
||||
}
|
||||
} catch {
|
||||
drop("serialization-failure");
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
record,
|
||||
entries: () => structuredClone(entries) as readonly DiagnosticRecord[],
|
||||
dropped: () => Object.freeze(Object.fromEntries(droppedReasons)),
|
||||
});
|
||||
}
|
||||
|
||||
type BootSafeContext = Readonly<{
|
||||
kind?: string;
|
||||
buildId?: string;
|
||||
configSchemaVersion?: string;
|
||||
supportReference?: string;
|
||||
}>;
|
||||
|
||||
let lastBootEvidence:
|
||||
| Readonly<{
|
||||
diagnostic: DiagnosticRecord | null;
|
||||
telemetry: Readonly<Record<string, unknown>> | null;
|
||||
}>
|
||||
| undefined;
|
||||
|
||||
export function recordBootFailure(
|
||||
error: unknown,
|
||||
safe: BootSafeContext,
|
||||
now: () => number = Date.now,
|
||||
) {
|
||||
const errorKind =
|
||||
typeof safe.kind === "string" ? safe.kind : safeErrorKind(error);
|
||||
const attributes = {
|
||||
error_kind: errorKind,
|
||||
build_id: safe.buildId ?? "unknown",
|
||||
config_schema_version: safe.configSchemaVersion ?? "unknown",
|
||||
};
|
||||
const diagnostic = projectDiagnosticRecord(
|
||||
{
|
||||
level: "error",
|
||||
eventId: "app.boot.failed",
|
||||
context: attributes,
|
||||
},
|
||||
now,
|
||||
);
|
||||
const telemetry = projectTelemetryEvent("app.boot.failed", attributes, now);
|
||||
lastBootEvidence = Object.freeze({
|
||||
diagnostic: diagnostic.success ? diagnostic.record : null,
|
||||
telemetry: telemetry.success ? telemetry.event : null,
|
||||
});
|
||||
return lastBootEvidence;
|
||||
}
|
||||
|
||||
export function getLastBootEvidence() {
|
||||
return lastBootEvidence ? structuredClone(lastBootEvidence) : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user