From d08cda9d2275ab1207f2943616a6ebd00a4abb49 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Tue, 3 Mar 2026 10:21:29 +0100 Subject: [PATCH] feat: enhance logging in PrismaClient for better query, warning, and error visibility --- nest-backend/src/app.ts | 93 +++++++++++++++++++++++++++++++++- nest-backend/src/lib/prisma.ts | 8 ++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/nest-backend/src/app.ts b/nest-backend/src/app.ts index 5f68658..9cca0aa 100644 --- a/nest-backend/src/app.ts +++ b/nest-backend/src/app.ts @@ -10,6 +10,97 @@ import teamRouter from './routes/team.js'; const app = express(); +// ── Logger ───────────────────────────────────────────────────────────────────── +const R = '\x1b[0m'; +const BOLD = '\x1b[1m'; +const DIM = '\x1b[2m'; +const RED = '\x1b[31m'; +const GREEN = '\x1b[32m'; +const YELLOW = '\x1b[33m'; +const BLUE = '\x1b[34m'; +const MAGENTA = '\x1b[35m'; +const CYAN = '\x1b[36m'; +const WHITE = '\x1b[37m'; +const BG_RED = '\x1b[41m'; +const BG_GREEN = '\x1b[42m'; +const BG_YELLOW = '\x1b[43m'; +const BG_BLUE = '\x1b[44m'; +const BG_MAGENTA = '\x1b[45m'; +const BG_CYAN = '\x1b[46m'; + +const METHOD_STYLE: Record = { + GET: `${BG_BLUE}${WHITE}${BOLD}`, + POST: `${BG_GREEN}${WHITE}${BOLD}`, + PUT: `${BG_YELLOW}${WHITE}${BOLD}`, + PATCH: `${BG_MAGENTA}${WHITE}${BOLD}`, + DELETE: `${BG_RED}${WHITE}${BOLD}`, +}; + +function methodBadge(method: string): string { + const style = METHOD_STYLE[method] ?? `${BG_CYAN}${WHITE}${BOLD}`; + return `${style} ${method.padEnd(6)} ${R}`; +} + +function statusBadge(code: number): string { + if (code < 300) return `${BG_GREEN}${WHITE}${BOLD} ${code} ${R}`; + if (code < 400) return `${BG_YELLOW}${WHITE}${BOLD} ${code} ${R}`; + return `${BG_RED}${WHITE}${BOLD} ${code} ${R}`; +} + +function prettyJson(value: unknown): string { + return JSON.stringify(value, (k, v) => k === 'password' ? '***' : v, 2) + .split('\n') + .map((line, i) => i === 0 ? line : ` ${DIM} ${line}${R}`) + .join('\n'); +} + +const SEP = `${DIM}${'─'.repeat(60)}${R}`; + +app.use((req, res, next) => { + const start = Date.now(); + const ts = new Date().toISOString().replace('T', ' ').slice(0, 23); + + // Skip health check noise + if (req.originalUrl === '/api/health') { next(); return; } + + const originalJson = res.json.bind(res); + let resBody: unknown; + res.json = (body) => { resBody = body; return originalJson(body); }; + + res.on('finish', () => { + const ms = Date.now() - start; + const userId = req.user?.userId ? `${CYAN}${req.user.userId.slice(0, 8)}…${R}` : `${DIM}anon${R}`; + const role = req.user?.role ? `${MAGENTA}${req.user.role}${R}` : `${DIM}-${R}`; + + const hasBody = ['POST', 'PUT', 'PATCH'].includes(req.method) + && req.body && Object.keys(req.body).length > 0; + + const lines: string[] = [ + SEP, + `${DIM}${ts}${R} ${methodBadge(req.method)} ${BOLD}${req.originalUrl}${R}`, + ` ${DIM}┌ user ${R} ${userId} ${DIM}role:${R} ${role}`, + ` ${DIM}└ status ${R} ${statusBadge(res.statusCode)} ${DIM}${ms}ms${R}`, + ]; + + if (hasBody) { + lines.push(` ${GREEN}↑ REQUEST BODY${R}`); + lines.push(` ${DIM} ${prettyJson(req.body)}${R}`); + } + + if (res.statusCode >= 400 && resBody) { + lines.push(` ${RED}↓ ERROR RESPONSE${R}`); + lines.push(` ${DIM} ${prettyJson(resBody)}${R}`); + } else if (res.statusCode < 300 && resBody && req.method !== 'GET') { + lines.push(` ${GREEN}↓ RESPONSE BODY${R}`); + lines.push(` ${DIM} ${prettyJson(resBody)}${R}`); + } + + console.log(lines.join('\n')); + }); + + next(); +}); + app.use(cors({ origin: [ 'http://localhost:5173', // nest-front dev @@ -37,7 +128,7 @@ app.use((_req, res) => res.status(404).json({ error: 'Not found' })); // Global error handler app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { - console.error(err); + console.error(`${BG_RED}${WHITE}${BOLD} UNHANDLED ERROR ${R}`, err); res.status(500).json({ error: 'Internal server error' }); }); diff --git a/nest-backend/src/lib/prisma.ts b/nest-backend/src/lib/prisma.ts index 4e54f7a..3bc967f 100644 --- a/nest-backend/src/lib/prisma.ts +++ b/nest-backend/src/lib/prisma.ts @@ -1,5 +1,11 @@ import { PrismaClient } from '@prisma/client'; -const prisma = new PrismaClient(); +const prisma = new PrismaClient({ + log: [ + { emit: 'stdout', level: 'query' }, + { emit: 'stdout', level: 'warn' }, + { emit: 'stdout', level: 'error' }, + ], +}); export default prisma;