pino-http
Version:
High-speed HTTP logger for Node.js
276 lines (220 loc) • 8.88 kB
JavaScript
'use strict'
const { pino, symbols: { stringifySym, chindingsSym } } = require('pino')
const serializers = require('pino-std-serializers')
const getCallerFile = require('get-caller-file')
const startTime = Symbol('startTime')
const reqObject = Symbol('reqObject')
function pinoLogger (opts, stream) {
if (opts && opts._writableState) {
stream = opts
opts = null
}
opts = Object.assign({}, opts)
opts.customAttributeKeys = opts.customAttributeKeys || {}
const reqKey = opts.customAttributeKeys.req || 'req'
const resKey = opts.customAttributeKeys.res || 'res'
const errKey = opts.customAttributeKeys.err || 'err'
const requestIdKey = opts.customAttributeKeys.reqId || 'reqId'
const responseTimeKey = opts.customAttributeKeys.responseTime || 'responseTime'
delete opts.customAttributeKeys
const customProps = opts.customProps || undefined
opts.wrapSerializers = 'wrapSerializers' in opts ? opts.wrapSerializers : true
if (opts.wrapSerializers) {
opts.serializers = Object.assign({}, opts.serializers)
const requestSerializer = opts.serializers[reqKey] || opts.serializers.req || serializers.req
const responseSerializer = opts.serializers[resKey] || opts.serializers.res || serializers.res
const errorSerializer = opts.serializers[errKey] || opts.serializers.err || serializers.err
opts.serializers[reqKey] = serializers.wrapRequestSerializer(requestSerializer)
opts.serializers[resKey] = serializers.wrapResponseSerializer(responseSerializer)
opts.serializers[errKey] = serializers.wrapErrorSerializer(errorSerializer)
}
delete opts.wrapSerializers
if (opts.useLevel && opts.customLogLevel) {
throw new Error('You can\'t pass \'useLevel\' and \'customLogLevel\' together')
}
function getValidLogLevel (level, defaultValue = 'info') {
if (level && typeof level === 'string') {
const logLevel = level.trim()
if (validLogLevels.includes(logLevel) === true) {
return logLevel
}
}
return defaultValue
}
function getLogLevelFromCustomLogLevel (customLogLevel, useLevel, res, err, req) {
return customLogLevel ? getValidLogLevel(customLogLevel(req, res, err), useLevel) : useLevel
}
const customLogLevel = opts.customLogLevel
delete opts.customLogLevel
const theStream = opts.stream || stream
delete opts.stream
const autoLogging = (opts.autoLogging !== false)
const autoLoggingIgnore = opts.autoLogging && opts.autoLogging.ignore ? opts.autoLogging.ignore : null
delete opts.autoLogging
const onRequestReceivedObject = getFunctionOrDefault(opts.customReceivedObject, undefined)
const receivedMessage = getFunctionOrDefault(opts.customReceivedMessage, undefined)
const onRequestSuccessObject = getFunctionOrDefault(opts.customSuccessObject, defaultSuccessfulRequestObjectProvider)
const successMessage = getFunctionOrDefault(opts.customSuccessMessage, defaultSuccessfulRequestMessageProvider)
const onRequestErrorObject = getFunctionOrDefault(opts.customErrorObject, defaultFailedRequestObjectProvider)
const errorMessage = getFunctionOrDefault(opts.customErrorMessage, defaultFailedRequestMessageProvider)
delete opts.customSuccessfulMessage
delete opts.customErroredMessage
const quietReqLogger = !!opts.quietReqLogger
const quietResLogger = !!opts.quietResLogger
const logger = wrapChild(opts, theStream)
const validLogLevels = Object.keys(logger.levels.values).concat('silent')
const useLevel = getValidLogLevel(opts.useLevel)
delete opts.useLevel
const genReqId = reqIdGenFactory(opts.genReqId)
const result = (req, res, next) => {
return loggingMiddleware(logger, req, res, next)
}
result.logger = logger
return result
function onResFinished (res, logger, err) {
let log = logger
const responseTime = Date.now() - res[startTime]
const req = res[reqObject]
const level = getLogLevelFromCustomLogLevel(customLogLevel, useLevel, res, err, req)
if (level === 'silent') {
return
}
const customPropBindings = (typeof customProps === 'function') ? customProps(req, res) : customProps
if (customPropBindings) {
const customPropBindingStr = logger[stringifySym](customPropBindings).replace(/[{}]/g, '')
const customPropBindingsStr = logger[chindingsSym]
if (!customPropBindingsStr.includes(customPropBindingStr)) {
log = logger.child(customPropBindings)
}
}
if (err || res.err || res.statusCode >= 500) {
const error = err || res.err || new Error('failed with status code ' + res.statusCode)
log[level](
onRequestErrorObject(req, res, error, {
[resKey]: res,
[errKey]: error,
[responseTimeKey]: responseTime
}),
errorMessage(req, res, error, responseTime)
)
return
}
log[level](
onRequestSuccessObject(req, res, {
[resKey]: res,
[responseTimeKey]: responseTime
}),
successMessage(req, res, responseTime)
)
}
function loggingMiddleware (logger, req, res, next) {
let shouldLogSuccess = true
req.id = req.id || genReqId(req, res)
const log = quietReqLogger ? logger.child({ [requestIdKey]: req.id }) : logger
let fullReqLogger = log.child({ [reqKey]: req })
const customPropBindings = (typeof customProps === 'function') ? customProps(req, res) : customProps
if (customPropBindings) {
fullReqLogger = fullReqLogger.child(customPropBindings)
}
const responseLogger = quietResLogger ? log : fullReqLogger
const requestLogger = quietReqLogger ? log : fullReqLogger
if (!res.log) {
res.log = responseLogger
}
if (Array.isArray(res.allLogs) === false) {
res.allLogs = []
}
res.allLogs.push(responseLogger)
if (!req.log) {
req.log = requestLogger
}
if (!req.allLogs) {
req.allLogs = []
}
req.allLogs.push(requestLogger)
res[startTime] = res[startTime] || Date.now()
// carry request to be executed when response is finished
res[reqObject] = req
const onResponseComplete = (err) => {
res.removeListener('close', onResponseComplete)
res.removeListener('finish', onResponseComplete)
res.removeListener('error', onResponseComplete)
return onResFinished(res, responseLogger, err)
}
if (autoLogging) {
if (autoLoggingIgnore !== null && shouldLogSuccess === true) {
const isIgnored = autoLoggingIgnore(req)
shouldLogSuccess = !isIgnored
}
if (shouldLogSuccess) {
const shouldLogReceived = receivedMessage !== undefined || onRequestReceivedObject !== undefined
if (shouldLogReceived) {
const level = getLogLevelFromCustomLogLevel(customLogLevel, useLevel, res, undefined, req)
const receivedObjectResult = onRequestReceivedObject !== undefined ? onRequestReceivedObject(req, res, undefined) : {}
const receivedStringResult = receivedMessage !== undefined ? receivedMessage(req, res) : undefined
requestLogger[level](receivedObjectResult, receivedStringResult)
}
res.on('close', onResponseComplete)
res.on('finish', onResponseComplete)
}
res.on('error', onResponseComplete)
}
if (next) {
next()
}
}
}
function wrapChild (opts, stream) {
const prevLogger = opts.logger
const prevGenReqId = opts.genReqId
let logger = null
if (prevLogger) {
opts.logger = undefined
opts.genReqId = undefined
logger = prevLogger.child({}, opts)
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
if (opts.transport && !opts.transport.caller) {
opts.transport.caller = getCallerFile()
}
logger = pino(opts, stream)
}
return logger
}
function reqIdGenFactory (func) {
if (typeof func === 'function') return func
const maxInt = 2147483647
let nextReqId = 0
return function genReqId (req, res) {
return req.id || (nextReqId = (nextReqId + 1) & maxInt)
}
}
function getFunctionOrDefault (value, defaultValue) {
if (value && typeof value === 'function') {
return value
}
return defaultValue
}
function defaultSuccessfulRequestObjectProvider (req, res, successObject) {
return successObject
}
function defaultFailedRequestObjectProvider (req, res, error, errorObject) {
return errorObject
}
function defaultFailedRequestMessageProvider () {
return 'request errored'
}
function defaultSuccessfulRequestMessageProvider (req, res) {
/* istanbul ignore next */
return !req.readableAborted && res.writableEnded ? 'request completed' : 'request aborted'
}
module.exports = pinoLogger
module.exports.stdSerializers = {
err: serializers.err,
req: serializers.req,
res: serializers.res
}
module.exports.startTime = startTime
module.exports.default = pinoLogger
module.exports.pinoHttp = pinoLogger