Repository: pinojs/pino (js)

Issue:
"timestamp" option is ignored in Browser environment
Hi, thank you for the great library.

I've run the following code.

I expected time should become ISO String, but actually the output is still epochTime.

```typescript
import pino from 'pino'

const logger = pino({
  timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`,
  browser: {
    asObject: true,
  },
})

logger.info('Running app')
```

![image](https://user-images.githubusercontent.com/10719495/92297012-a95edd00-ef75-11ea-8056-e0242bed7eb3.png)

is this intensional? Thanks in advance.

Pull request description:
Add timestamp option to browser env, closes #903
This PR makes `timestamp` option work in the same way as the node.js environment.

https://github.com/pinojs/pino/issues/903

Patch:
diff --git a/browser.js b/browser.js
index f014335..42dca3e 100644
--- a/browser.js
+++ b/browser.js
@@ -61,7 +61,8 @@ function pino (opts) {
     transmit,
     serialize,
     asObject: opts.browser.asObject,
-    levels
+    levels,
+    timestamp: getTimeFunction(opts)
   }
   logger.levels = pino.levels
   logger.level = level
@@ -160,6 +161,7 @@ pino.levels = {
 }
 
 pino.stdSerializers = stdSerializers
+pino.stdTimeFunctions = Object.assign({}, { nullTime, epochTime, unixTime, isoTime })
 
 function set (opts, logger, level, fallback) {
   var proto = Object.getPrototypeOf(logger)
@@ -174,7 +176,7 @@ function wrap (opts, logger, level) {
 
   logger[level] = (function (write) {
     return function LOG () {
-      var ts = Date.now()
+      var ts = opts.timestamp()
       var args = new Array(arguments.length)
       var proto = (Object.getPrototypeOf && Object.getPrototypeOf(this) === _console) ? _console : this
       for (var i = 0; i < args.length; i++) args[i] = arguments[i]
@@ -208,7 +210,11 @@ function asObject (logger, level, args, ts) {
   if (logger._serialize) applySerializers(args, logger._serialize, logger.serializers, logger._stdErrSerialize)
   var argsCloned = args.slice()
   var msg = argsCloned[0]
-  var o = { time: ts, level: pino.levels.values[level] }
+  var o = {}
+  if (ts) {
+    o.time = ts
+  }
+  o.level = pino.levels.values[level]
   var lvl = (logger._childLevel | 0) + 1
   if (lvl < 1) lvl = 1
   // deliberate, catching objects, arrays
@@ -298,15 +304,30 @@ function asErrValue (err) {
   return obj
 }
 
+function getTimeFunction (opts) {
+  if (typeof opts.timestamp === 'function') {
+    return opts.timestamp
+  }
+  if (opts.timestamp === false) {
+    return nullTime
+  }
+  return epochTime
+}
+
 function mock () { return {} }
 function passthrough (a) { return a }
 function noop () {}
 
+function nullTime () { return false }
+function epochTime () { return Date.now() }
+function unixTime () { return Math.round(Date.now() / 1000.0) }
+function isoTime () { return new Date(Date.now()).toISOString() } // using Date.now() for testability
+
 /* eslint-disable */
 /* istanbul ignore next */
 function pfGlobalThisOrFallback () {
   function defd (o) { return typeof o !== 'undefined' && o }
-  try { 
+  try {
     if (typeof globalThis !== 'undefined') return globalThis
     Object.defineProperty(Object.prototype, 'globalThis', {
       get: function () {
@@ -316,7 +337,7 @@ function pfGlobalThisOrFallback () {
       configurable: true
     })
     return globalThis
-  } catch (e) { 
+  } catch (e) {
     return defd(self) || defd(window) || defd(this) || {}
   }
 }


Test patch:
diff --git a/test/browser-timestamp.test.js b/test/browser-timestamp.test.js
new file mode 100644
index 0000000..994d835
--- /dev/null
+++ b/test/browser-timestamp.test.js
@@ -0,0 +1,88 @@
+'use strict'
+const test = require('tape')
+const pino = require('../browser')
+
+Date.now = () => 1599400603614
+
+test('null timestamp', ({ end, is }) => {
+  const instance = pino({
+    timestamp: pino.stdTimeFunctions.nullTime,
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, undefined)
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})
+
+test('iso timestamp', ({ end, is }) => {
+  const instance = pino({
+    timestamp: pino.stdTimeFunctions.isoTime,
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, '2020-09-06T13:56:43.614Z')
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})
+
+test('epoch timestamp', ({ end, is }) => {
+  const instance = pino({
+    timestamp: pino.stdTimeFunctions.epochTime,
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, 1599400603614)
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})
+
+test('unix timestamp', ({ end, is }) => {
+  const instance = pino({
+    timestamp: pino.stdTimeFunctions.unixTime,
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, Math.round(1599400603614 / 1000.0))
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})
+
+test('epoch timestamp by default', ({ end, is }) => {
+  const instance = pino({
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, 1599400603614)
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})
+
+test('not print timestamp if the option is false', ({ end, is }) => {
+  const instance = pino({
+    timestamp: false,
+    browser: {
+      asObject: true,
+      write: function (o) {
+        is(o.time, undefined)
+      }
+    }
+  })
+  instance.info('hello world')
+  end()
+})