Files
speckle-server/packages/server/logging/expressLogging.ts
T
Iain Sproat 7e89950358 feat(logging): log all http requests and responses (#1416)
- feat(logging): log all http requests and responses
- the auto logger does not log the body, to ensure sensitive payloads are not logged. Unfortunately this means that error messages are not logged either, so need to be manually logged.
- fix(logging): 400 errors should be info not error severity logging
2023-02-27 15:14:56 +00:00

87 lines
2.8 KiB
TypeScript

import { logger } from './logging'
import { randomUUID } from 'crypto'
import HttpLogger from 'pino-http'
import { IncomingMessage } from 'http'
import { NextFunction, Response } from 'express'
import pino, { SerializedResponse } from 'pino'
import { GenReqId } from 'pino-http'
const REQUEST_ID_HEADER = 'x-request-id'
const GenerateRequestId: GenReqId = (req: IncomingMessage) => DetermineRequestId(req)
const DetermineRequestId = (
req: IncomingMessage,
uuidGenerator: () => string = randomUUID
): string => {
const headers = req.headers[REQUEST_ID_HEADER]
if (!Array.isArray(headers)) return headers || uuidGenerator()
return headers[0] || uuidGenerator()
}
export const LoggingExpressMiddleware = HttpLogger({
logger,
autoLogging: true,
genReqId: GenerateRequestId,
customLogLevel: (req, res, err) => {
if (res.statusCode >= 400 && res.statusCode < 500) {
return 'info'
} else if (res.statusCode >= 500 || err) {
return 'error'
} else if (res.statusCode >= 300 && res.statusCode < 400) {
return 'silent'
}
return 'info'
},
// we need to redact any potential sensitive data from being logged.
// as we do not know what headers may be sent in a request by a user or client
// we have to allow list selected headers
serializers: {
req: pino.stdSerializers.wrapRequestSerializer((req) => {
return {
id: req.raw.id,
method: req.raw.method,
path: req.raw.url?.split('?')[0], // Remove query params which might be sensitive
// Allowlist useful headers
headers: {
host: req.raw.headers.host,
'user-agent': req.raw.headers['user-agent'],
'x-request-id': req.raw.headers[REQUEST_ID_HEADER],
referer: req.raw.headers.referer
}
}
}),
res: pino.stdSerializers.wrapResponseSerializer((res) => {
const resRaw = res as SerializedResponse & {
raw: {
headers: Record<string, string>
}
}
return {
statusCode: res.raw.statusCode,
// Allowlist useful headers
headers: {
'content-length': resRaw.raw.headers['content-length'],
'content-type': resRaw.raw.headers['content-type'],
'retry-after': resRaw.raw.headers['retry-after'],
'x-ratelimit-remaining': resRaw.raw.headers['x-ratelimit-remaining'],
'x-ratelimit-reset': resRaw.raw.headers['x-ratelimit-reset'],
'x-request-id': resRaw.raw.headers['x-request-id'],
'x-speckle-meditation': resRaw.raw.headers['x-speckle-meditation']
}
}
})
}
})
export const DetermineRequestIdMiddleware = (
req: IncomingMessage,
res: Response,
next: NextFunction
) => {
const id = DetermineRequestId(req)
res.setHeader(REQUEST_ID_HEADER, id)
next()
}