Repository: pinojs/pino (js)

Issue:
Bug: level prop in browser doesn't work
http://getpino.io/#/docs/API?id=level

Changing minimum logging level doesn't work as described in the documentation.

```
const pino = require("pino")({
  level: "info"
});

pino.info("display. OK");
pino.trace("doens't display. OK");

pino.level = "trace";
pino.trace("doens't display. ERROR");
```

pino version:  4.16.1

Pull request description:
Add level and levelVal propreties to browser logger
Closes #418 

Patch:
diff --git a/browser.js b/browser.js
index 2581a03..97dcb49 100644
--- a/browser.js
+++ b/browser.js
@@ -41,25 +41,25 @@ function pino (opts) {
   }
   if (opts.enabled === false) opts.level = 'silent'
   var level = opts.level || 'info'
-  var val = pino.levels.values[level]
-  if (level === 'silent') val = Infinity
   var logger = Object.create(proto)
   if (!logger.log) logger.log = noop
 
+  Object.defineProperty(logger, 'levelVal', {
+    get: getLevelVal
+  })
+  Object.defineProperty(logger, 'level', {
+    get: getLevel,
+    set: setLevel
+  })
+
   var setOpts = {
     transmit: transmit,
     serialize: serialize,
     asObject: opts.browser.asObject,
-    levels: levels,
-    level: level
+    levels: levels
   }
-
-  set(setOpts, logger, val, 'error', 'log') // <-- must stay first
-  set(setOpts, logger, val, 'fatal', 'error')
-  set(setOpts, logger, val, 'warn', 'error')
-  set(setOpts, logger, val, 'info', 'log')
-  set(setOpts, logger, val, 'debug', 'log')
-  set(setOpts, logger, val, 'trace', 'log')
+  logger.levels = pino.levels
+  logger.level = level
 
   logger.setMaxListeners = logger.getMaxListeners =
   logger.emit = logger.addListener = logger.on =
@@ -75,6 +75,29 @@ function pino (opts) {
 
   if (transmit) logger._logEvent = createLogEventShape()
 
+  function getLevelVal () {
+    return this.level === 'silent'
+      ? Infinity
+      : this.levels.values[this.level]
+  }
+
+  function getLevel () {
+    return this._level
+  }
+  function setLevel (level) {
+    if (level !== 'silent' && !this.levels.values[level]) {
+      throw Error('unknown level ' + level)
+    }
+    this._level = level
+
+    set(setOpts, logger, 'error', 'log') // <-- must stay first
+    set(setOpts, logger, 'fatal', 'error')
+    set(setOpts, logger, 'warn', 'error')
+    set(setOpts, logger, 'info', 'log')
+    set(setOpts, logger, 'debug', 'log')
+    set(setOpts, logger, 'trace', 'log')
+  }
+
   function child (bindings) {
     if (!bindings) {
       throw new Error('missing bindings for child Pino')
@@ -105,7 +128,6 @@ function pino (opts) {
     Child.prototype = this
     return new Child(this)
   }
-  logger.levels = pino.levels
   return logger
 }
 
@@ -132,11 +154,12 @@ pino.levels = {
 
 pino.stdSerializers = stdSerializers
 
-function set (opts, logger, val, level, fallback) {
+function set (opts, logger, level, fallback) {
+  var val = logger.levelVal
   logger[level] = val > pino.levels.values[level] ? noop
     : (logger[level] ? logger[level] : (_console[level] || _console[fallback] || noop))
 
-  wrap(opts, logger, val, level)
+  wrap(opts, logger, logger.val, level)
 }
 
 function wrap (opts, logger, val, level) {
@@ -156,7 +179,7 @@ function wrap (opts, logger, val, level) {
       else write.apply(proto, args)
 
       if (opts.transmit) {
-        var transmitLevel = opts.transmit.level || opts.level
+        var transmitLevel = opts.transmit.level || logger.level
         var transmitValue = pino.levels.values[transmitLevel]
         var methodValue = pino.levels.values[level]
         if (methodValue < transmitValue) return
@@ -165,7 +188,7 @@ function wrap (opts, logger, val, level) {
           methodLevel: level,
           methodValue: methodValue,
           transmitLevel: transmitLevel,
-          transmitValue: pino.levels.values[opts.transmit.level || opts.level],
+          transmitValue: pino.levels.values[opts.transmit.level || logger.level],
           send: opts.transmit.send,
           val: val
         }, args)


Test patch:
diff --git a/test/browser.levels.test.js b/test/browser.levels.test.js
new file mode 100644
index 0000000..27f5efc
--- /dev/null
+++ b/test/browser.levels.test.js
@@ -0,0 +1,157 @@
+'use strict'
+var test = require('tape')
+var pino = require('../browser')
+
+test('set the level by string', function (t) {
+  t.plan(4)
+  var expected = [
+    {
+      level: 50,
+      msg: 'this is an error'
+    },
+    {
+      level: 60,
+      msg: 'this is fatal'
+    }
+  ]
+  var instance = pino({
+    browser: {
+      write: function (actual) {
+        checkLogObjects(t, actual, expected.shift())
+      }
+    }
+  })
+
+  instance.level = 'error'
+  instance.info('hello world')
+  instance.error('this is an error')
+  instance.fatal('this is fatal')
+  t.end()
+})
+
+test('set the level via constructor', function (t) {
+  t.plan(4)
+  var expected = [
+    {
+      level: 50,
+      msg: 'this is an error'
+    },
+    {
+      level: 60,
+      msg: 'this is fatal'
+    }
+  ]
+  var instance = pino({
+    level: 'error',
+    browser: {
+      write: function (actual) {
+        checkLogObjects(t, actual, expected.shift())
+      }
+    }
+  })
+
+  instance.info('hello world')
+  instance.error('this is an error')
+  instance.fatal('this is fatal')
+  t.end()
+})
+
+test('the wrong level throws', function (t) {
+  t.plan(1)
+  var instance = pino()
+  t.throws(function () {
+    instance.level = 'kaboom'
+  })
+})
+
+test('the wrong level by number throws', function (t) {
+  t.plan(1)
+  var instance = pino()
+  t.throws(function () {
+    instance.levelVal = 55
+  })
+})
+
+test('exposes level string mappings', function (t) {
+  t.plan(1)
+  t.equal(pino.levels.values.error, 50)
+})
+
+test('exposes level number mappings', function (t) {
+  t.plan(1)
+  t.equal(pino.levels.labels[50], 'error')
+})
+
+test('returns level integer', function (t) {
+  t.plan(1)
+  var instance = pino({level: 'error'})
+  t.equal(instance.levelVal, 50)
+})
+
+test('silent level via constructor', function (t) {
+  var instance = pino({
+    level: 'silent',
+    browser: {
+      write: function (actual) {
+        t.fail('no data should be logged')
+      }
+    }
+  })
+
+  Object.keys(pino.levels.values).forEach(function (level) {
+    instance[level]('hello world')
+  })
+  t.end()
+})
+
+test('silent level by string', function (t) {
+  var instance = pino({
+    browser: {
+      write: function (actual) {
+        t.fail('no data should be logged')
+      }
+    }
+  })
+
+  instance.level = 'silent'
+
+  Object.keys(pino.levels.values).forEach(function (level) {
+    instance[level]('hello world')
+  })
+  t.end()
+})
+
+test('exposed levels', function (t) {
+  t.plan(1)
+  t.deepEqual(Object.keys(pino.levels.values), [
+    'fatal',
+    'error',
+    'warn',
+    'info',
+    'debug',
+    'trace'
+  ])
+})
+
+test('exposed labels', function (t) {
+  t.plan(1)
+  t.deepEqual(Object.keys(pino.levels.labels), [
+    '10',
+    '20',
+    '30',
+    '40',
+    '50',
+    '60'
+  ])
+})
+
+function checkLogObjects (t, actual, expected) {
+  t.ok(actual.time <= Date.now(), 'time is greater than Date.now()')
+
+  var actualCopy = Object.assign({}, actual)
+  var expectedCopy = Object.assign({}, expected)
+  delete actualCopy.time
+  delete expectedCopy.time
+
+  t.deepEqual(actualCopy, expectedCopy)
+}