Enhance logging in API calls and request/response middleware with sensitive data filtering and compact format
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m28s

This commit is contained in:
2026-02-28 21:24:25 +01:00
parent 8e18149ad1
commit ed8fb134ad
2 changed files with 84 additions and 40 deletions

View File

@@ -43,6 +43,17 @@ function filterHeaders(headers) {
return out;
}
function formatUTC(d) {
const pad = n => String(n).padStart(2, '0');
const Y = d.getUTCFullYear();
const M = pad(d.getUTCMonth() + 1);
const D = pad(d.getUTCDate());
const h = pad(d.getUTCHours());
const m = pad(d.getUTCMinutes());
const s = pad(d.getUTCSeconds());
return `${Y}-${M}-${D} ${h}:${m}:${s} UTC`;
}
export default function requestResponseLogger(req, res, next) {
try {
const start = Date.now();
@@ -73,51 +84,28 @@ export default function requestResponseLogger(req, res, next) {
res.on('finish', () => {
try {
const duration = Date.now() - start;
const resHeaders = filterHeaders(res.getHeaders ? res.getHeaders() : {});
let loggedResponseBody = null;
const duration = Date.now() - start; // ms
const resContentType = (res.getHeader && (res.getHeader('content-type') || '')).toString().toLowerCase();
if (responseBody === undefined) {
loggedResponseBody = '[none-captured]';
} else if (resContentType.includes('application/json')) {
try {
const parsed = typeof responseBody === 'string' ? JSON.parse(responseBody) : responseBody;
loggedResponseBody = filterValue(null, parsed);
} catch (e) {
loggedResponseBody = typeof responseBody === 'string' ? (responseBody.length > 1000 ? responseBody.slice(0, 1000) + '...[truncated]' : responseBody) : util.inspect(responseBody);
}
} else if (resContentType.includes('text/') || resContentType.includes('application/xml')) {
const asStr = typeof responseBody === 'string' ? responseBody : util.inspect(responseBody);
loggedResponseBody = asStr.length > 1000 ? asStr.slice(0, 1000) + '...[truncated]' : asStr;
} else {
loggedResponseBody = '[not-logged-due-to-content-type]';
}
const log = {
time: new Date().toISOString(),
ip,
method,
url: originalUrl,
status: res.statusCode,
duration_ms: duration,
request: {
headers: reqHeaders,
query: filterValue(null, req.query || {}),
body: reqBody,
},
response: {
headers: resHeaders,
body: loggedResponseBody,
},
};
// Compact key=value log (no bodies, sensitive data filtered)
const tokens = [];
tokens.push(`time=${formatUTC(new Date())}`);
tokens.push(`method=${method}`);
tokens.push(`path=${originalUrl.replace(/\s/g, '%20')}`);
const fmt = resContentType.includes('json') ? 'json' : (resContentType.includes('html') ? 'html' : 'other');
tokens.push(`format=${fmt}`);
tokens.push(`status=${res.statusCode}`);
tokens.push(`duration=${(duration).toFixed(2)}`);
// Output as single-line JSON for log aggregation
// Optional: content-length if available
try {
console.info(JSON.stringify(log));
const cl = res.getHeader && (res.getHeader('content-length') || res.getHeader('Content-Length'));
if (cl) tokens.push(`length=${String(cl)}`);
} catch (e) {
console.info('LOG_ERROR', util.inspect(log));
// ignore
}
console.info(tokens.join(' '));
} catch (e) {
console.error('RequestLogger error:', e);
}