feat: enhance logging in PrismaClient for better query, warning, and error visibility
This commit is contained in:
@@ -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<string, string> = {
|
||||
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' });
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user