Repository: pinojs/pino
Pull request #365 (2018-02-23 16:06:09)

introducing global serializer
Resolves
https://github.com/pinojs/pino/issues/362

Please, let me know if I missed anything

davidmarkclements: Symbol.for is a global namespace - over time collisions (especially against something like *) willl become likely. In general we should prefix Symbol.for namespaces with `pino.` - eg `Symbol.for('pino.*')`

Also could you please post before/after benchmark results here
PavelPolyakov: I used `Symbol.for('*')` alike the [metadata one's usage](https://github.com/pinojs/pino/blob/master/docs/API.md#metadata) . I think probability that anyone would have `Symbol.for('*')` in the object they are going to serialise is the same as having `Symbol.for('pino.*')`.

Here are the results on my MacPro mid 2015:
`master`: https://pastebin.com/tzjKiwv7
`feature/global-serializer`: https://pastebin.com/2bDWnrmM
davidmarkclements: Yeah see I disagree with metadata usage – I think it needs to be prefixed as well. 

@mcollina @jsumners – agree/disagree? 

Re benchmarks they look good!
jsumners: I'm good with namespacing the symbols.
PavelPolyakov: @davidmarkclements @jsumners 
I can bring the namespacing as part of this PR as well, but it'd mean that we need to bump the version (from 4 to 5), since it'll not be backward compatible.
PavelPolyakov: if you want me to do it, please, approve that this can be done in scope of this PR.

We also can somehow make the `Symbol.for('needsMetadata')` usage deprecated, till the next version is not released. BUT I don't have any good idea how to do that, since I assume we can not write any deprecation message to the console. Maybe just deprecate it through the documentation?
mcollina: I think we should leave `Symbol.for('needsMedata')` as is, and then namespace the new one. So we can release this as a minor. Also, open an issue about namespacing `Symbol.for('needsMedata')` so that we remember to do it for v5. 
PavelPolyakov: Good, I've updated the new `Symbol` 
mcollina: @jsumners are you good with this?
mcollina: Thanks for all the hard work @PavelPolyakov!

Landing now.

Patch:
diff --git a/docs/API.md b/docs/API.md
index e1c9acf..0731b92 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -47,6 +47,8 @@
     of objects. These functions should return an JSONifiable object and they
     should never throw. When logging an object, each top-level property matching the exact key of a serializer
     will be serialized using the defined serializer.
+        
+    Alternatively, it is possible to register a serializer under the key `Symbol.for('pino.*')` which will act upon the complete log object, i.e. every property.
   * `timestamp` (boolean|function): Enables or disables the inclusion of a timestamp in the
     log message. If a function is supplied, it must synchronously return a JSON string
     representation of the time, e.g. `,"time":1493426328206 (which is the default).
diff --git a/pino.js b/pino.js
index bc3eff7..ec3af49 100644
--- a/pino.js
+++ b/pino.js
@@ -147,6 +147,10 @@ function asJson (obj, msg, num) {
     if (objError) {
       data += ',"type":"Error","stack":' + this.stringify(obj.stack)
     }
+    // if global serializer is set, call it first
+    if (this.serializers[Symbol.for('pino.*')]) {
+      obj = this.serializers[Symbol.for('pino.*')](obj)
+    }
     for (var key in obj) {
       value = obj[key]
       if ((notHasOwnProperty || obj.hasOwnProperty(key)) && value !== undefined) {
@@ -171,6 +175,9 @@ function asChindings (that, bindings) {
   var key
   var value
   var data = that.chindings
+  if (that.serializers[Symbol.for('pino.*')]) {
+    bindings = that.serializers[Symbol.for('pino.*')](bindings)
+  }
   for (key in bindings) {
     value = bindings[key]
     if (key !== 'level' && key !== 'serializers' && bindings.hasOwnProperty(key) && value !== undefined) {


Test patch:
diff --git a/test/serializers.test.js b/test/serializers.test.js
index bca08aa..26281a5 100644
--- a/test/serializers.test.js
+++ b/test/serializers.test.js
@@ -111,3 +111,27 @@ test('non overriden serializers are available in the children', function (t) {
   child.fatal({onlyChild: 'test'})
   parent.fatal({onlyChild: 'test'})
 })
+
+test('Symbol.for(\'pino.*\') serializer', function (t) {
+  t.plan(6)
+  var globalSerializer = {
+    [Symbol.for('pino.*')]: function (obj) {
+      if (obj.lionel === 'richie') {
+        return {hello: 'is', it: 'me', you: 'are', looking: 'for'}
+      }
+      return {lionel: 'richie'}
+    }
+  }
+  var c = 0
+  var logger = pino({serializers: globalSerializer}, sink(function (o, enc, cb) {
+    c++
+    if (c === 1) { t.match(o, {lionel: 'richie'}); t.notMatch(o, ['hello', 'it', 'you', 'looking']) }
+    if (c === 2) { t.match(o, {hello: 'is', it: 'me', you: 'are', looking: 'for'}); t.notMatch(o, ['lionel']) }
+    if (c === 3) { t.match(o, {lionel: 'richie'}); t.notMatch(o, ['pid', 'hostname']) }
+    cb()
+  }))
+
+  logger.info({hello: 'is', it: 'me', you: 'are', looking: 'for'})
+  logger.info({lionel: 'richie'})
+  logger.info('message')
+})