levelled up logging, albeit with a bit of verbosity...

This commit is contained in:
user
2026-03-01 04:36:17 +02:00
parent 596dcc78fc
commit 5bf1148a4f
6 changed files with 731 additions and 277 deletions

View File

@@ -88,6 +88,126 @@ const logger = winston.createLogger({
const stream = { write: (message: string) => logger.http(message.trim()) };
type LogLevel = keyof typeof levels;
type ErrorKind = "validation" | "auth" | "db" | "external" | "unknown";
type FlowCtxLike = {
flowId: string;
userId?: string;
sessionId?: string;
};
const REDACTED_KEYS = new Set([
"password",
"code",
"secret",
"token",
"verificationtoken",
"backupcodes",
"authorization",
"headers",
"hash",
]);
function sanitizeMeta(input: Record<string, unknown>) {
const sanitized = Object.fromEntries(
Object.entries(input).map(([key, value]) => {
if (value === undefined) {
return [key, undefined];
}
const lowered = key.toLowerCase();
if (REDACTED_KEYS.has(lowered)) {
return [key, "[REDACTED]"];
}
return [key, value];
}),
);
return Object.fromEntries(
Object.entries(sanitized).filter(([, value]) => value !== undefined),
);
}
function classifyError(error: unknown): ErrorKind {
if (!error) return "unknown";
if (typeof error === "object" && error && "code" in error) {
const code = String((error as { code?: unknown }).code ?? "").toUpperCase();
if (code.includes("AUTH") || code.includes("UNAUTHORIZED")) return "auth";
if (code.includes("VALIDATION") || code.includes("INVALID")) return "validation";
if (code.includes("DB") || code.includes("DATABASE")) return "db";
}
return "unknown";
}
function errorMessage(error: unknown) {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
if (error && typeof error === "object" && "message" in error) {
return String((error as { message?: unknown }).message ?? "Unknown error");
}
return "Unknown error";
}
function writeLog(level: LogLevel, message: string, payload: Record<string, unknown>) {
switch (level) {
case "error":
logger.error(message, payload);
return;
case "warn":
logger.warn(message, payload);
return;
case "debug":
logger.debug(message, payload);
return;
case "http":
logger.http(message, payload);
return;
default:
logger.info(message, payload);
}
}
function logDomainEvent({
level = "info",
event,
fctx,
durationMs,
error,
retryable,
meta,
}: {
level?: LogLevel;
event: string;
fctx: FlowCtxLike;
durationMs?: number;
error?: unknown;
retryable?: boolean;
meta?: Record<string, unknown>;
}) {
const payload: Record<string, unknown> = {
event,
flowId: fctx.flowId,
userId: fctx.userId,
sessionId: fctx.sessionId,
};
if (durationMs !== undefined) payload.duration_ms = durationMs;
if (retryable !== undefined) payload.retryable = retryable;
if (error !== undefined) {
payload.error_kind = classifyError(error);
payload.error_message = errorMessage(error);
if (error && typeof error === "object" && "code" in error) {
payload.error_code = String(
(error as { code?: unknown }).code ?? "UNKNOWN",
);
}
}
if (meta) {
Object.assign(payload, sanitizeMeta(meta));
}
writeLog(level, event, payload);
}
function getError(payload: Err, error?: any) {
logger.error(JSON.stringify({ payload, error }, null, 2));
return {
@@ -100,4 +220,4 @@ function getError(payload: Err, error?: any) {
} as Err;
}
export { getError, logger, stream };
export { getError, logDomainEvent, logger, stream };