Repository: pinojs/hapi-pino (js)

Issue:
Merge hapi log event data into pino serialized object.
I would like to stick to the hapi logging interface, but at the same time take advantage of the properties serialized by pino.

Current behaviour is this (hapi-pino 1.4.1):
```js
server.log(['error'], { err, msg: 'x went wrong' });

// Pino log
{ ... tags: ['error'], data: { err: err, msg: 'x went wrong' } }
```
But I would rather see:
```js
{ ... tags: ['error'], err: err, msg: 'x went wrong' }
```
In short, I would like to be able to merge Hapi log event data properties into pino's object root instead of under the `data` key.

edit: I would also like to see this:
```js
server.log(['info'], 'job completed');

// Current behaviour
{ ... tags: ['info'], data: 'job completed' }

// Wanted behaviour
{ ... tags: ['info'], msg: 'job completed' }
```

Pull request description:
Adds support for mergeHapiLogData plugin-level option. Fixes #15
With this PR, users can specify a `mergeHapiLogData` option when registering the plugin. 
```js
{
    register: 'hapi-pino',
    options: {
        mergeHapiLogData: true // Default is false.
    }
}
```

When enabled, the data attribute from hapi log events (e.g. `server.log([tags], data)`, `request.log([tags], data)`) will be merged straight into pino's log object. 
```js
server.log(['info'], { hello: 'world' });
{ level: 30, hello: 'world', ...}
```
Additionally, string only logs will be merged under the msg attribute.
```js
server.log(['info'], 'hello world');
{ level: 30, msg: 'hello world', ...}
```
When disabled (default value), the data attribute will be included under a data key. This is the current behaviour (before this PR)
```js
server.log(['info'], { hello: 'world' });
{ level: 30, data: { hello: 'world' }, ...}

server.log(['info'], 'hello world');
{ level: 30, data: 'hello world', ...}
```



Patch:
diff --git a/README.md b/README.md
index 309f832..4f1ee58 100644
--- a/README.md
+++ b/README.md
@@ -95,7 +95,22 @@ events"](#hapievents) section.
   `tags`, defaults to `'info'`.
 - `[instance]` - uses a previously created Pino instance as the logger.
   The instance's `stream` and `serializers` take precedence.
+- `[mergeHapiLogData]` - When enabled, Hapi-pino will merge the data received
+  from Hapi's logging interface (`server.log(tags, data)` or `request.log(tags, data)`)
+  into Pino's logged attributes at root level. If data is a string, it will be used as
+  the value for the `msg` key. Default is `false`, in which case data will be logged under 
+  a `data` key.
 
+  E.g.
+```js
+server.log(['info'], {hello: 'world'})
+
+// with mergeHapiLogData: true
+{ level: 30, hello: 'world', ...}
+
+// with mergeHapiLogData: false (Default)
+{ level: 30, data: { hello: 'world' }}
+```
 
 <a name="serverdecorations"></a>
 ### Server Decorations
diff --git a/benchmarks/hapi-pino-merge-hapi-log.js b/benchmarks/hapi-pino-merge-hapi-log.js
new file mode 100644
index 0000000..861c5c0
--- /dev/null
+++ b/benchmarks/hapi-pino-merge-hapi-log.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const Hapi = require('hapi')
+
+const server = new Hapi.Server()
+server.connection({
+  host: 'localhost',
+  port: 3000
+})
+
+server.route({
+  method: 'GET',
+  path: '/',
+  handler: function (request, reply) {
+    return reply('hello world')
+  }
+})
+
+server.register({
+  register: require('..'),
+  options: {
+    mergeHapiLogData: true
+  }
+}, (err) => {
+  if (err) {
+    console.error(err)
+    process.exit(1)
+  }
+
+  server.on('response', () => {
+    server.log(['info'], { hello: 'world' })
+  })
+
+  server.start((err) => {
+    if (err) {
+      console.error(err)
+      process.exit(1)
+    }
+  })
+})
diff --git a/benchmarks/hapi-pino-no-merge-hapi-log.js b/benchmarks/hapi-pino-no-merge-hapi-log.js
new file mode 100644
index 0000000..64236ad
--- /dev/null
+++ b/benchmarks/hapi-pino-no-merge-hapi-log.js
@@ -0,0 +1,35 @@
+'use strict'
+
+const Hapi = require('hapi')
+
+const server = new Hapi.Server()
+server.connection({
+  host: 'localhost',
+  port: 3000
+})
+
+server.route({
+  method: 'GET',
+  path: '/',
+  handler: function (request, reply) {
+    return reply('hello world')
+  }
+})
+
+server.register(require('..'), (err) => {
+  if (err) {
+    console.error(err)
+    process.exit(1)
+  }
+
+  server.on('response', () => {
+    server.log(['info'], { hello: 'world' })
+  })
+
+  server.start((err) => {
+    if (err) {
+      console.error(err)
+      process.exit(1)
+    }
+  })
+})
diff --git a/index.js b/index.js
index 1c74140..75ee6ca 100644
--- a/index.js
+++ b/index.js
@@ -42,6 +42,8 @@ function register (server, options, next) {
     return next(new Error('invalid tag levels'))
   }
 
+  const mergeHapiLogData = options.mergeHapiLogData
+
   // expose logger as 'server.app.logger'
   server.app.logger = logger
 
@@ -98,17 +100,28 @@ function register (server, options, next) {
     var level
     var found = false
 
+    var logObject
+    if (mergeHapiLogData) {
+      if (typeof data === 'string') {
+        data = { msg: data }
+      }
+
+      logObject = Object.assign({ tags }, data)
+    } else {
+      logObject = { tags, data }
+    }
+
     for (var i = 0; i < tags.length; i++) {
       level = tagToLevels[tags[i]]
       if (level) {
-        current[level]({ tags, data })
+        current[level](logObject)
         found = true
         break
       }
     }
 
     if (!found && allTags) {
-      current[allTags]({ tags, data })
+      current[allTags](logObject)
     }
   }
 }


Test patch:
diff --git a/test.js b/test.js
index 60e5d41..6ad1c97 100644
--- a/test.js
+++ b/test.js
@@ -418,3 +418,47 @@ experiment('uses a prior pino instance', () => {
     })
   })
 })
+
+experiment('logging with mergeHapiLogData option enabled', () => {
+  test('log event data is merged into pino\'s log object', (done) => {
+    const server = getServer()
+    const stream = sink((data) => {
+      expect(data).to.include({ hello: 'world' })
+      done()
+    })
+    const plugin = {
+      register: Pino.register,
+      options: {
+        stream: stream,
+        level: 'info',
+        mergeHapiLogData: true
+      }
+    }
+
+    server.register(plugin, (err) => {
+      expect(err).to.be.undefined()
+      server.log(['info'], { hello: 'world' })
+    })
+  })
+
+  test('when data is string, merge it as msg property', (done) => {
+    const server = getServer()
+    const stream = sink((data) => {
+      expect(data).to.include({ msg: 'hello world' })
+      done()
+    })
+    const plugin = {
+      register: Pino.register,
+      options: {
+        stream: stream,
+        level: 'info',
+        mergeHapiLogData: true
+      }
+    }
+
+    server.register(plugin, (err) => {
+      expect(err).to.be.undefined()
+      server.log(['info'], 'hello world')
+    })
+  })
+})