import type { Request } from 'express' /** Fields stripped from logged request bodies to avoid leaking secrets. */ const SENSITIVE_FIELDS: ReadonlySet = new Set([ 'password', 'token', 'secret', 'code', 'sessionKey', 'encryptedData', 'iv', ]) const BODY_METHODS: ReadonlySet = new Set(['POST', 'PUT', 'PATCH']) /** Max characters of JSON-serialised body/query included in a log line. */ const MAX_LOG_PAYLOAD = 2048 function truncate(value: string): string { return value.length > MAX_LOG_PAYLOAD ? `${value.slice(0, MAX_LOG_PAYLOAD)}…(truncated)` : value } export function sanitizeBody( body: Record | undefined, ): Record | undefined { if (!body || typeof body !== 'object') return undefined const keys = Object.keys(body) if (keys.length === 0) return undefined const result: Record = {} for (const key of keys) { result[key] = SENSITIVE_FIELDS.has(key) ? '***' : body[key] } return result } /** * Build a human-readable suffix for a log line: * ` | query={…} body={…}` * Returns an empty string when there is nothing to append. */ export function formatRequestExtras(request: Request): string { const parts: string[] = [] const query = request.query if (query && Object.keys(query).length > 0) { parts.push(`query=${truncate(JSON.stringify(query))}`) } if (BODY_METHODS.has(request.method)) { const sanitized = sanitizeBody(request.body as Record) if (sanitized) { parts.push(`body=${truncate(JSON.stringify(sanitized))}`) } } return parts.length > 0 ? ` | ${parts.join(' ')}` : '' }