diff --git a/README.md b/README.md index c4958e5..36e6537 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,11 @@ $ node example.js | pino-pretty * `autoLogging.ignore`: set to a `function (req) => { /* returns boolean */ }`. Useful for defining logic based on req properties (such as a user-agent header) to ignore successful requests. * `stream`: same as the second parameter * `customReceivedMessage`: set to a `function (req, res) => { /* returns message string */ }` This function will be invoked at each request received, setting "msg" property to returned string. If not set, nothing value will be used. +* `customReceivedObject`: set to a `function (req, res, loggableObject) => { /* returns loggable object */ }` This function will be invoked at each request received, replacing the base loggable received object. When set, it is up to the reponsibility of the caller to merge with the `loggableObject` parameter. If not set, default value will be used. * `customSuccessMessage`: set to a `function (req, res) => { /* returns message string */ }` This function will be invoked at each successful response, setting "msg" property to returned string. If not set, default value will be used. +* `customSuccessObject`: set to a `function (req, res, loggableObject) => { /* returns loggable object */ }` This function will be invoked at each successful response, replacing the base loggable success object. When set, it is up to the reponsibility of the caller to merge with the `loggableObject` parameter. If not set, default value will be used. * `customErrorMessage`: set to a `function (req, res, err) => { /* returns message string */ }` This function will be invoked at each failed response, setting "msg" property to returned string. If not set, default value will be used. +* `customErrorObject`: set to a `function (req, res, err, loggableObject) => { /* returns message string */ }` This function will be invoked at each failed response, the base loggable error object. When set, it is up to the reponsibility of the caller to merge with the `loggableObject` parameter. If not set, default value will be used. * `customAttributeKeys`: allows the log object attributes added by `pino-http` to be given custom keys. Accepts an object of format `{ [original]: [override] }`. Attributes available for override are `req`, `res`, `err`, `responseTime` and, when using quietReqLogger, `reqId`. * `wrapSerializers`: when `false`, custom serializers will be passed the raw value directly. Defaults to `true`. * `customProps`: set to a `function (req, res) => { /* returns on object */ }` or `{ /* returns on object */ }` This function will be invoked for each request with `req` and `res` where we could pass additional properties that need to be logged outside the `req`. @@ -197,6 +200,53 @@ function handle (req, res) { server.listen(3000) ``` +##### Structured Object Hooks + +It is possible to override the default structured object with your own. The hook is provided with the +pino-http base object so that you can merge in your own keys. + +This is useful in scenarios where you want to augment core pino-http logger object with your own event +labels. + +> If you simply want to change the message which is logged then check out the custom[Received|Error|Success]Message +> hooks e.g. customReceivedMessage + +```js +const logger = require('pino-http')({ + //... remaining config omitted for brevity + customReceivedObject: (req, res, val) => { + return { + category: 'ApplicationEvent', + eventCode: 'REQUEST_RECEIVED' + }; + }, + + customSuccessObject: (req, res, val) => { + return { + ...val, + category: 'ApplicationEvent', + eventCode: + res.statusCode < 300 + ? 'REQUEST_PROCESSED' + : 'REQUEST_FAILED' + }; + }, + + customErrorObject: (req, res, error, val) => { + const store = storage.getStore(); + const formattedBaggage = convertBaggageToObject(store?.baggage); + + return { + ...val, + category: 'ApplicationEvent', + eventCode: 'REQUEST_FAILED' + }; + } + + // ...remaining config omitted for brevity +}) +``` + ##### PinoHttp.logger (P.Logger) The `pinoHttp` instance has a property `logger`, which references to an actual logger instance, used diff --git a/index.d.ts b/index.d.ts index c3a8e2c..0cc77ca 100644 --- a/index.d.ts +++ b/index.d.ts @@ -32,6 +32,9 @@ export interface Options extends pino.LoggerOptions { customReceivedMessage?: ((req: IncomingMessage, res: ServerResponse) => string) | undefined; customSuccessMessage?: ((req: IncomingMessage, res: ServerResponse) => string) | undefined; customErrorMessage?: ((req: IncomingMessage, res: ServerResponse, error: Error) => string) | undefined; + customReceivedObject?: ((req: IncomingMessage, res: ServerResponse, val?: any) => any) | undefined; + customSuccessObject?: ((req: IncomingMessage, res: ServerResponse, val: any) => any) | undefined; + customErrorObject?: ((req: IncomingMessage, res: ServerResponse, error: Error, val: any) => string) | undefined; customAttributeKeys?: CustomAttributeKeys | undefined; wrapSerializers?: boolean | undefined; customProps?: ((req: IncomingMessage, res: ServerResponse) => object) | undefined; diff --git a/logger.js b/logger.js index 9beeab3..6d92f53 100644 --- a/logger.js +++ b/logger.js @@ -64,9 +64,15 @@ function pinoLogger (opts, stream) { const autoLoggingIgnore = opts.autoLogging && opts.autoLogging.ignore ? opts.autoLogging.ignore : null delete opts.autoLogging - const receivedMessage = opts.customReceivedMessage && typeof opts.customReceivedMessage === 'function' ? opts.customReceivedMessage : undefined - const successMessage = opts.customSuccessMessage || function (req, res) { return res.writableEnded ? 'request completed' : 'request aborted' } - const errorMessage = opts.customErrorMessage || function () { return 'request errored' } + 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 @@ -106,18 +112,25 @@ function pinoLogger (opts, stream) { if (err || this.err || this.statusCode >= 500) { const error = err || this.err || new Error('failed with status code ' + this.statusCode) - log[level]({ - [resKey]: this, - [errKey]: error, - [responseTimeKey]: responseTime - }, errorMessage(req, this, error)) + log[level]( + onRequestErrorObject(req, this, error, { + [resKey]: this, + [errKey]: error, + [responseTimeKey]: responseTime + }), + errorMessage(req, this, error) + ) + return } - log[level]({ - [resKey]: this, - [responseTimeKey]: responseTime - }, successMessage(req, this)) + log[level]( + onRequestSuccessObject(req, this, { + [resKey]: this, + [responseTimeKey]: responseTime + }), + successMessage(req, this) + ) } function loggingMiddleware (req, res, next) { @@ -147,9 +160,14 @@ function pinoLogger (opts, stream) { } if (shouldLogSuccess) { - if (receivedMessage !== undefined) { + const shouldLogReceived = receivedMessage !== undefined || onRequestReceivedObject !== undefined + + if (shouldLogReceived) { const level = getLogLevelFromCustomLogLevel(customLogLevel, useLevel, res, undefined, req) - req.log[level](receivedMessage(req, res)) + const receivedObjectResult = onRequestReceivedObject !== undefined ? onRequestReceivedObject(req, res, undefined) : {} + const receivedStringResult = receivedMessage !== undefined ? receivedMessage(req, res) : undefined + + req.log[level](receivedObjectResult, receivedStringResult) } res.on('close', onResFinished) @@ -196,6 +214,30 @@ function reqIdGenFactory (func) { } } +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) { + return res.writableEnded ? 'request completed' : 'request aborted' +} + module.exports = pinoLogger module.exports.stdSerializers = { err: serializers.err, diff --git a/test/test.js b/test/test.js index 396fc5c..0d9becc 100644 --- a/test/test.js +++ b/test/test.js @@ -829,6 +829,25 @@ test('uses the custom successMessage callback if passed in as an option', functi }) }) +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 @@ -852,6 +871,57 @@ test('uses the custom receivedMessage callback if passed in as an option', funct }) }) +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) @@ -894,6 +964,25 @@ test('uses the custom errorMessage callback if passed in as an option', function }) }) +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)