minimal browser implementation

This commit is contained in:
davidmarkclements 2016-10-24 15:49:22 +02:00
Родитель edc2ebb8fe
Коммит b914c53d0d
7 изменённых файлов: 372 добавлений и 9 удалений

4
.zuul.yml Normal file
Просмотреть файл

@ -0,0 +1,4 @@
name: pino
ui: tape
browserify:
- transform: fresh-require/transform

Просмотреть файл

@ -19,6 +19,7 @@ It also includes a shell utility to pretty-print its log files.
* [How do I rotate log files?](#rotate)
* [How do I redact sensitive information?](#redact)
* [How to use Transports with Pino](#transports)
* [Pino in the browser](#browser)
* [Caveats](#caveats)
* [Team](#team)
* [Acknowledgements](#acknowledgements)
@ -940,6 +941,13 @@ https://github.com/deviantony/docker-elk to setup an ELK stack.
[pino-socket]: https://www.npmjs.com/package/pino-socket
<a name="browser"></a>
## Pino in the browser
Pino is compatible with [`browserify`](http://npm.im) for browser side usage. This can be useful with isomorphic/universal JavaScript code.
In the browser, `pino` uses corresponding [Log4j](https://en.wikipedia.org/wiki/Log4j) `console` methods (`console.error`, `console.warn`, `console.info`, `console.debug`, `console.trace`) and uses `console.error` for any `fatal` level logs.
<a name="caveats"></a>
## Caveats

95
browser.js Normal file
Просмотреть файл

@ -0,0 +1,95 @@
'use strict'
module.exports = pino
var _console = global.console || {}
function Pino () {}
Pino.prototype = _console
function pino (opts) {
opts = opts || {}
var level = opts.level || 'info'
var val = pino.levels.values[level]
var logger = new Pino()
if (!logger.log) logger.log = noop
set(logger, val, 'error', 'log') // <-- must stay first
set(logger, val, 'fatal', 'error')
set(logger, val, 'warn', 'error')
set(logger, val, 'info', 'log')
set(logger, val, 'debug', 'log')
set(logger, val, 'trace', 'log')
logger.setMaxListeners = logger.getMaxListeners =
logger.emit = logger.addListener = logger.on =
logger.prependListener = logger.once =
logger.prependOnceListener = logger.removeListener =
logger.removeAllListeners = logger.listeners =
logger.listenerCount = logger.eventNames =
logger.write = logger.flush = noop
logger.child = function child (bindings) {
if (!bindings) {
throw new Error('missing bindings for child Pino')
}
function Child (parent) {
this.error = bind(parent, bindings, 'error')
this.fatal = bind(parent, bindings, 'fatal')
this.warn = bind(parent, bindings, 'warn')
this.info = bind(parent, bindings, 'info')
this.debug = bind(parent, bindings, 'debug')
this.trace = bind(parent, bindings, 'trace')
}
Child.prototype = this
return new Child(this)
}
logger.levels = pino.levels
return logger
}
pino._Pino = Pino
pino.LOG_VERSION = 1
pino.levels = {
values: {
fatal: 60,
error: 50,
warn: 40,
info: 30,
debug: 20,
trace: 10
},
labels: {
'10': 'trace',
'20': 'debug',
'30': 'info',
'40': 'warn',
'50': 'error',
'60': 'fatal'
}
}
pino.stdSerializers = {
req: mock,
res: mock,
err: mock
}
function bind (parent, bindings, level) {
return function () {
var args = new Array(1 + arguments.length)
args[0] = bindings
for (var i = 1; i < args.length; i++) {
args[i] = arguments[i - 1]
}
return parent[level].apply(null, args)
}
}
function set (logger, val, level, fallback) {
logger[level] = val > pino.levels.values[level] ? noop
: (logger[level] ? logger[level] : (_console[fallback] || noop))
}
function mock () { return {} }
function noop () {}

Просмотреть файл

@ -1 +0,0 @@
module.exports = function noop () {}

Просмотреть файл

@ -3,13 +3,12 @@
"version": "2.13.2",
"description": "super fast, all natural json logger",
"main": "pino.js",
"browser": {
"./pretty.js": "./noop.js"
},
"browser": "./browser.js",
"bin": {
"pino": "./pretty.js"
},
"scripts": {
"browser-test": "zuul tape test/browser.test.js --local",
"test": "standard && tap --no-cov test/*test.js",
"ci": "standard && tap --cov test/*test.js"
},
@ -37,12 +36,15 @@
"debug": "^2.2.0",
"fastbench": "^1.0.0",
"flush-write-stream": "^1.0.0",
"fresh-require": "^1.0.3",
"log": "^1.4.0",
"loglevel": "^1.4.0",
"pre-commit": "^1.1.2",
"standard": "^8.0.0",
"tap": "^7.0.0",
"winston": "^2.1.1"
"tape": "^4.6.2",
"winston": "^2.1.1",
"zuul": "^3.11.1"
},
"dependencies": {
"chalk": "^1.1.1",

Просмотреть файл

@ -2,10 +2,10 @@
var stringifySafe = require('fast-safe-stringify')
var format = require('quick-format')
var EventEmitter = require('events').EventEmitter
var os = require('os')
var flatstr = require('flatstr')
var once = require('once')
var noop = require('./noop')
var pid = process.pid
var hostname = os.hostname()
var baseLog = flatstr('{"pid":' + pid + ',"hostname":"' + hostname + '",')
@ -141,9 +141,7 @@ function Pino (level, stream, serializers, stringify, end, name, timestamp, slow
}
}
if (require('eve' + 'nts')) {
Pino.prototype = new (require('eve' + 'nts').EventEmitter)()
}
Pino.prototype = new EventEmitter()
Pino.prototype.fatal = genLog(levels.fatal)
Pino.prototype.error = genLog(levels.error)
@ -416,6 +414,8 @@ function onExit (fn) {
}
}
function noop () {}
module.exports = pino
module.exports.stdSerializers = {
req: asReqValue,

255
test/browser.test.js Normal file
Просмотреть файл

@ -0,0 +1,255 @@
'use strict'
var test = require('tape')
var fresh = require('fresh-require')
var pino = require('../browser')
levelTest('fatal')
levelTest('error')
levelTest('warn')
levelTest('info')
levelTest('debug')
levelTest('trace')
test('throw if creating child without bindings', function (t) {
t.plan(1)
var instance = pino()
t.throws(function () {
instance.child()
})
})
test('stubs write, flush and ee methods on instance', function (t) {
var instance = pino()
t.ok(isFunc(instance.setMaxListeners))
t.ok(isFunc(instance.getMaxListeners))
t.ok(isFunc(instance.emit))
t.ok(isFunc(instance.addListener))
t.ok(isFunc(instance.on))
t.ok(isFunc(instance.prependListener))
t.ok(isFunc(instance.once))
t.ok(isFunc(instance.prependOnceListener))
t.ok(isFunc(instance.removeListener))
t.ok(isFunc(instance.removeAllListeners))
t.ok(isFunc(instance.listeners))
t.ok(isFunc(instance.listenerCount))
t.ok(isFunc(instance.eventNames))
t.ok(isFunc(instance.write))
t.ok(isFunc(instance.flush))
t.is(instance.on(), undefined)
t.end()
})
test('exposes levels object', function (t) {
t.deepEqual(pino.levels, {
values: {
fatal: 60,
error: 50,
warn: 40,
info: 30,
debug: 20,
trace: 10
},
labels: {
'10': 'trace',
'20': 'debug',
'30': 'info',
'40': 'warn',
'50': 'error',
'60': 'fatal'
}
})
t.end()
})
test('exposes LOG_VERSION', function (t) {
t.is(pino.LOG_VERSION, 1)
t.end()
})
test('exposes faux _Pino constructor', function (t) {
t.ok(isFunc(pino._Pino))
t.end()
})
test('exposes faux stdSerializers', function (t) {
t.ok(pino.stdSerializers)
t.ok(pino.stdSerializers.req)
t.ok(pino.stdSerializers.res)
t.ok(pino.stdSerializers.err)
t.deepEqual(pino.stdSerializers.req(), {})
t.deepEqual(pino.stdSerializers.res(), {})
t.deepEqual(pino.stdSerializers.err(), {})
t.end()
})
consoleMethodTest('error')
consoleMethodTest('fatal', 'error')
consoleMethodTest('warn')
consoleMethodTest('info')
consoleMethodTest('debug')
consoleMethodTest('trace')
absentConsoleMethodTest('error', 'log')
absentConsoleMethodTest('warn', 'error')
absentConsoleMethodTest('info', 'log')
absentConsoleMethodTest('debug', 'log')
absentConsoleMethodTest('trace', 'log')
test('in absence of console, log methods become noops', function (t) {
var console = global.console
delete global.console
var instance = fresh('../browser', require)()
global.console = console
t.is(fnName(instance.log), 'noop')
t.is(fnName(instance.fatal), 'noop')
t.is(fnName(instance.error), 'noop')
t.is(fnName(instance.warn), 'noop')
t.is(fnName(instance.info), 'noop')
t.is(fnName(instance.debug), 'noop')
t.is(fnName(instance.trace), 'noop')
t.end()
})
test('exposes faux _Pino constructor', function (t) {
t.ok(isFunc(pino._Pino))
t.end()
})
function levelTest (name) {
test(name + ' logs', function (t) {
var msg = 'hello world'
sink(name, function (args) {
t.is(args[0], msg)
t.end()
})
pino({level: name})[name](msg)
})
test('passing objects at level ' + name, function (t) {
var msg = { hello: 'world' }
sink(name, function (args) {
t.is(args[0], msg)
t.end()
})
pino({level: name})[name](msg)
})
test('passing an object and a string at level ' + name, function (t) {
var a = { hello: 'world' }
var b = 'a string'
sink(name, function (args) {
t.is(args[0], a)
t.is(args[1], b)
t.end()
})
pino({level: name})[name](a, b)
})
test('formatting logs as ' + name, function (t) {
sink(name, function (args) {
t.is(args[0], 'hello %d')
t.is(args[1], 42)
t.end()
})
pino({level: name})[name]('hello %d', 42)
})
test('passing error at level ' + name, function (t) {
var err = new Error('myerror')
sink(name, function (args) {
t.is(args[0], err)
t.end()
})
pino({level: name})[name](err)
})
test('passing error with a serializer at level ' + name, function (t) {
// in browser - should have no effect (should not crash)
var err = new Error('myerror')
sink(name, function (args) {
t.is(args[0].err, err)
t.end()
})
var instance = pino({
level: name,
serializers: {
err: pino.stdSerializers.err
}
})
instance[name]({err: err})
})
test('child logger for level ' + name, function (t) {
var msg = 'hello world'
var parent = { hello: 'world' }
sink(name, function (args) {
t.is(args[0], parent)
t.is(args[1], msg)
t.end()
})
var instance = pino({level: name})
var child = instance.child(parent)
child[name](msg)
})
test('child-child logger for level ' + name, function (t) {
var msg = 'hello world'
var grandParent = { hello: 'world' }
var parent = { hello: 'you' }
sink(name, function (args) {
t.is(args[0], grandParent)
t.is(args[1], parent)
t.is(args[2], msg)
t.end()
})
var instance = pino({level: name})
var child = instance.child(grandParent).child(parent)
child[name](msg)
})
}
function consoleMethodTest (level, method) {
if (!method) method = level
test('pino().' + level + ' uses console.' + method, function (t) {
sink(method, function (args) {
t.is(args[0], 'test')
t.end()
})
var instance = require('../browser')({level: level})
instance[level]('test')
})
}
function absentConsoleMethodTest (method, fallback) {
test('in absence of console.' + method + ', console.' + fallback + ' is used', function (t) {
var fn = console[method]
console[method] = undefined
sink(fallback, function (args) {
t.is(args[0], 'test')
t.end()
console[method] = fn
})
var instance = require('../browser')({level: method})
instance[method]('test')
})
}
function isFunc (fn) { return typeof fn === 'function' }
function fnName (fn) {
var rx = /^\s*function\s*([^\(]*)/i
var match = rx.exec(fn)
return match && match[1]
}
function sink (method, fn) {
if (method === 'fatal') method = 'error'
var orig = console[method]
console[method] = function () {
console[method] = orig
fn(Array.prototype.slice.call(arguments))
}
}