initttt
This commit is contained in:
281
packages/logger/index.ts
Normal file
281
packages/logger/index.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
|
||||
import { settings } from "@pkg/settings";
|
||||
import type { Err } from "@pkg/result";
|
||||
import winston from "winston";
|
||||
import util from "util";
|
||||
|
||||
process.on("warning", (warning) => {
|
||||
const msg = String(warning?.message || "");
|
||||
const name = String((warning as any)?.name || "");
|
||||
|
||||
if (
|
||||
name === "TimeoutNegativeWarning" ||
|
||||
msg.includes("TimeoutNegativeWarning") ||
|
||||
msg.includes("Timeout duration was set to 1")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(warning);
|
||||
});
|
||||
|
||||
const levels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
} as const;
|
||||
|
||||
const colors = {
|
||||
error: "red",
|
||||
warn: "yellow",
|
||||
info: "green",
|
||||
http: "magenta",
|
||||
debug: "white",
|
||||
};
|
||||
|
||||
const level = () => {
|
||||
const envLevel = process.env.LOG_LEVEL?.toLowerCase();
|
||||
if (envLevel && Object.prototype.hasOwnProperty.call(levels, envLevel)) {
|
||||
return envLevel as keyof typeof levels;
|
||||
}
|
||||
return settings.isDevelopment ? "debug" : "warn";
|
||||
};
|
||||
|
||||
const consoleFormat = winston.format.combine(
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf((info) => {
|
||||
const { level, message, timestamp } = info;
|
||||
|
||||
const extra = Object.fromEntries(
|
||||
Object.entries(info).filter(
|
||||
([key]) => key !== "level" && key !== "message" && key !== "timestamp",
|
||||
),
|
||||
);
|
||||
|
||||
const formattedMessage =
|
||||
message instanceof Error
|
||||
? message.stack || message.message
|
||||
: typeof message === "object"
|
||||
? util.inspect(message, { depth: null, colors: true })
|
||||
: String(message);
|
||||
|
||||
const formattedExtra =
|
||||
Object.keys(extra).length > 0
|
||||
? `\n${util.inspect(extra, { depth: null, colors: true })}`
|
||||
: "";
|
||||
|
||||
return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`;
|
||||
}),
|
||||
);
|
||||
|
||||
winston.addColors(colors);
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: level(),
|
||||
levels,
|
||||
format: consoleFormat,
|
||||
transports: [
|
||||
new winston.transports.Console({ format: consoleFormat }),
|
||||
new OpenTelemetryTransportV3(),
|
||||
],
|
||||
exceptionHandlers: [new winston.transports.Console({ format: consoleFormat })],
|
||||
rejectionHandlers: [new winston.transports.Console({ format: consoleFormat })],
|
||||
});
|
||||
|
||||
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 formatErrorDetail(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.stack || error.message;
|
||||
}
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
if (!error) {
|
||||
return "Unknown error";
|
||||
}
|
||||
if (typeof error === "object") {
|
||||
const candidate = error as {
|
||||
code?: unknown;
|
||||
message?: unknown;
|
||||
description?: unknown;
|
||||
detail?: unknown;
|
||||
error?: unknown;
|
||||
stack?: unknown;
|
||||
};
|
||||
|
||||
const parts = [
|
||||
candidate.code ? `code=${String(candidate.code)}` : null,
|
||||
candidate.message ? `message=${String(candidate.message)}` : null,
|
||||
candidate.description
|
||||
? `description=${String(candidate.description)}`
|
||||
: null,
|
||||
candidate.detail
|
||||
? `detail=${
|
||||
typeof candidate.detail === "string"
|
||||
? candidate.detail
|
||||
: util.inspect(candidate.detail, {
|
||||
depth: null,
|
||||
colors: false,
|
||||
})
|
||||
}`
|
||||
: null,
|
||||
candidate.error
|
||||
? `error=${
|
||||
typeof candidate.error === "string"
|
||||
? candidate.error
|
||||
: util.inspect(candidate.error, {
|
||||
depth: null,
|
||||
colors: false,
|
||||
})
|
||||
}`
|
||||
: null,
|
||||
candidate.stack ? String(candidate.stack) : null,
|
||||
].filter(Boolean);
|
||||
|
||||
return parts.length > 0
|
||||
? parts.join(" | ")
|
||||
: util.inspect(error, { depth: null, colors: false });
|
||||
}
|
||||
|
||||
return String(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 {
|
||||
code: payload.code,
|
||||
message: payload.message,
|
||||
description: payload.description,
|
||||
detail: payload.detail,
|
||||
error: error instanceof Error ? error.message : error,
|
||||
actionable: payload.actionable,
|
||||
} as Err;
|
||||
}
|
||||
|
||||
export { getError, logDomainEvent, logger, stream };
|
||||
export { formatErrorDetail };
|
||||
Reference in New Issue
Block a user