UNPKG

pino-http

Version:

High-speed HTTP logger for Node.js

276 lines (220 loc) 8.88 kB
'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