Files
UHD_Media_Tool/packages/tasks/src/logger.ts
T
rootandClaude Fable 5.1 1262340856 feat(tasks): OpenWorkflow-backed TaskEngine, task registry, outbox relay and op-ledger fencing (W1-4)
- TaskEngine port + OpenWorkflowEngine ([email protected].1, schema openworkflow):
  enqueue/signal/cancel/get/list/startWorker/stop
- defineTask registry with zod input schema; 9 stub task definitions (§3.1-8)
- TaskContext: step.run/sleep/waitForFact, redacting logger, traceId,
  attemptToken, abortSignal driven by PG cancel flag
- signal persists fact to tasks_fact first; waitForFact checks DB before waiting
- OutboxRelay: FOR UPDATE SKIP LOCKED, idempotencyKey outbox:<id>
- withAttemptToken fencing on tasks_op_ledger (own migration, merged in W2)
- tests: kill -9 crash recovery (child worker), cancel, idempotency,
  concurrent relays, early fact, lease-loss fencing

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-27 17:51:21 +08:00

65 lines
2.3 KiB
TypeScript

export type LogLevel = "debug" | "info" | "warn" | "error";
export interface LogRecord {
level: LogLevel;
msg: string;
at: string;
fields: Record<string, unknown>;
}
export type LogSink = (record: LogRecord) => void;
export interface TaskLogger {
debug(msg: string, fields?: Record<string, unknown>): void;
info(msg: string, fields?: Record<string, unknown>): void;
warn(msg: string, fields?: Record<string, unknown>): void;
error(msg: string, fields?: Record<string, unknown>): void;
child(fields: Record<string, unknown>): TaskLogger;
}
const SENSITIVE_KEY = /pass(word|key)?|secret|token|cookie|authorization|api[-_]?key|credential|session/i;
// URL 查询参数里的凭证(passkey、token、apikey、sign 等)
const SENSITIVE_QUERY = /([?&](?:passkey|token|apikey|api_key|key|sign|secret|auth)=)[^&#\s]+/gi;
const REDACTED = "***";
/** 深拷贝并脱敏:敏感键名整体替换,字符串里的凭证型 URL 参数替换 */
export function redact(value: unknown, depth = 0): unknown {
if (depth > 8) return "[depth]";
if (typeof value === "string") return value.replace(SENSITIVE_QUERY, `$1${REDACTED}`);
if (Array.isArray(value)) return value.map((v) => redact(v, depth + 1));
if (value instanceof Error) return { name: value.name, message: redact(value.message, depth + 1) };
if (value && typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(value)) out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1);
return out;
}
return value;
}
export const consoleSink: LogSink = (r) => {
const line = JSON.stringify(r);
if (r.level === "error" || r.level === "warn") console.error(line);
else console.log(line);
};
export const silentSink: LogSink = () => {};
export function createLogger(sink: LogSink = consoleSink, base: Record<string, unknown> = {}): TaskLogger {
const emit =
(level: LogLevel) =>
(msg: string, fields: Record<string, unknown> = {}) =>
sink({
level,
msg: redact(msg) as string,
at: new Date().toISOString(),
fields: redact({ ...base, ...fields }) as Record<string, unknown>,
});
return {
debug: emit("debug"),
info: emit("info"),
warn: emit("warn"),
error: emit("error"),
child: (fields) => createLogger(sink, { ...base, ...fields }),
};
}