Skip to content

Commit 7970361

Browse files
authored
Merge pull request #57 from SOURAV-ROY/v2
feat: Add structured request logger, update Vercel deployment configuration
2 parents e59ea40 + 25a025a commit 7970361

2 files changed

Lines changed: 122 additions & 14 deletions

File tree

middleware/requestLogger.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Structured request logger — emits JSON similar to error logger
2+
const redact = (obj) => {
3+
if (!obj || typeof obj !== "object") return obj;
4+
5+
const SENSITIVE =
6+
/(password|passwd|pwd|token|access_token|secret|ssn|card|cvc|cvv|authorization)/i;
7+
const out = Array.isArray(obj) ? [] : {};
8+
9+
Object.keys(obj).forEach((k) => {
10+
try {
11+
const v = obj[k];
12+
if (SENSITIVE.test(k)) {
13+
out[k] = "[REDACTED]";
14+
return;
15+
}
16+
17+
if (typeof v === "string") {
18+
// Truncate very large strings
19+
out[k] = v.length > 2000 ? v.slice(0, 2000) + "...[TRUNCATED]" : v;
20+
return;
21+
}
22+
23+
// shallow copy for simple nested objects/arrays
24+
if (typeof v === "object") {
25+
out[k] = Array.isArray(v) ? v.slice(0, 20) : "[OBJECT]";
26+
return;
27+
}
28+
29+
out[k] = v;
30+
} catch (e) {
31+
out[k] = "[UNSERIALIZABLE]";
32+
}
33+
});
34+
35+
return out;
36+
};
37+
38+
const requestLogger = (req, res, next) => {
39+
if (process.env.NODE_ENV === "test") return next();
40+
41+
const start = process.hrtime.bigint();
42+
43+
const onFinish = () => {
44+
try {
45+
const end = process.hrtime.bigint();
46+
const durationMs = Number(end - start) / 1e6;
47+
48+
const statusCode = res.statusCode || 0;
49+
let level = "info";
50+
if (statusCode >= 500) level = "error";
51+
else if (statusCode >= 400) level = "warn";
52+
53+
const contentType = (req.headers["content-type"] || "").toLowerCase();
54+
const isMultipart =
55+
contentType.includes("multipart/form-data") ||
56+
contentType.includes("application/octet-stream");
57+
58+
const logEntry = {
59+
timestamp: new Date().toISOString(),
60+
level,
61+
message: `${req.method} ${req.originalUrl}`,
62+
method: req.method,
63+
path: req.originalUrl,
64+
statusCode,
65+
responseTimeMs: Number(durationMs.toFixed(3)),
66+
ip:
67+
req.ip ||
68+
req.headers["x-forwarded-for"] ||
69+
(req.connection && req.connection.remoteAddress) ||
70+
null,
71+
userAgent: req.get && req.get("user-agent"),
72+
source: req.headers["x-request-id"] || "unknown",
73+
params: redact(req.params || {}),
74+
query: redact(req.query || {}),
75+
body: isMultipart
76+
? "[multipart/form-data or binary]"
77+
: redact(req.body || {}),
78+
};
79+
80+
// Don't log auth header or cookies raw
81+
const headers = { ...req.headers };
82+
if (headers.authorization) headers.authorization = "[REDACTED]";
83+
if (headers.cookie) headers.cookie = "[REDACTED]";
84+
logEntry.headers = headers;
85+
86+
// Emit as JSON so ingestion systems can parse it
87+
if (level === "error") console.error(JSON.stringify(logEntry));
88+
else console.log(JSON.stringify(logEntry));
89+
} catch (e) {
90+
// avoid throwing from logger
91+
console.error(
92+
JSON.stringify({
93+
timestamp: new Date().toISOString(),
94+
level: "error",
95+
message: "requestLogger failure",
96+
error: e.message,
97+
}),
98+
);
99+
}
100+
};
101+
102+
res.on("finish", onFinish);
103+
res.on("close", onFinish);
104+
105+
next();
106+
};
107+
108+
module.exports = requestLogger;

vercel.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
{
2-
"version": 2,
3-
"builds": [
4-
{
5-
"src": "./index.js",
6-
"use": "@vercel/node"
7-
}
8-
],
9-
"routes": [
10-
{
11-
"src": "/(.*)",
12-
"dest": "/"
13-
}
14-
]
1+
{
2+
"version": 2,
3+
"builds": [
4+
{
5+
"src": "./index.js",
6+
"use": "@vercel/node"
7+
}
8+
],
9+
"routes": [
10+
{
11+
"src": "/(.*)",
12+
"dest": "/"
13+
}
14+
]
1515
}

0 commit comments

Comments
 (0)