Stop dumping every byte as a number in event I/O, workflow test results, and script dry-runs. Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.2 KiB
JavaScript
87 lines
2.2 KiB
JavaScript
import pino from "pino";
|
|
import { isBinary, summarizeBinary } from "../../json-preview.js";
|
|
import { redactString } from "../../secret-value.js";
|
|
|
|
const LEVEL_TO_NUM = {
|
|
trace: 10,
|
|
debug: 20,
|
|
info: 30,
|
|
warn: 40,
|
|
error: 50,
|
|
fatal: 60,
|
|
};
|
|
|
|
/**
|
|
* Pino logger that collects log lines in memory (no SQLite / process logger).
|
|
*
|
|
* @returns {{ log: import("pino").Logger, logs: Array<{ ts: string, level: number, msg?: string | null, payload?: unknown }> }}
|
|
*/
|
|
export function createDryRunLogger() {
|
|
/** @type {Array<{ ts: string, level: number, msg?: string | null, payload?: unknown }>} */
|
|
const logs = [];
|
|
|
|
const dest = {
|
|
write(line) {
|
|
let record;
|
|
try {
|
|
record = JSON.parse(
|
|
redactString(typeof line === "string" ? line : String(line)),
|
|
);
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
const ts =
|
|
typeof record.time === "number"
|
|
? new Date(record.time).toISOString()
|
|
: typeof record.time === "string"
|
|
? record.time
|
|
: new Date().toISOString();
|
|
|
|
const level =
|
|
typeof record.level === "number"
|
|
? record.level
|
|
: LEVEL_TO_NUM[record.level] ?? LEVEL_TO_NUM.info;
|
|
|
|
const { time: _time, level: _level, msg = null, ...rest } = record;
|
|
logs.push({
|
|
ts,
|
|
level,
|
|
msg: typeof msg === "string" ? redactString(msg) : msg,
|
|
payload: Object.keys(rest).length ? rest : null,
|
|
});
|
|
},
|
|
};
|
|
|
|
const log = pino({ level: "trace" }, dest);
|
|
return { log, logs };
|
|
}
|
|
|
|
/**
|
|
* @param {unknown} value
|
|
*/
|
|
export function safeSerialize(value) {
|
|
const seen = new WeakSet();
|
|
try {
|
|
return JSON.parse(
|
|
redactString(
|
|
JSON.stringify(value, function (key, v) {
|
|
const raw = this[key];
|
|
if (isBinary(raw)) return summarizeBinary(raw);
|
|
if (typeof v === "bigint") return v.toString();
|
|
if (typeof v === "object" && v !== null) {
|
|
if (seen.has(v)) return "[Circular]";
|
|
seen.add(v);
|
|
}
|
|
return v;
|
|
}),
|
|
),
|
|
);
|
|
} catch (err) {
|
|
return {
|
|
error: "output could not be serialized",
|
|
detail: err instanceof Error ? err.message : String(err),
|
|
};
|
|
}
|
|
}
|