UNPKG

pino-http

Version:

High-speed HTTP logger for Node.js

1,550 lines (1,305 loc) 36.8 kB
'use strict' const test = require('tap').test const http = require('http') const net = require('net') const stream = require('stream') const pinoHttp = require('../') const pino = require('pino') const split = require('split2') const { join } = require('path') const ERROR_URL = '/make-error' const noop = function () {} const DEFAULT_REQUEST_RECEIVED_MSG = 'request received' const DEFAULT_REQUEST_COMPLETED_MSG = 'request completed' const DEFAULT_REQUEST_ABORTED_MSG = 'request aborted' const DEFAULT_REQUEST_ERROR_MSG = 'request errored' function setup (t, logger, cb, handler, next) { const server = http.createServer(handler || function (req, res) { logger(req, res, next) if (req.url === '/') { res.end('hello world') return } else if (req.url === ERROR_URL) { res.statusCode = 500 res.end('error') return } res.statusCode = 404 res.end('Not Found') }) server.listen(0, '127.0.0.1', function (err) { cb(err || null, server) }) t.teardown(function (cb) { server.close(cb) }) return server } function doGet (server, path, callback) { path = path || '/' const address = server.address() const cb = callback || noop return http.get('http://' + address.address + ':' + address.port + path, cb) } test('default settings', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') t.equal(line.msg, DEFAULT_REQUEST_COMPLETED_MSG, 'message is set') t.equal(line.req.method, 'GET', 'method is get') t.equal(line.res.statusCode, 200, 'statusCode is 200') t.end() }) }) test('stream in options', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ stream: dest }) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') t.equal(line.msg, DEFAULT_REQUEST_COMPLETED_MSG, 'message is set') t.equal(line.req.method, 'GET', 'method is get') t.equal(line.res.statusCode, 200, 'statusCode is 200') t.end() }) }) test('add transport.caller information when missing', function (t) { t.plan(1) const options = { transport: { targets: [ { target: 'pino/file', options: { destination: (process.platform !== 'win32') ? '/dev/null' : undefined } } ] } } const logger = pinoHttp(options) logger.logger.info('hello world') t.equal(options.transport.caller, join(__dirname, '../logger.js'), 'caller is set') t.end() }) test('exposes the internal pino', function (t) { t.plan(1) const dest = split(JSON.parse) const logger = pinoHttp(dest) dest.on('data', function (line) { t.equal(line.msg, 'hello world') t.end() }) logger.logger.info('hello world') }) test('internal pino logger not shared between multiple middleware', function (t) { t.plan(1) const dest = split(JSON.parse) const middleware1 = pinoHttp(dest) const middleware2 = pinoHttp(dest) t.not(middleware1.logger, middleware2.logger, 'expected loggers not to be shared between middleware invocations') }) test('req.allLogs is correctly created if it does not exist', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) function handler (req, res) { delete req.allLogs logger(req, res) t.ok(Array.isArray(req.allLogs), 'req.allLogs should be an array') t.equal(req.allLogs.length, 1, 'req.allLogs should have one logger entry') t.equal(typeof req.allLogs[0].info, 'function', 'req.allLogs should contain a valid logger instance') res.end('hello world') } setup(t, logger, function (err, server) { t.error(err) doGet(server) }, handler) dest.on('data', function () { t.end() }) }) test('when multiple pino middleware are present each pino logger retains its own redact config', function (t) { t.plan(6) const middleware1Output = split(JSON.parse) const middleware2Output = split(JSON.parse) const middleware3Output = split(JSON.parse) const middleware1 = pinoHttp({ redact: ['req.method'] }, middleware1Output) const middleware2 = pinoHttp({ redact: ['req.url'] }, middleware2Output) const middleware3 = pinoHttp({}, middleware3Output) setup(t, (req, res, next) => { middleware1(req, res, next) middleware2(req, res, next) middleware3(req, res, next) t.ok(req.log, 'pino http middleware should have set request log logger to middleware1\'s logger') t.equal(req.allLogs.length, 3, 'multiple pino http middleware should have set request additional loggers') }, function (err, server) { t.error(err) doGet(server, '/') }) middleware1Output.on('data', function (line) { t.equal(line.req.method, '[Redacted]', 'method is Redacted') }) middleware2Output.on('data', function (line) { t.equal(line.req.url, '[Redacted]', 'url is Redacted') }) middleware3Output.on('data', function (line) { t.equal(line.req.method, 'GET', 'method is get and not redacted') }) }) test('uses the log level passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ useLevel: 'debug', level: 'debug' }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 20, 'level') t.notOk(line.useLevel, 'useLevel not forwarded') t.end() }) }) test('uses the custom log level passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customLogLevel: function (_req, _res, _err) { return 'warn' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 40, 'level') t.notOk(line.customLogLevel, 'customLogLevel not forwarded') t.end() }) }) test('uses the custom log level passed in as an option, req and res is defined', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customLogLevel: function (_req, _res, _err) { t.ok(_req, 'req is defined') t.ok(_res, 'res is defined') return 'warn' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function () { t.end() }) }) test('uses the log level passed in as an option, where the level is a custom one', function (t) { const dest = split(JSON.parse) const logger = pinoHttp( { customLevels: { infoCustom: 25 }, useLevel: 'infoCustom', level: 'infoCustom' }, dest ) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 25, 'level') t.notOk(line.useLevel, 'useLevel not forwarded') t.end() }) }) test('uses the custom log level passed in as an option, where the level itself is also a custom one', function (t) { const dest = split(JSON.parse) const logger = pinoHttp( { customLevels: { custom: 35 }, customLogLevel: function (_req, _res, _err) { return 'custom' } }, dest ) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 35, 'level') t.notOk(line.customLogLevel, 'customLogLevel not forwarded') t.end() }) }) test('no autoLogging if useLevel or customLogLevel is silent', function (t) { const dest = split(JSON.parse) const logger = pinoHttp( { customLogLevel: function (_req, _res, _err) { return 'silent' } }, dest ) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { const line = dest.read() t.equal(line, null) t.end() }) }) }) test('uses the custom invalid log level passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customLogLevel: function (_req, _res, _err) { return 'error-log-level' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 30, 'level') t.notOk(line.customLogLevel, 'customLogLevel not forwarded') t.end() }) }) test('throw error if custom log level and log level passed in together', function (t) { const dest = split(JSON.parse) const throwFunction = function () { pinoHttp({ useLevel: 'info', customLogLevel: function (_req, _res, _err) { return 'warn' } }, dest) } t.throws(throwFunction, { message: 'You can\'t pass \'useLevel\' and \'customLogLevel\' together' }) t.end() }) test('allocate a unique id to every request', function (t) { t.plan(5) const dest = split(JSON.parse) const logger = pinoHttp(dest) let lastId = null setup(t, logger, function (err, server) { t.error(err) doGet(server) doGet(server) }) dest.on('data', function (line) { t.not(line.req.id, lastId) lastId = line.req.id t.ok(line.req.id, 'req.id is defined') }) }) test('uses a custom genReqId function', function (t) { t.plan(5) const dest = split(JSON.parse) let idToTest function genReqId (req, res) { t.ok(res, 'res is defined') t.ok(req.url, 'The first argument must be the request parameter') idToTest = (Date.now() + Math.random()).toString(32) return idToTest } const logger = pinoHttp({ genReqId }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(typeof line.req.id, 'string') t.equal(line.req.id, idToTest) t.end() }) }) test('reuses existing req.id if present', function (t) { t.plan(2) const dest = split(JSON.parse) const logger = pinoHttp(dest) const someId = 'id-to-reuse-12345' function loggerWithExistingReqId (req, res) { req.id = someId logger(req, res) } setup(t, loggerWithExistingReqId, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.req.id, someId) t.end() }) }) test('startTime', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) const someStartTime = 56 t.equal(typeof pinoHttp.startTime, 'symbol') function loggerWithStartTime (req, res) { res[pinoHttp.startTime] = someStartTime logger(req, res) t.equal(res[pinoHttp.startTime], someStartTime) } setup(t, loggerWithStartTime, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(typeof line.responseTime, 'number') t.end() }) }) test('responseTime', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.responseTime >= 0, 'responseTime is defined') t.end() }) }) test('responseTime for errored request', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.ok(line.responseTime >= 0, 'responseTime is defined') t.equal(line.msg, DEFAULT_REQUEST_ERROR_MSG, 'message is set') t.end() }) }) test('responseTime for request emitting error event', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) function handle (req, res) { logger(req, res) res.emit('error', new Error('Some error')) res.end() } setup(t, logger, function (err, server) { t.error(err) doGet(server) }, handle) dest.on('data', function (line) { t.ok(line.responseTime >= 0, 'responseTime is defined') t.end() }) }) // TODO(mcollina): fix this test test('log requests aborted during payload', { skip: true }, function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) function handle (req, res) { logger(req, res) const read = new stream.Readable({ read () { if (this.called) { return } this.called = true this.push('delayed') } }) read.pipe(res) } function listen (err, server) { t.error(err) const client = net.connect(server.address().port, server.address().address, () => { client.write('GET /delayed HTTP/1.1\r\nHost: localhost\r\n\r\n') }) client.on('data', (data) => { client.destroy() }) } setup(t, logger, listen, handle) dest.on('data', function (line) { t.ok(line.responseTime >= 0, 'responseTime is defined') t.equal(line.msg, DEFAULT_REQUEST_ABORTED_MSG, 'message is set') t.end() }) }) test('log requests aborted on the server', function (t) { const dest = split(JSON.parse) const logger = pinoHttp(dest) function handle (req, res) { logger(req, res) req.destroy() res.end() } function listen (err, server) { t.error(err) const client = doGet(server) client.on('error', function () { // skip error }) } setup(t, logger, listen, handle) dest.on('data', function (line) { t.ok(line.responseTime >= 0, 'responseTime is defined') t.equal(line.msg, DEFAULT_REQUEST_ABORTED_MSG, 'message is set') t.end() }) }) test('no auto logging with autoLogging set to false', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ autoLogging: false }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { const line = dest.read() t.equal(line, null) t.end() }) }) }) test('autoLogging set to true and path not ignored', test => { const dest = split(JSON.parse) const logger = pinoHttp({ autoLogging: { ignore: req => req.url === '/ignorethis' } }, dest) setup(test, logger, function (err, server) { test.error(err) doGet(server, '/shouldlogthis') }) dest.on('data', function (line) { test.pass('path should log') test.end() }) }) test('no auto logging with autoLogging set to true and ignoring a specific user-agent', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ autoLogging: { ignore: function (req) { return req.headers['user-agent'] === 'ELB-HealthChecker/2.0' } } }, dest) setup(t, logger, function (err, server) { t.error(err) const { address, port } = server.address() http.get({ protocol: 'http:', hostname: address, port, path: '/', headers: { 'User-Agent': 'ELB-HealthChecker/2.0' } }, function () { const line = dest.read() t.equal(line, null) t.end() }) }) }) test('support a custom instance', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest) }) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') t.equal(line.msg, DEFAULT_REQUEST_COMPLETED_MSG, 'message is set') t.equal(line.req.method, 'GET', 'method is get') t.equal(line.res.statusCode, 200, 'statusCode is 200') t.end() }) }) test('support a custom instance with custom genReqId function', function (t) { const dest = split(JSON.parse) let idToTest function genReqId (req, res) { t.ok(res, 'res is defined') t.ok(req.url, 'The first argument must be the request parameter') idToTest = (Date.now() + Math.random()).toString(32) return idToTest } const logger = pinoHttp({ logger: pino(dest), genReqId }) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') t.notOk(line.genReqId) t.equal(line.msg, DEFAULT_REQUEST_COMPLETED_MSG, 'message is set') t.equal(line.req.method, 'GET', 'method is get') t.equal(line.res.statusCode, 200, 'statusCode is 200') t.end() }) }) test('support a custom instance with one of its customLevels as useLevel', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino({ customLevels: { custom: 25 } }, dest), useLevel: 'custom', level: 'custom' }) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.level, 25, 'level') t.notOk(line.useLevel, 'useLevel not forwarded') t.end() }) }) test('does not crash when no request connection object', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest) }) t.plan(1) const server = http.createServer(handler) server.unref() server.listen(9999, () => { http.get('http://127.0.0.1:9999', (res) => { t.pass('made it through logic path without crashing') }) }) function handler (req, res) { delete req.connection logger(req, res) res.end() } }) // https://github.com/pinojs/pino-http/issues/42 test('does not return excessively long object', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { req: function (req) { delete req.connection return req } } }) t.plan(1) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (req, res) { logger(req, res) res.end() } dest.on('data', function (obj) { t.equal(Object.keys(obj.req).length, 6) t.end() }) }) test('err.raw is available to custom serializers', function (t) { t.plan(1) const error = new Error('foo') const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { err (err) { t.equal(err.raw, error) t.end() } } }) const server = http.createServer((req, res) => { logger(req, res) res.err = error res.end() }) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) }) test('req.raw is available to custom serializers', function (t) { t.plan(2) const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { req: function (req) { t.ok(req.raw) t.ok(req.raw.connection) return req } } }) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (req, res) { logger(req, res) res.end() } }) test('res.raw is available to custom serializers', function (t) { t.plan(2) const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { res: function (res) { t.ok(res.raw) t.ok(res.raw.statusCode) t.end() return res } } }) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (req, res) { logger(req, res) res.end() } }) test('res.raw is not enumerable', function (t) { t.plan(1) const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { res: function (res) { t.equal(Object.prototype.propertyIsEnumerable.call(res, 'raw'), false) t.end() return res } } }) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (req, res) { logger(req, res) res.end() } }) test('err.raw, req.raw and res.raw are passed into custom serializers directly, when opts.wrapSerializers is false', (t) => { t.plan(6) const error = new Error('foo') const dest = split(JSON.parse) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (request, response) { const logger = pinoHttp({ logger: pino(dest), wrapSerializers: false, serializers: { err: function (err) { t.notOk(err.raw) t.equal(err, error) return err }, req: function (req) { t.notOk(req.raw) t.equal(req, request) return req }, res: function (res) { t.notOk(res.raw) t.equal(res, response) return res } } }) logger(request, response) response.err = error response.end() } }) test('req.id has a non-function value', function (t) { t.plan(1) const dest = split(JSON.parse) const logger = pinoHttp({ logger: pino(dest), serializers: { req: function (req) { t.equal(typeof req.id === 'function', false) t.end() return req } } }) const server = http.createServer(handler) server.unref() server.listen(0, () => { http.get(server.address(), () => {}) }) function handler (req, res) { logger(req, res) res.end() } }) test('uses the custom successMessage callback if passed in as an option', function (t) { const dest = split(JSON.parse) const customResponseMessage = 'Custom response message' const logger = pinoHttp({ customSuccessMessage: function (req, res) { return customResponseMessage + ' ' + req.method } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.msg, customResponseMessage + ' GET') t.end() }) }) test('pass responseTime argument to the custom successMessage callback', function (t) { const dest = split(JSON.parse) const customResponseMessage = 'Response time is: ' const logger = pinoHttp({ customSuccessMessage: function (req, res, responseTime) { return customResponseMessage + responseTime + ' ' + req.method } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.match(line.msg, /Response time is: \d+ GET/) t.end() }) }) test('pass responseTime argument to the custom errorMessage callback', function (t) { const dest = split(JSON.parse) const customErrorMessage = 'Response time is:' const logger = pinoHttp({ customErrorMessage: function (req, res, err, responseTime) { return `${customErrorMessage} ${responseTime} ${req.method}` } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.match(line.msg, /Response time is: \d+ GET/) t.end() }) }) test('uses the custom successObject callback if passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customSuccessObject: function (req, res, val) { return { ...val, label: req.method + ' customSuccessObject' } } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.label, 'GET customSuccessObject') t.end() }) }) test('uses the custom receivedMessage callback if passed in as an option', function (t) { const dest = split(JSON.parse) const message = DEFAULT_REQUEST_RECEIVED_MSG const logger = pinoHttp({ customReceivedMessage: function (_req, _res) { return message } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { if (line.msg === DEFAULT_REQUEST_COMPLETED_MSG) { return } t.equal(line.msg, message) t.end() }) }) test('uses the custom receivedObject callback if passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customReceivedObject: function (req, val) { return { label: req.method + ' customReceivedObject' } } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { if (line.label === undefined) { return } t.equal(line.label, 'GET customReceivedObject') t.end() }) }) test('uses the custom receivedObject + receivedMessage callback if passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customReceivedMessage: function (_req, _res) { return DEFAULT_REQUEST_RECEIVED_MSG }, customReceivedObject: function (req, val) { return { label: req.method + ' customReceivedObject' } } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { if (line.label === undefined && line.msg !== undefined) { return } t.equal(line.msg, DEFAULT_REQUEST_RECEIVED_MSG) t.equal(line.label, 'GET customReceivedObject') t.end() }) }) test('receve receivedMessage before successMessage', function (t) { t.plan(3) const dest = split(JSON.parse) const message = DEFAULT_REQUEST_RECEIVED_MSG const logger = pinoHttp({ customReceivedMessage: function (_req, _res) { return message } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { t.equal(dest.read().msg, DEFAULT_REQUEST_RECEIVED_MSG) t.equal(dest.read().msg, DEFAULT_REQUEST_COMPLETED_MSG) t.end() }) }) }) test('uses the custom errorMessage callback if passed in as an option', function (t) { const dest = split(JSON.parse) const customErrorMessage = 'Custom error message' const logger = pinoHttp({ customErrorMessage: function (req, res, err) { return customErrorMessage + ' ' + req.method + ' ' + err.toString() } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.equal(line.msg.indexOf(customErrorMessage + ' GET'), 0) t.end() }) }) test('uses the custom errorObject callback if passed in as an option', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customErrorObject: function (req, res, err, val) { return { ...val, label: 'customErrorObject ' + req.method + ' ' + err.toString() } } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.equal(line.label.indexOf('customErrorObject GET'), 0) t.end() }) }) test('receve receivedMessage before errorMessage', function (t) { t.plan(3) const dest = split(JSON.parse) const message = DEFAULT_REQUEST_RECEIVED_MSG const logger = pinoHttp({ customReceivedMessage: function (_req, _res) { return message } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL, function () { t.equal(dest.read().msg, DEFAULT_REQUEST_RECEIVED_MSG) t.equal(dest.read().msg, DEFAULT_REQUEST_ERROR_MSG) t.end() }) }) }) test('uses custom log object attribute keys when provided, successful request', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customAttributeKeys: { req: 'httpReq', res: 'httpRes', err: 'httpErr', responseTime: 'timeTaken' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.httpReq, 'httpReq is defined') t.ok(line.httpRes, 'httpRes is defined') t.equal(typeof line.timeTaken, 'number') t.end() }) }) test('uses custom log object attribute keys when provided, error request', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customAttributeKeys: { req: 'httpReq', res: 'httpRes', err: 'httpErr', responseTime: 'timeTaken' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.ok(line.httpReq, 'httpReq is defined') t.ok(line.httpRes, 'httpRes is defined') t.ok(line.httpErr, 'httpRes is defined') t.equal(typeof line.timeTaken, 'number') t.end() }) }) test('uses custom request properties to log additional attributes when provided', function (t) { const dest = split(JSON.parse) function customPropsHandler (req, res) { if (req && res) { return { key1: 'value1', key2: 'value2' } } } const logger = pinoHttp({ customProps: customPropsHandler }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.key1, 'value1') t.equal(line.key2, 'value2') t.end() }) }) test('uses old custom request properties interface to log additional attributes', function (t) { const dest = split(JSON.parse) function customPropsHandler (req, res) { if (req && res) { return { key1: 'value1', key2: 'value2' } } } const logger = pinoHttp({ customProps: customPropsHandler }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.key1, 'value1') t.equal(line.key2, 'value2') t.end() }) }) test('uses custom request properties to log additional attributes when response provided', function (t) { const dest = split(JSON.parse) function customPropsHandler (req, res) { if (req && res) { return { key1: 'value1', key2: res.statusCode } } } const logger = pinoHttp({ customProps: customPropsHandler }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) dest.on('data', function (line) { t.equal(line.key1, 'value1') t.equal(line.key2, 500) t.end() }) }) test('uses custom request properties and a receivedMessage callback and the properties are set on the receivedMessage', function (t) { const dest = split(JSON.parse) const message = DEFAULT_REQUEST_RECEIVED_MSG const logger = pinoHttp({ customReceivedMessage: function (_req, _res) { return message }, customProps: (req, res) => { return { key1: 'value1', key2: res.statusCode } } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, ERROR_URL) }) let calls = 0 dest.on('data', function (line) { calls++ if (line.msg === message) { t.equal(line.key1, 'value1') t.equal(line.key2, 200) t.equal(line.req.url, ERROR_URL) t.ok(line.req, 'req is defined') t.notOk(line.res, 'res is not defined yet') } else if (line.msg === DEFAULT_REQUEST_ERROR_MSG) { t.equal(line.key1, 'value1') t.equal(line.key2, 500) t.equal(line.req.url, ERROR_URL) t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') } if (calls === 2) { t.end() } }) }) test('uses custom request properties to log additional attributes; custom props is an object instead of callback', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customProps: { key1: 'value1' } }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.key1, 'value1') t.end() }) }) test('uses custom request properties and once customProps', function (t) { const dest = split() function customPropsHandler (req, res) { return { key1: 'value1' } } const logger = pinoHttp({ customProps: customPropsHandler }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.equal(line.match(/key1/g).length, 1, 'once customProps') t.end() }) }) test('dont pass custom request properties to log additional attributes', function (t) { const dest = split(JSON.parse) const logger = pinoHttp({ customProps: undefined }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server) }) dest.on('data', function (line) { t.ok(line.hostname, 'hostname is defined') t.ok(line.level, 'level is defined') t.ok(line.msg, 'msg is defined') t.ok(line.pid, 'pid is defined') t.ok(line.req, 'req is defined') t.ok(line.res, 'res is defined') t.ok(line.time, 'time is defined') t.end() }) }) test('auto logging and next callback', function (t) { t.plan(3) const dest = split(JSON.parse) const logger = pinoHttp({ autoLogging: true }, dest) setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { const line = dest.read() t.equal(line.msg, DEFAULT_REQUEST_COMPLETED_MSG) t.end() }) }, function (req, res) { logger(req, res, function () { t.pass('called') res.end('hello world') }) }) }) test('quiet request logging', function (t) { t.plan(8) const dest = split(JSON.parse) const logger = pinoHttp({ quietReqLogger: true }, dest) function handler (req, res) { t.pass('called') req.id = 'testId' logger(req, res) req.log.info('quiet message') res.end('hello world') } setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { const quietLine = dest.read() t.equal(quietLine.msg, 'quiet message') t.equal(quietLine.reqId, 'testId') t.notOk(quietLine.req) const responseLine = dest.read() t.equal(responseLine.msg, DEFAULT_REQUEST_COMPLETED_MSG) t.equal(responseLine.reqId, 'testId') t.ok(responseLine.req) t.end() }) }, handler) }) test('quiet request logging - custom request id key', function (t) { t.plan(8) const dest = split(JSON.parse) const logger = pinoHttp({ quietReqLogger: true, customAttributeKeys: { reqId: 'customRequestId' } }, dest) function handler (req, res) { t.pass('called') req.id = 'testId' logger(req, res) req.log.info('quiet message') res.end('hello world') } setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { const quietLine = dest.read() t.equal(quietLine.msg, 'quiet message') t.notOk(quietLine.req) t.equal(quietLine.customRequestId, 'testId') const responseLine = dest.read() t.equal(responseLine.msg, DEFAULT_REQUEST_COMPLETED_MSG) t.equal(responseLine.customRequestId, 'testId') t.ok(responseLine.req) t.end() }) }, handler) }) test('quiet response logging', function (t) { t.plan(5) const dest = split(JSON.parse) const logger = pinoHttp({ quietResLogger: true }, dest) function handler (req, res) { t.pass('called') req.id = 'testId' logger(req, res) req.log.info('quiet message') res.end('hello world') } setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { dest.read() const responseLine = dest.read() t.equal(responseLine.msg, DEFAULT_REQUEST_COMPLETED_MSG) t.notOk(responseLine.req) t.ok(responseLine.res) t.end() }) }, handler) }) test('quiet request and response logging', function (t) { t.plan(6) const dest = split(JSON.parse) const logger = pinoHttp({ quietReqLogger: true, quietResLogger: true }, dest) function handler (req, res) { t.pass('called') req.id = 'testId' logger(req, res) req.log.info('quiet message') res.end('hello world') } setup(t, logger, function (err, server) { t.error(err) doGet(server, null, function () { dest.read() const responseLine = dest.read() t.equal(responseLine.msg, DEFAULT_REQUEST_COMPLETED_MSG) t.equal(responseLine.reqId, 'testId') t.notOk(responseLine.req) t.ok(responseLine.res) t.end() }) }, handler) })