- Create apply-migrations.ts and jobs-worker.ts scripts (package.json references) - Convert badge leaderboard from $queryRawUnsafe to $queryRaw with Prisma.sql templates - Fix OAuth email binding: add oauth_require_link site setting, skip 2FA-protected accounts - Add per-user 2FA rate limiting (5/30s) to prevent TOTP brute-force - Add structured JSON logger with levels (debug/info/warn/error) - Split 341-line HomePage into GuestView + UserView components - Add OpenAPI v3.1 spec at /api/openapi.json - Add LOG_LEVEL env var, regenerate Prisma client - Add mysql2 dependency for migration scripts - All 58 tests pass, typecheck clean
72 lines
1.8 KiB
TypeScript
72 lines
1.8 KiB
TypeScript
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
|
|
interface LogEntry {
|
|
level: LogLevel;
|
|
message: string;
|
|
timestamp: string;
|
|
requestId?: string;
|
|
module?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
debug: 0,
|
|
info: 1,
|
|
warn: 2,
|
|
error: 3,
|
|
};
|
|
|
|
const currentLevel: LogLevel =
|
|
(process.env.LOG_LEVEL as LogLevel) ?? (process.env.NODE_ENV === "production" ? "info" : "debug");
|
|
|
|
let requestIdCounter = 0;
|
|
|
|
export function generateRequestId(): string {
|
|
requestIdCounter += 1;
|
|
return `${Date.now().toString(36)}-${requestIdCounter.toString(36)}`;
|
|
}
|
|
|
|
function shouldLog(level: LogLevel): boolean {
|
|
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
}
|
|
|
|
function formatLog(entry: LogEntry): string {
|
|
return JSON.stringify(entry);
|
|
}
|
|
|
|
function writeLog(entry: LogEntry): void {
|
|
if (!shouldLog(entry.level)) return;
|
|
|
|
const formatted = formatLog(entry);
|
|
|
|
switch (entry.level) {
|
|
case "error":
|
|
console.error(formatted);
|
|
break;
|
|
case "warn":
|
|
console.warn(formatted);
|
|
break;
|
|
default:
|
|
console.log(formatted);
|
|
break;
|
|
}
|
|
}
|
|
|
|
export const logger = {
|
|
debug(message: string, meta: Record<string, unknown> = {}): void {
|
|
writeLog({ level: "debug", message, timestamp: new Date().toISOString(), ...meta });
|
|
},
|
|
|
|
info(message: string, meta: Record<string, unknown> = {}): void {
|
|
writeLog({ level: "info", message, timestamp: new Date().toISOString(), ...meta });
|
|
},
|
|
|
|
warn(message: string, meta: Record<string, unknown> = {}): void {
|
|
writeLog({ level: "warn", message, timestamp: new Date().toISOString(), ...meta });
|
|
},
|
|
|
|
error(message: string, meta: Record<string, unknown> = {}): void {
|
|
writeLog({ level: "error", message, timestamp: new Date().toISOString(), ...meta });
|
|
},
|
|
};
|