You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
11 KiB
450 lines
11 KiB
'use strict' |
|
|
|
var stringifySafe = require('fast-safe-stringify') |
|
var format = require('quick-format-unescaped') |
|
var EventEmitter = require('events').EventEmitter |
|
var os = require('os') |
|
var fs = require('fs') |
|
var flatstr = require('flatstr') |
|
var once = require('once') |
|
var util = require('util') |
|
var pid = process.pid |
|
var hostname = os.hostname() |
|
var baseLog = flatstr('{"pid":' + pid + ',"hostname":"' + hostname + '",') |
|
var extend = require('object.assign').getPolyfill() |
|
|
|
var LOG_VERSION = 1 |
|
|
|
var defaultOptions = { |
|
safe: true, |
|
name: undefined, |
|
serializers: {}, |
|
timestamp: true, |
|
slowtime: false, |
|
extreme: false, |
|
level: 'info', |
|
enabled: true |
|
} |
|
|
|
var levels = { |
|
fatal: 60, |
|
error: 50, |
|
warn: 40, |
|
info: 30, |
|
debug: 20, |
|
trace: 10 |
|
} |
|
|
|
var nums = Object.keys(levels).reduce(function (o, k) { |
|
o[levels[k]] = k |
|
return o |
|
}, {}) |
|
|
|
// level string catch |
|
var lscache = Object.keys(nums).reduce(function (o, k) { |
|
o[k] = flatstr('"level":' + Number(k)) |
|
return o |
|
}, {}) |
|
|
|
function streamIsBlockable (s) { |
|
if (s.hasOwnProperty('_handle') && s._handle.hasOwnProperty('fd') && s._handle.fd) return true |
|
if (s.hasOwnProperty('fd') && s.fd) return true |
|
return false |
|
} |
|
|
|
function pino (opts, stream) { |
|
var iopts = opts |
|
var istream = stream |
|
if (iopts && (iopts.writable || iopts._writableState)) { |
|
istream = iopts |
|
iopts = defaultOptions |
|
} |
|
istream = istream || process.stdout |
|
iopts = extend({}, defaultOptions, iopts) |
|
|
|
// internal options |
|
iopts.stringify = iopts.safe ? stringifySafe : JSON.stringify |
|
iopts.formatOpts = {lowres: true} |
|
iopts.end = ',"v":' + LOG_VERSION + '}\n' |
|
iopts.cache = !iopts.extreme ? null : { |
|
size: 4096, |
|
buf: '' |
|
} |
|
iopts.chindings = '' |
|
|
|
if (iopts.enabled === false) { |
|
iopts.level = 'silent' |
|
} |
|
|
|
var logger = new Pino(iopts, istream) |
|
if (iopts.cache) { |
|
// setImmediate is causing a very weird crash: |
|
// Assertion failed: (cb_v->IsFunction()), function MakeCallback... |
|
// but setTimeout isn't *shrug* |
|
setTimeout(function () { |
|
if (!streamIsBlockable(istream)) { |
|
logger.emit('error', new Error('stream must have a file descriptor in extreme mode')) |
|
} |
|
}, 100) |
|
|
|
onExit(function (code, evt) { |
|
var buf = iopts.cache.buf |
|
if (buf) { |
|
// We need to block the process exit long enough to flush the buffer |
|
// to the destination stream. We do that by forcing a synchronous |
|
// write directly to the stream's file descriptor. |
|
var fd = (istream.fd) ? istream.fd : istream._handle.fd |
|
fs.writeSync(fd, buf) |
|
} |
|
if (!process._events[evt] || process._events[evt].length < 2 || !process._events[evt].filter(function (f) { |
|
return f + '' !== onExit.passCode + '' && f + '' !== onExit.insertCode + '' |
|
}).length) { |
|
process.exit(code) |
|
} else { |
|
return 'no exit' |
|
} |
|
}) |
|
} |
|
|
|
return logger |
|
} |
|
|
|
Object.defineProperty(pino, 'levels', { |
|
value: { |
|
values: copy({}, levels), |
|
labels: copy({}, nums) |
|
}, |
|
enumerable: true |
|
}) |
|
Object.defineProperty(pino.levels.values, 'silent', {value: 100}) |
|
Object.defineProperty(pino.levels.labels, '100', {value: 'silent'}) |
|
|
|
pino.addLevel = function addLevel (name, lvl) { |
|
if (pino.levels.values.hasOwnProperty(name)) return false |
|
if (pino.levels.labels.hasOwnProperty(lvl)) return false |
|
pino.levels.values[name] = lvl |
|
pino.levels.labels[lvl] = name |
|
lscache[lvl] = flatstr('"level":' + Number(lvl)) |
|
Pino.prototype[name] = genLog(lvl) |
|
return true |
|
} |
|
|
|
function Pino (opts, stream) { |
|
this.stream = stream |
|
this.serializers = opts.serializers |
|
this.stringify = opts.stringify |
|
this.end = opts.end |
|
this.name = opts.name |
|
this.timestamp = opts.timestamp |
|
this.slowtime = opts.slowtime |
|
this.chindings = opts.chindings |
|
this.cache = opts.cache |
|
this.formatOpts = opts.formatOpts |
|
this._setLevel(opts.level) |
|
this._baseLog = flatstr(baseLog + |
|
(this.name === undefined ? '' : '"name":' + this.stringify(this.name) + ',')) |
|
|
|
if (opts.timestamp === false) { |
|
this.time = getNoTime |
|
} else if (opts.slowtime) { |
|
this.time = getSlowTime |
|
} else { |
|
this.time = getTime |
|
} |
|
} |
|
|
|
Pino.prototype = new EventEmitter() |
|
|
|
Pino.prototype.fatal = genLog(levels.fatal) |
|
Pino.prototype.error = genLog(levels.error) |
|
Pino.prototype.warn = genLog(levels.warn) |
|
Pino.prototype.info = genLog(levels.info) |
|
Pino.prototype.debug = genLog(levels.debug) |
|
Pino.prototype.trace = genLog(levels.trace) |
|
|
|
Object.defineProperty(Pino.prototype, 'levels', {value: pino.levels}) |
|
|
|
Object.defineProperty(Pino.prototype, 'levelVal', { |
|
get: function getLevelVal () { |
|
return this._levelVal |
|
}, |
|
set: function setLevelVal (num) { |
|
if (typeof num === 'string') { return this._setLevel(num) } |
|
|
|
if (this.emit) { |
|
this.emit('level-change', pino.levels.labels[num], num, pino.levels.labels[this._levelVal], this._levelVal) |
|
} |
|
|
|
this._levelVal = num |
|
|
|
for (var key in pino.levels.values) { |
|
if (num > pino.levels.values[key]) { |
|
this[key] = noop |
|
continue |
|
} |
|
this[key] = Pino.prototype[key] |
|
} |
|
} |
|
}) |
|
|
|
Pino.prototype._setLevel = function _setLevel (level) { |
|
if (typeof level === 'number') { level = pino.levels.labels[level] } |
|
|
|
if (!pino.levels.values[level]) { |
|
throw new Error('unknown level ' + level) |
|
} |
|
this.levelVal = pino.levels.values[level] |
|
} |
|
|
|
Pino.prototype._getLevel = function _getLevel (level) { |
|
return pino.levels.labels[this.levelVal] |
|
} |
|
|
|
Object.defineProperty(Pino.prototype, 'level', { |
|
get: Pino.prototype._getLevel, |
|
set: Pino.prototype._setLevel |
|
}) |
|
|
|
Object.defineProperty( |
|
Pino.prototype, |
|
'LOG_VERSION', |
|
{value: LOG_VERSION} |
|
) |
|
|
|
Pino.prototype.asJson = function asJson (obj, msg, num) { |
|
if (!msg && obj instanceof Error) { |
|
msg = obj.message |
|
} |
|
var data = this._baseLog + lscache[num] + this.time() |
|
// to catch both null and undefined |
|
/* eslint-disable eqeqeq */ |
|
if (msg != undefined) { |
|
data += ',"msg":' + JSON.stringify(msg) |
|
} |
|
var value |
|
if (obj) { |
|
if (obj.stack) { |
|
data += ',"type":"Error","stack":' + this.stringify(obj.stack) |
|
} |
|
for (var key in obj) { |
|
value = obj[key] |
|
if (obj.hasOwnProperty(key) && value !== undefined) { |
|
value = this.stringify(this.serializers[key] ? this.serializers[key](value) : value) |
|
if (value !== undefined) { |
|
data += ',"' + key + '":' + value |
|
} |
|
} |
|
} |
|
} |
|
return data + this.chindings + this.end |
|
} |
|
|
|
function getNoTime () { |
|
return '' |
|
} |
|
|
|
function getTime () { |
|
return ',"time":' + Date.now() |
|
} |
|
|
|
function getSlowTime () { |
|
return ',"time":"' + (new Date()).toISOString() + '"' |
|
} |
|
|
|
Pino.prototype.child = function child (bindings) { |
|
if (!bindings) { |
|
throw new Error('missing bindings for child Pino') |
|
} |
|
|
|
var data = ',' |
|
var value |
|
var key |
|
for (key in bindings) { |
|
value = bindings[key] |
|
if (key !== 'level' && key !== 'serializers' && bindings.hasOwnProperty(key) && value !== undefined) { |
|
value = this.serializers[key] ? this.serializers[key](value) : value |
|
data += '"' + key + '":' + this.stringify(value) + ',' |
|
} |
|
} |
|
data = this.chindings + data.substr(0, data.length - 1) |
|
|
|
var opts = { |
|
level: bindings.level || this.level, |
|
serializers: bindings.hasOwnProperty('serializers') ? extend(this.serializers, bindings.serializers) : this.serializers, |
|
stringify: this.stringify, |
|
end: this.end, |
|
name: this.name, |
|
timestamp: this.timestamp, |
|
slowtime: this.slowtime, |
|
chindings: data, |
|
cache: this.cache, |
|
formatOpts: this.formatOpts |
|
} |
|
|
|
return new Pino(opts, this.stream) |
|
} |
|
|
|
Pino.prototype.write = function (obj, msg, num) { |
|
var s = this.asJson(obj, msg, num) |
|
if (!this.cache) { |
|
this.stream.write(flatstr(s)) |
|
return |
|
} |
|
|
|
this.cache.buf += s |
|
if (this.cache.buf.length > this.cache.size) { |
|
this.stream.write(flatstr(this.cache.buf)) |
|
this.cache.buf = '' |
|
} |
|
} |
|
|
|
Pino.prototype.flush = function () { |
|
if (!this.cache) { |
|
return |
|
} |
|
|
|
this.stream.write(flatstr(this.cache.buf)) |
|
this.cache.buf = '' |
|
} |
|
|
|
function mapHttpRequest (req) { |
|
return { |
|
req: asReqValue(req) |
|
} |
|
} |
|
|
|
function mapHttpResponse (res) { |
|
return { |
|
res: asResValue(res) |
|
} |
|
} |
|
|
|
function asReqValue (req) { |
|
return { |
|
method: req.method, |
|
url: req.url, |
|
headers: req.headers, |
|
remoteAddress: req.connection.remoteAddress, |
|
remotePort: req.connection.remotePort |
|
} |
|
} |
|
|
|
function asResValue (res) { |
|
return { |
|
statusCode: res.statusCode, |
|
header: res._header |
|
} |
|
} |
|
|
|
function asErrValue (err) { |
|
var obj = { |
|
type: err.constructor.name, |
|
message: err.message, |
|
stack: err.stack |
|
} |
|
for (var key in err) { |
|
if (obj[key] === undefined) { |
|
obj[key] = err[key] |
|
} |
|
} |
|
return obj |
|
} |
|
|
|
function countInterp (s, i) { |
|
var n = 0 |
|
var pos = 0 |
|
while (true) { |
|
pos = s.indexOf(i, pos) |
|
if (pos >= 0) { |
|
++n |
|
pos += 2 |
|
} else break |
|
} |
|
return n |
|
} |
|
|
|
function genLog (z) { |
|
return function LOG (a, b, c, d, e, f, g, h, i, j, k) { |
|
var l = 0 |
|
var m = null |
|
var n = null |
|
var o |
|
var p |
|
if (typeof a === 'object' && a !== null) { |
|
m = a |
|
n = [b, c, d, e, f, g, h, i, j, k] |
|
l = 1 |
|
|
|
if (m.method && m.headers && m.socket) { |
|
m = mapHttpRequest(m) |
|
} else if (m.statusCode) { |
|
m = mapHttpResponse(m) |
|
} |
|
} else { |
|
n = [a, b, c, d, e, f, g, h, i, j, k] |
|
} |
|
p = n.length = arguments.length - l |
|
if (p > 1) { |
|
l = typeof a === 'string' ? countInterp(a, '%j') : 0 |
|
if (l) { |
|
n.length = l + countInterp(a, '%d') + countInterp(a, '%s') + 1 |
|
o = String(util.format.apply(null, n)) |
|
} else { |
|
o = format(n, this.formatOpts) |
|
} |
|
} else if (p) { |
|
o = n[0] |
|
} |
|
this.write(m, o, z) |
|
} |
|
} |
|
|
|
function copy (a, b) { |
|
for (var k in b) { a[k] = b[k] } |
|
return a |
|
} |
|
|
|
function onExit (fn) { |
|
var oneFn = once(fn) |
|
process.on('beforeExit', handle('beforeExit')) |
|
process.on('exit', handle('exit')) |
|
process.on('uncaughtException', handle('uncaughtException', 1)) |
|
process.on('SIGHUP', handle('SIGHUP', 129)) |
|
process.on('SIGINT', handle('SIGINT', 130)) |
|
process.on('SIGQUIT', handle('SIGQUIT', 131)) |
|
process.on('SIGTERM', handle('SIGTERM', 143)) |
|
function handle (evt, code) { |
|
onExit.passCode = function (code) { |
|
if (oneFn.value) { oneFn = once(fn) } |
|
oneFn(code, evt) |
|
} |
|
onExit.insertCode = function () { |
|
if (oneFn.value) { oneFn = once(fn) } |
|
oneFn(code, evt) |
|
} |
|
return (code === undefined) ? onExit.passCode : onExit.insertCode |
|
} |
|
} |
|
|
|
function noop () {} |
|
|
|
module.exports = pino |
|
module.exports.stdSerializers = { |
|
req: asReqValue, |
|
res: asResValue, |
|
err: asErrValue |
|
} |
|
module.exports.pretty = require('./pretty') |
|
Object.defineProperty( |
|
module.exports, |
|
'LOG_VERSION', |
|
{value: LOG_VERSION, enumerable: true} |
|
) |
|
|
|
// This is an internal API. It can change at any time, including semver-minor. |
|
// Use it at your own risk. |
|
Object.defineProperty( |
|
module.exports, |
|
'_Pino', |
|
{value: Pino} |
|
)
|
|
|