61 lines
1.6 KiB
TypeScript
61 lines
1.6 KiB
TypeScript
import type { Request } from 'express'
|
|
|
|
/** Fields stripped from logged request bodies to avoid leaking secrets. */
|
|
const SENSITIVE_FIELDS: ReadonlySet<string> = new Set([
|
|
'password',
|
|
'token',
|
|
'secret',
|
|
'code',
|
|
'sessionKey',
|
|
'encryptedData',
|
|
'iv',
|
|
])
|
|
|
|
const BODY_METHODS: ReadonlySet<string> = 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<string, unknown> | undefined,
|
|
): Record<string, unknown> | undefined {
|
|
if (!body || typeof body !== 'object') return undefined
|
|
const keys = Object.keys(body)
|
|
if (keys.length === 0) return undefined
|
|
|
|
const result: Record<string, unknown> = {}
|
|
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<string, unknown>)
|
|
if (sanitized) {
|
|
parts.push(`body=${truncate(JSON.stringify(sanitized))}`)
|
|
}
|
|
}
|
|
|
|
return parts.length > 0 ? ` | ${parts.join(' ')}` : ''
|
|
}
|