Repository: pinojs/hapi-pino
Pull request #167 (2022-06-07 00:10:02)

Add support to replace default serialalizers unwrapped
Presently is not really possible to completely replace hapi-pino's default serializers with a custom implementation as `req` and `res` serializers are always wrapped with the standard serializers. This pull request adds support to replace them unwrapped by providing the `wrapSerializers` option set to `false`


Patch:
diff --git a/README.md b/README.md
index edf0cc0..6639422 100644
--- a/README.md
+++ b/README.md
@@ -192,6 +192,29 @@ events"](#hapievents) section.
   }
   ```
 
+### `options.wrapSerializers: boolean`
+
+  **Default**: `true`
+
+  When `false`, custom serializers will be passed the raw value directly.
+
+  **Example**:
+  If you prefer to work with the raw value directly, or you want to honor the custom serializers already defined by `options.instance`, you can pass in `options.wrapSerializers` as `false`:
+
+  ```js
+  {
+    wrapSerializers: false,
+    serializers: {
+      req (req) {
+        // `req` is the raw hapi's `Request` object, not the already serialized request from `pino.stdSerializers.req`.
+        return {
+          message: req.foo
+        };
+      }
+    }
+  }
+  ```
+
 ### `options.instance: Pino`
 
   Uses a previously created Pino instance as the logger.
diff --git a/index.d.ts b/index.d.ts
index e94deb1..6ceaf92 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -23,11 +23,15 @@ declare namespace HapiPino {
     logRouteTags?: boolean | undefined;
     logRequestStart?: boolean | ((req: Request) => boolean) | undefined;
     logRequestComplete?: boolean | ((req: Request) => boolean) | undefined;
+    customRequestStartMessage?: ((req: Request) => string) | undefined;
+    customRequestCompleteMessage?: ((req: Request, responseTime: number) => string) | undefined;
+    customRequestErrorMessage?: ((req: Request, error: Error) => string) | undefined;
     tags?: { [key in pino.Level]?: string } | undefined;
     stream?: NodeJS.WriteStream | undefined;
     allTags?: pino.Level | undefined;
     instance?: pino.Logger | undefined;
     logEvents?: string[] | false | null | undefined;
+    wrapSerializers: boolean | undefined;
     mergeHapiLogData?: boolean | undefined;
     ignorePaths?: string[] | undefined;
     ignoreTags?: string[] | undefined;
diff --git a/index.js b/index.js
index 0e313da..d0f120d 100644
--- a/index.js
+++ b/index.js
@@ -29,9 +29,14 @@ async function register (server, options) {
   })
 
   options.serializers = options.serializers || {}
-  options.serializers.req = stdSerializers.wrapRequestSerializer(options.serializers.req || stdSerializers.req)
-  options.serializers.res = stdSerializers.wrapResponseSerializer(options.serializers.res || stdSerializers.res)
+
+  const wrapSerializers = 'wrapSerializers' in options ? options.wrapSerializers : true
+  const reqSerializer = options.serializers.req || stdSerializers.req
+  const resSerializer = options.serializers.res || stdSerializers.res
+
   options.serializers.err = options.serializers.err || pino.stdSerializers.err
+  options.serializers.req = wrapSerializers ? stdSerializers.wrapRequestSerializer(reqSerializer) : reqSerializer
+  options.serializers.res = wrapSerializers ? stdSerializers.wrapResponseSerializer(resSerializer) : resSerializer
 
   if (options.logEvents === undefined) {
     options.logEvents = ['onPostStart', 'onPostStop', 'response', 'request-error']


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index 6d374cf..3648706 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -26,12 +26,16 @@ const options: HapiPino.Options = {
     fatal: 'fatal',
   },
   allTags: 'info',
+  wrapSerializers: false,
   serializers: {
     req: (req: any) => console.log(req),
   },
   getChildBindings: (req: Request) => ({
     'x-request-id': req.headers['x-request-id'],
   }),
+  customRequestStartMessage: (req: Request) => `request start ${req.path}`,
+  customRequestCompleteMessage: (req: Request, responseTime: number) => `request complete ${req.path} in ${responseTime}ms`,
+  customRequestErrorMessage: (req: Request, error: Error) => `request failed ${req.path} with error ${error.message}`,
   instance: pinoLogger,
   logEvents: false,
   mergeHapiLogData: false,
diff --git a/test.js b/test.js
index 47dc489..f50a82f 100644
--- a/test.js
+++ b/test.js
@@ -1665,6 +1665,40 @@ experiment('custom serializers', () => {
     await finish
   })
 
+  test('logging with configured req serializer (unwrapped)', async () => {
+    const server = getServer()
+    let done
+    const finish = new Promise(function (resolve, reject) {
+      done = resolve
+    })
+    const stream = sink(data => {
+      expect(data.req.path).to.equal('/')
+      expect(data.req.raw).to.be.an.object()
+
+      expect(data.res).to.be.an.object()
+      expect(data.res.statusCode).to.be.equal(404)
+      done()
+    })
+    const logger = require('pino')(stream)
+    const plugin = {
+      plugin: Pino,
+      options: {
+        instance: logger,
+        wrapSerializers: false,
+        serializers: {
+          req: req => ({ path: req.path, raw: req })
+        }
+      }
+    }
+
+    await server.register(plugin)
+    await server.inject({
+      method: 'GET',
+      url: '/'
+    })
+    await finish
+  })
+
   test('logging with configured res serializer', async () => {
     const server = getServer()
     let done
@@ -1695,6 +1729,41 @@ experiment('custom serializers', () => {
     await finish
   })
 
+  test('logging with configured res serializer (unwrapped)', async () => {
+    const server = getServer()
+    let done
+    const finish = new Promise(function (resolve, reject) {
+      done = resolve
+    })
+    const stream = sink(data => {
+      expect(data.req).to.be.an.object()
+      expect(data.req.url).to.be.equal('/')
+
+      expect(data.res.code).to.equal(404)
+      expect(data.res.headersFlushed).to.equal(true)
+      expect(data.res.raw).to.be.an.object()
+      done()
+    })
+    const logger = require('pino')(stream)
+    const plugin = {
+      plugin: Pino,
+      options: {
+        instance: logger,
+        wrapSerializers: false,
+        serializers: {
+          res: res => ({ code: res.statusCode, headersFlushed: res.headersSent, raw: res })
+        }
+      }
+    }
+
+    await server.register(plugin)
+    await server.inject({
+      method: 'GET',
+      url: '/'
+    })
+    await finish
+  })
+
   test('logging with pre-defined err serializer', async () => {
     const server = getServer()
     let done