TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import { redactSecrets } from "@/lib/crypto/keys";23type Level = "debug" | "info" | "warn" | "error";45const LEVELS: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 };6const threshold = LEVELS[(process.env.LOG_LEVEL as Level) ?? (process.env.NODE_ENV === "production" ? "info" : "debug")] ?? 20;78const FORBIDDEN_KEYS = /(api[_-]?key|password|secret|token|authorization|cookie|prompt|content|messages)/i;910function sanitize(value: unknown, depth = 0): unknown {11 if (depth > 4) return "[depth]";12 if (typeof value === "string") return redactSecrets(value).slice(0, 2000);13 if (Array.isArray(value)) return value.slice(0, 50).map((v) => sanitize(v, depth + 1));14 if (value && typeof value === "object") {15 const out: Record<string, unknown> = {};16 for (const [k, v] of Object.entries(value as Record<string, unknown>)) {17 out[k] = FORBIDDEN_KEYS.test(k) ? "[redacted]" : sanitize(v, depth + 1);18 }19 return out;20 }21 return value;22}2324function emit(level: Level, msg: string, meta?: Record<string, unknown>) {25 if (LEVELS[level] < threshold) return;26 const line = JSON.stringify({ t: new Date().toISOString(), level, msg: redactSecrets(msg), ...(meta ? (sanitize(meta) as object) : {}) });27 if (level === "error") console.error(line);28 else if (level === "warn") console.warn(line);29 else console.log(line);30}3132/**33 * Structured JSON logger. Keys that look sensitive are redacted automatically,34 * and any string is passed through `redactSecrets`. Never log prompts or keys on purpose.35 */36export const log = {37 debug: (msg: string, meta?: Record<string, unknown>) => emit("debug", msg, meta),38 info: (msg: string, meta?: Record<string, unknown>) => emit("info", msg, meta),39 warn: (msg: string, meta?: Record<string, unknown>) => emit("warn", msg, meta),40 error: (msg: string, meta?: Record<string, unknown>) => emit("error", msg, meta),41};42