|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var VERSION = '1.8.12'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var LOG_VERSION = 0; |
|
|
|
|
|
var xxx = function xxx(s) { |
|
var args = ['XX' + 'X: '+s].concat( |
|
Array.prototype.slice.call(arguments, 1)); |
|
console.error.apply(this, args); |
|
}; |
|
var xxx = function xxx() {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var runtimeEnv; |
|
if (typeof (process) !== 'undefined' && process.versions) { |
|
if (process.versions.nw) { |
|
runtimeEnv = 'nw'; |
|
} else if (process.versions.node) { |
|
runtimeEnv = 'node'; |
|
} |
|
} |
|
if (!runtimeEnv && typeof (window) !== 'undefined' && |
|
window.window === window) { |
|
runtimeEnv = 'browser'; |
|
} |
|
if (!runtimeEnv) { |
|
throw new Error('unknown runtime environment'); |
|
} |
|
|
|
|
|
var os, fs, dtrace; |
|
if (runtimeEnv === 'browser') { |
|
os = { |
|
hostname: function () { |
|
return window.location.host; |
|
} |
|
}; |
|
fs = {}; |
|
dtrace = null; |
|
} else { |
|
os = require('os'); |
|
fs = require('fs'); |
|
try { |
|
dtrace = require('dtrace-provider' + ''); |
|
} catch (e) { |
|
dtrace = null; |
|
} |
|
} |
|
var util = require('util'); |
|
var assert = require('assert'); |
|
var EventEmitter = require('events').EventEmitter; |
|
var stream = require('stream'); |
|
|
|
try { |
|
var safeJsonStringify = require('safe-json-stringify'); |
|
} catch (e) { |
|
safeJsonStringify = null; |
|
} |
|
if (process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY) { |
|
safeJsonStringify = null; |
|
} |
|
|
|
|
|
try { |
|
var mv = require('mv' + ''); |
|
} catch (e) { |
|
mv = null; |
|
} |
|
|
|
try { |
|
var sourceMapSupport = require('source-map-support' + ''); |
|
} catch (_) { |
|
sourceMapSupport = null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function objCopy(obj) { |
|
if (obj == null) { |
|
return obj; |
|
} else if (Array.isArray(obj)) { |
|
return obj.slice(); |
|
} else if (typeof (obj) === 'object') { |
|
var copy = {}; |
|
Object.keys(obj).forEach(function (k) { |
|
copy[k] = obj[k]; |
|
}); |
|
return copy; |
|
} else { |
|
return obj; |
|
} |
|
} |
|
|
|
var format = util.format; |
|
if (!format) { |
|
|
|
|
|
var inspect = util.inspect; |
|
var formatRegExp = /%[sdj%]/g; |
|
format = function format(f) { |
|
if (typeof (f) !== 'string') { |
|
var objects = []; |
|
for (var i = 0; i < arguments.length; i++) { |
|
objects.push(inspect(arguments[i])); |
|
} |
|
return objects.join(' '); |
|
} |
|
|
|
var i = 1; |
|
var args = arguments; |
|
var len = args.length; |
|
var str = String(f).replace(formatRegExp, function (x) { |
|
if (i >= len) |
|
return x; |
|
switch (x) { |
|
case '%s': return String(args[i++]); |
|
case '%d': return Number(args[i++]); |
|
case '%j': return fastAndSafeJsonStringify(args[i++]); |
|
case '%%': return '%'; |
|
default: |
|
return x; |
|
} |
|
}); |
|
for (var x = args[i]; i < len; x = args[++i]) { |
|
if (x === null || typeof (x) !== 'object') { |
|
str += ' ' + x; |
|
} else { |
|
str += ' ' + inspect(x); |
|
} |
|
} |
|
return str; |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function getCaller3Info() { |
|
if (this === undefined) { |
|
|
|
return; |
|
} |
|
var obj = {}; |
|
var saveLimit = Error.stackTraceLimit; |
|
var savePrepare = Error.prepareStackTrace; |
|
Error.stackTraceLimit = 3; |
|
|
|
Error.prepareStackTrace = function (_, stack) { |
|
var caller = stack[2]; |
|
if (sourceMapSupport) { |
|
caller = sourceMapSupport.wrapCallSite(caller); |
|
} |
|
obj.file = caller.getFileName(); |
|
obj.line = caller.getLineNumber(); |
|
var func = caller.getFunctionName(); |
|
if (func) |
|
obj.func = func; |
|
}; |
|
Error.captureStackTrace(this, getCaller3Info); |
|
this.stack; |
|
|
|
Error.stackTraceLimit = saveLimit; |
|
Error.prepareStackTrace = savePrepare; |
|
return obj; |
|
} |
|
|
|
|
|
function _indent(s, indent) { |
|
if (!indent) indent = ' '; |
|
var lines = s.split(/\r?\n/g); |
|
return indent + lines.join('\n' + indent); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _warn(msg, dedupKey) { |
|
assert.ok(msg); |
|
if (dedupKey) { |
|
if (_warned[dedupKey]) { |
|
return; |
|
} |
|
_warned[dedupKey] = true; |
|
} |
|
process.stderr.write(msg + '\n'); |
|
} |
|
function _haveWarned(dedupKey) { |
|
return _warned[dedupKey]; |
|
} |
|
var _warned = {}; |
|
|
|
|
|
function ConsoleRawStream() {} |
|
ConsoleRawStream.prototype.write = function (rec) { |
|
if (rec.level < INFO) { |
|
console.log(rec); |
|
} else if (rec.level < WARN) { |
|
console.info(rec); |
|
} else if (rec.level < ERROR) { |
|
console.warn(rec); |
|
} else { |
|
console.error(rec); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
var TRACE = 10; |
|
var DEBUG = 20; |
|
var INFO = 30; |
|
var WARN = 40; |
|
var ERROR = 50; |
|
var FATAL = 60; |
|
|
|
var levelFromName = { |
|
'trace': TRACE, |
|
'debug': DEBUG, |
|
'info': INFO, |
|
'warn': WARN, |
|
'error': ERROR, |
|
'fatal': FATAL |
|
}; |
|
var nameFromLevel = {}; |
|
Object.keys(levelFromName).forEach(function (name) { |
|
nameFromLevel[levelFromName[name]] = name; |
|
}); |
|
|
|
|
|
var dtp = undefined; |
|
var probes = dtrace && {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resolveLevel(nameOrNum) { |
|
var level; |
|
var type = typeof (nameOrNum); |
|
if (type === 'string') { |
|
level = levelFromName[nameOrNum.toLowerCase()]; |
|
if (!level) { |
|
throw new Error(format('unknown level name: "%s"', nameOrNum)); |
|
} |
|
} else if (type !== 'number') { |
|
throw new TypeError(format('cannot resolve level: invalid arg (%s):', |
|
type, nameOrNum)); |
|
} else if (nameOrNum < 0 || Math.floor(nameOrNum) !== nameOrNum) { |
|
throw new TypeError(format('level is not a positive integer: %s', |
|
nameOrNum)); |
|
} else { |
|
level = nameOrNum; |
|
} |
|
return level; |
|
} |
|
|
|
|
|
function isWritable(obj) { |
|
if (obj instanceof stream.Writable) { |
|
return true; |
|
} |
|
return typeof (obj.write) === 'function'; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Logger(options, _childOptions, _childSimple) { |
|
xxx('Logger start:', options) |
|
if (!(this instanceof Logger)) { |
|
return new Logger(options, _childOptions); |
|
} |
|
|
|
|
|
var parent; |
|
if (_childOptions !== undefined) { |
|
parent = options; |
|
options = _childOptions; |
|
if (!(parent instanceof Logger)) { |
|
throw new TypeError( |
|
'invalid Logger creation: do not pass a second arg'); |
|
} |
|
} |
|
if (!options) { |
|
throw new TypeError('options (object) is required'); |
|
} |
|
if (!parent) { |
|
if (!options.name) { |
|
throw new TypeError('options.name (string) is required'); |
|
} |
|
} else { |
|
if (options.name) { |
|
throw new TypeError( |
|
'invalid options.name: child cannot set logger name'); |
|
} |
|
} |
|
if (options.stream && options.streams) { |
|
throw new TypeError('cannot mix "streams" and "stream" options'); |
|
} |
|
if (options.streams && !Array.isArray(options.streams)) { |
|
throw new TypeError('invalid options.streams: must be an array') |
|
} |
|
if (options.serializers && (typeof (options.serializers) !== 'object' || |
|
Array.isArray(options.serializers))) { |
|
throw new TypeError('invalid options.serializers: must be an object') |
|
} |
|
|
|
EventEmitter.call(this); |
|
|
|
|
|
if (parent && _childSimple) { |
|
|
|
|
|
this._isSimpleChild = true; |
|
|
|
this._level = parent._level; |
|
this.streams = parent.streams; |
|
this.serializers = parent.serializers; |
|
this.src = parent.src; |
|
var fields = this.fields = {}; |
|
var parentFieldNames = Object.keys(parent.fields); |
|
for (var i = 0; i < parentFieldNames.length; i++) { |
|
var name = parentFieldNames[i]; |
|
fields[name] = parent.fields[name]; |
|
} |
|
var names = Object.keys(options); |
|
for (var i = 0; i < names.length; i++) { |
|
var name = names[i]; |
|
fields[name] = options[name]; |
|
} |
|
return; |
|
} |
|
|
|
|
|
var self = this; |
|
if (parent) { |
|
this._level = parent._level; |
|
this.streams = []; |
|
for (var i = 0; i < parent.streams.length; i++) { |
|
var s = objCopy(parent.streams[i]); |
|
s.closeOnExit = false; |
|
this.streams.push(s); |
|
} |
|
this.serializers = objCopy(parent.serializers); |
|
this.src = parent.src; |
|
this.fields = objCopy(parent.fields); |
|
if (options.level) { |
|
this.level(options.level); |
|
} |
|
} else { |
|
this._level = Number.POSITIVE_INFINITY; |
|
this.streams = []; |
|
this.serializers = null; |
|
this.src = false; |
|
this.fields = {}; |
|
} |
|
|
|
if (!dtp && dtrace) { |
|
dtp = dtrace.createDTraceProvider('bunyan'); |
|
|
|
for (var level in levelFromName) { |
|
var probe; |
|
|
|
probes[levelFromName[level]] = probe = |
|
dtp.addProbe('log-' + level, 'char *'); |
|
|
|
|
|
probe.dtp = dtp; |
|
} |
|
|
|
dtp.enable(); |
|
} |
|
|
|
|
|
|
|
if (options.stream) { |
|
self.addStream({ |
|
type: 'stream', |
|
stream: options.stream, |
|
closeOnExit: false, |
|
level: options.level |
|
}); |
|
} else if (options.streams) { |
|
options.streams.forEach(function (s) { |
|
self.addStream(s, options.level); |
|
}); |
|
} else if (parent && options.level) { |
|
this.level(options.level); |
|
} else if (!parent) { |
|
if (runtimeEnv === 'browser') { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.addStream({ |
|
type: 'raw', |
|
stream: new ConsoleRawStream(), |
|
closeOnExit: false, |
|
level: options.level |
|
}); |
|
} else { |
|
self.addStream({ |
|
type: 'stream', |
|
stream: process.stdout, |
|
closeOnExit: false, |
|
level: options.level |
|
}); |
|
} |
|
} |
|
if (options.serializers) { |
|
self.addSerializers(options.serializers); |
|
} |
|
if (options.src) { |
|
this.src = true; |
|
} |
|
xxx('Logger: ', self) |
|
|
|
|
|
|
|
|
|
|
|
|
|
var fields = objCopy(options); |
|
delete fields.stream; |
|
delete fields.level; |
|
delete fields.streams; |
|
delete fields.serializers; |
|
delete fields.src; |
|
if (this.serializers) { |
|
this._applySerializers(fields); |
|
} |
|
if (!fields.hostname && !self.fields.hostname) { |
|
fields.hostname = os.hostname(); |
|
} |
|
if (!fields.pid) { |
|
fields.pid = process.pid; |
|
} |
|
Object.keys(fields).forEach(function (k) { |
|
self.fields[k] = fields[k]; |
|
}); |
|
} |
|
|
|
util.inherits(Logger, EventEmitter); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.addStream = function addStream(s, defaultLevel) { |
|
var self = this; |
|
if (defaultLevel === null || defaultLevel === undefined) { |
|
defaultLevel = INFO; |
|
} |
|
|
|
s = objCopy(s); |
|
|
|
|
|
if (!s.type) { |
|
if (s.stream) { |
|
s.type = 'stream'; |
|
} else if (s.path) { |
|
s.type = 'file' |
|
} |
|
} |
|
s.raw = (s.type === 'raw'); |
|
|
|
if (s.level !== undefined) { |
|
s.level = resolveLevel(s.level); |
|
} else { |
|
s.level = resolveLevel(defaultLevel); |
|
} |
|
if (s.level < self._level) { |
|
self._level = s.level; |
|
} |
|
|
|
switch (s.type) { |
|
case 'stream': |
|
assert.ok(isWritable(s.stream), |
|
'"stream" stream is not writable: ' + util.inspect(s.stream)); |
|
|
|
if (!s.closeOnExit) { |
|
s.closeOnExit = false; |
|
} |
|
break; |
|
case 'file': |
|
if (s.reemitErrorEvents === undefined) { |
|
s.reemitErrorEvents = true; |
|
} |
|
if (!s.stream) { |
|
s.stream = fs.createWriteStream(s.path, |
|
{flags: 'a', encoding: 'utf8'}); |
|
if (!s.closeOnExit) { |
|
s.closeOnExit = true; |
|
} |
|
} else { |
|
if (!s.closeOnExit) { |
|
s.closeOnExit = false; |
|
} |
|
} |
|
break; |
|
case 'rotating-file': |
|
assert.ok(!s.stream, |
|
'"rotating-file" stream should not give a "stream"'); |
|
assert.ok(s.path); |
|
assert.ok(mv, '"rotating-file" stream type is not supported: ' |
|
+ 'missing "mv" module'); |
|
s.stream = new RotatingFileStream(s); |
|
if (!s.closeOnExit) { |
|
s.closeOnExit = true; |
|
} |
|
break; |
|
case 'raw': |
|
if (!s.closeOnExit) { |
|
s.closeOnExit = false; |
|
} |
|
break; |
|
default: |
|
throw new TypeError('unknown stream type "' + s.type + '"'); |
|
} |
|
|
|
if (s.reemitErrorEvents && typeof (s.stream.on) === 'function') { |
|
|
|
|
|
s.stream.on('error', function onStreamError(err) { |
|
self.emit('error', err, s); |
|
}); |
|
} |
|
|
|
self.streams.push(s); |
|
delete self.haveNonRawStreams; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.addSerializers = function addSerializers(serializers) { |
|
var self = this; |
|
|
|
if (!self.serializers) { |
|
self.serializers = {}; |
|
} |
|
Object.keys(serializers).forEach(function (field) { |
|
var serializer = serializers[field]; |
|
if (typeof (serializer) !== 'function') { |
|
throw new TypeError(format( |
|
'invalid serializer for "%s" field: must be a function', |
|
field)); |
|
} else { |
|
self.serializers[field] = serializer; |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.child = function (options, simple) { |
|
return new (this.constructor)(this, options || {}, simple); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.reopenFileStreams = function () { |
|
var self = this; |
|
self.streams.forEach(function (s) { |
|
if (s.type === 'file') { |
|
if (s.stream) { |
|
|
|
|
|
s.stream.end(); |
|
s.stream.destroySoon(); |
|
delete s.stream; |
|
} |
|
s.stream = fs.createWriteStream(s.path, |
|
{flags: 'a', encoding: 'utf8'}); |
|
s.stream.on('error', function (err) { |
|
self.emit('error', err, s); |
|
}); |
|
} |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.level = function level(value) { |
|
if (value === undefined) { |
|
return this._level; |
|
} |
|
var newLevel = resolveLevel(value); |
|
var len = this.streams.length; |
|
for (var i = 0; i < len; i++) { |
|
this.streams[i].level = newLevel; |
|
} |
|
this._level = newLevel; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.levels = function levels(name, value) { |
|
if (name === undefined) { |
|
assert.equal(value, undefined); |
|
return this.streams.map( |
|
function (s) { return s.level }); |
|
} |
|
var stream; |
|
if (typeof (name) === 'number') { |
|
stream = this.streams[name]; |
|
if (stream === undefined) { |
|
throw new Error('invalid stream index: ' + name); |
|
} |
|
} else { |
|
var len = this.streams.length; |
|
for (var i = 0; i < len; i++) { |
|
var s = this.streams[i]; |
|
if (s.name === name) { |
|
stream = s; |
|
break; |
|
} |
|
} |
|
if (!stream) { |
|
throw new Error(format('no stream with name "%s"', name)); |
|
} |
|
} |
|
if (value === undefined) { |
|
return stream.level; |
|
} else { |
|
var newLevel = resolveLevel(value); |
|
stream.level = newLevel; |
|
if (newLevel < this._level) { |
|
this._level = newLevel; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype._applySerializers = function (fields, excludeFields) { |
|
var self = this; |
|
|
|
xxx('_applySerializers: excludeFields', excludeFields); |
|
|
|
|
|
|
|
Object.keys(this.serializers).forEach(function (name) { |
|
if (fields[name] === undefined || |
|
(excludeFields && excludeFields[name])) |
|
{ |
|
return; |
|
} |
|
xxx('_applySerializers; apply to "%s" key', name) |
|
try { |
|
fields[name] = self.serializers[name](fields[name]); |
|
} catch (err) { |
|
_warn(format('bunyan: ERROR: Exception thrown from the "%s" ' |
|
+ 'Bunyan serializer. This should never happen. This is a bug ' |
|
+ 'in that serializer function.\n%s', |
|
name, err.stack || err)); |
|
fields[name] = format('(Error in Bunyan log "%s" serializer ' |
|
+ 'broke field. See stderr for details.)', name); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype._emit = function (rec, noemit) { |
|
var i; |
|
|
|
|
|
|
|
if (this.haveNonRawStreams === undefined) { |
|
this.haveNonRawStreams = false; |
|
for (i = 0; i < this.streams.length; i++) { |
|
if (!this.streams[i].raw) { |
|
this.haveNonRawStreams = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
var str; |
|
if (noemit || this.haveNonRawStreams) { |
|
str = fastAndSafeJsonStringify(rec) + '\n'; |
|
} |
|
|
|
if (noemit) |
|
return str; |
|
|
|
var level = rec.level; |
|
for (i = 0; i < this.streams.length; i++) { |
|
var s = this.streams[i]; |
|
if (s.level <= level) { |
|
xxx('writing log rec "%s" to "%s" stream (%d <= %d): %j', |
|
rec.msg, s.type, s.level, level, rec); |
|
s.stream.write(s.raw ? rec : str); |
|
} |
|
}; |
|
|
|
return str; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function mkRecord(log, minLevel, args) { |
|
var excludeFields, fields, msgArgs; |
|
if (args[0] instanceof Error) { |
|
|
|
fields = { |
|
|
|
err: (log.serializers && log.serializers.err |
|
? log.serializers.err(args[0]) |
|
: Logger.stdSerializers.err(args[0])) |
|
}; |
|
excludeFields = {err: true}; |
|
if (args.length === 1) { |
|
msgArgs = [fields.err.message]; |
|
} else { |
|
msgArgs = args.slice(1); |
|
} |
|
} else if (typeof (args[0]) !== 'object' || Array.isArray(args[0])) { |
|
|
|
fields = null; |
|
msgArgs = args.slice(); |
|
} else if (Buffer.isBuffer(args[0])) { |
|
|
|
|
|
fields = null; |
|
msgArgs = args.slice(); |
|
msgArgs[0] = util.inspect(msgArgs[0]); |
|
} else { |
|
fields = args[0]; |
|
if (fields && args.length === 1 && fields.err && |
|
fields.err instanceof Error) |
|
{ |
|
msgArgs = [fields.err.message]; |
|
} else { |
|
msgArgs = args.slice(1); |
|
} |
|
} |
|
|
|
|
|
var rec = objCopy(log.fields); |
|
var level = rec.level = minLevel; |
|
var recFields = (fields ? objCopy(fields) : null); |
|
if (recFields) { |
|
if (log.serializers) { |
|
log._applySerializers(recFields, excludeFields); |
|
} |
|
Object.keys(recFields).forEach(function (k) { |
|
rec[k] = recFields[k]; |
|
}); |
|
} |
|
rec.msg = format.apply(log, msgArgs); |
|
if (!rec.time) { |
|
rec.time = (new Date()); |
|
} |
|
|
|
if (log.src && !rec.src) { |
|
rec.src = getCaller3Info() |
|
} |
|
rec.v = LOG_VERSION; |
|
|
|
return rec; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function mkProbeArgs(str, log, minLevel, msgArgs) { |
|
return [ str || log._emit(mkRecord(log, minLevel, msgArgs), true) ]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function mkLogEmitter(minLevel) { |
|
return function () { |
|
var log = this; |
|
var str = null; |
|
var rec = null; |
|
|
|
if (!this._emit) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
var dedupKey = 'unbound'; |
|
if (!_haveWarned[dedupKey]) { |
|
var caller = getCaller3Info(); |
|
_warn(format('bunyan usage error: %s:%s: attempt to log ' |
|
+ 'with an unbound log method: `this` is: %s', |
|
caller.file, caller.line, util.inspect(this)), |
|
dedupKey); |
|
} |
|
return; |
|
} else if (arguments.length === 0) { |
|
return (this._level <= minLevel); |
|
} |
|
|
|
var msgArgs = new Array(arguments.length); |
|
for (var i = 0; i < msgArgs.length; ++i) { |
|
msgArgs[i] = arguments[i]; |
|
} |
|
|
|
if (this._level <= minLevel) { |
|
rec = mkRecord(log, minLevel, msgArgs); |
|
str = this._emit(rec); |
|
} |
|
|
|
if (probes) { |
|
probes[minLevel].fire(mkProbeArgs, str, log, minLevel, msgArgs); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.prototype.trace = mkLogEmitter(TRACE); |
|
Logger.prototype.debug = mkLogEmitter(DEBUG); |
|
Logger.prototype.info = mkLogEmitter(INFO); |
|
Logger.prototype.warn = mkLogEmitter(WARN); |
|
Logger.prototype.error = mkLogEmitter(ERROR); |
|
Logger.prototype.fatal = mkLogEmitter(FATAL); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger.stdSerializers = {}; |
|
|
|
|
|
Logger.stdSerializers.req = function (req) { |
|
if (!req || !req.connection) |
|
return req; |
|
return { |
|
method: req.method, |
|
url: req.url, |
|
headers: req.headers, |
|
remoteAddress: req.connection.remoteAddress, |
|
remotePort: req.connection.remotePort |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
Logger.stdSerializers.res = function (res) { |
|
if (!res || !res.statusCode) |
|
return res; |
|
return { |
|
statusCode: res.statusCode, |
|
header: res._header |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getFullErrorStack(ex) |
|
{ |
|
var ret = ex.stack || ex.toString(); |
|
if (ex.cause && typeof (ex.cause) === 'function') { |
|
var cex = ex.cause(); |
|
if (cex) { |
|
ret += '\nCaused by: ' + getFullErrorStack(cex); |
|
} |
|
} |
|
return (ret); |
|
} |
|
|
|
|
|
|
|
var errSerializer = Logger.stdSerializers.err = function (err) { |
|
if (!err || !err.stack) |
|
return err; |
|
var obj = { |
|
message: err.message, |
|
name: err.name, |
|
stack: getFullErrorStack(err), |
|
code: err.code, |
|
signal: err.signal |
|
} |
|
return obj; |
|
}; |
|
|
|
|
|
|
|
function safeCyclesSet() { |
|
var seen = new Set(); |
|
return function (key, val) { |
|
if (!val || typeof (val) !== 'object') { |
|
return val; |
|
} |
|
if (seen.has(val)) { |
|
return '[Circular]'; |
|
} |
|
seen.add(val); |
|
return val; |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function safeCyclesArray() { |
|
var seen = []; |
|
return function (key, val) { |
|
if (!val || typeof (val) !== 'object') { |
|
return val; |
|
} |
|
if (seen.indexOf(val) !== -1) { |
|
return '[Circular]'; |
|
} |
|
seen.push(val); |
|
return val; |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var safeCycles = typeof (Set) !== 'undefined' ? safeCyclesSet : safeCyclesArray; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function fastAndSafeJsonStringify(rec) { |
|
try { |
|
return JSON.stringify(rec); |
|
} catch (ex) { |
|
try { |
|
return JSON.stringify(rec, safeCycles()); |
|
} catch (e) { |
|
if (safeJsonStringify) { |
|
return safeJsonStringify(rec); |
|
} else { |
|
var dedupKey = e.stack.split(/\n/g, 3).join('\n'); |
|
_warn('bunyan: ERROR: Exception in ' |
|
+ '`JSON.stringify(rec)`. You can install the ' |
|
+ '"safe-json-stringify" module to have Bunyan fallback ' |
|
+ 'to safer stringification. Record:\n' |
|
+ _indent(format('%s\n%s', util.inspect(rec), e.stack)), |
|
dedupKey); |
|
return format('(Exception in JSON.stringify(rec): %j. ' |
|
+ 'See stderr for details.)', e.message); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
var RotatingFileStream = null; |
|
if (mv) { |
|
|
|
RotatingFileStream = function RotatingFileStream(options) { |
|
this.path = options.path; |
|
|
|
this.count = (options.count == null ? 10 : options.count); |
|
assert.equal(typeof (this.count), 'number', |
|
format('rotating-file stream "count" is not a number: %j (%s) in %j', |
|
this.count, typeof (this.count), this)); |
|
assert.ok(this.count >= 0, |
|
format('rotating-file stream "count" is not >= 0: %j in %j', |
|
this.count, this)); |
|
|
|
|
|
if (options.period) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var period = { |
|
'hourly': '1h', |
|
'daily': '1d', |
|
'weekly': '1w', |
|
'monthly': '1m', |
|
'yearly': '1y' |
|
}[options.period] || options.period; |
|
var m = /^([1-9][0-9]*)([hdwmy]|ms)$/.exec(period); |
|
if (!m) { |
|
throw new Error(format('invalid period: "%s"', options.period)); |
|
} |
|
this.periodNum = Number(m[1]); |
|
this.periodScope = m[2]; |
|
} else { |
|
this.periodNum = 1; |
|
this.periodScope = 'd'; |
|
} |
|
|
|
var lastModified = null; |
|
try { |
|
var fileInfo = fs.statSync(this.path); |
|
lastModified = fileInfo.mtime.getTime(); |
|
} |
|
catch (err) { |
|
|
|
} |
|
var rotateAfterOpen = false; |
|
if (lastModified) { |
|
var lastRotTime = this._calcRotTime(0); |
|
if (lastModified < lastRotTime) { |
|
rotateAfterOpen = true; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.stream = fs.createWriteStream(this.path, |
|
{flags: 'a', encoding: 'utf8'}); |
|
|
|
this.rotQueue = []; |
|
this.rotating = false; |
|
if (rotateAfterOpen) { |
|
this._debug('rotateAfterOpen -> call rotate()'); |
|
this.rotate(); |
|
} else { |
|
this._setupNextRot(); |
|
} |
|
} |
|
|
|
util.inherits(RotatingFileStream, EventEmitter); |
|
|
|
RotatingFileStream.prototype._debug = function () { |
|
|
|
if (false) { |
|
if (arguments.length === 0) { |
|
return true; |
|
} |
|
var args = Array.prototype.slice.call(arguments); |
|
args[0] = '[' + (new Date().toISOString()) + ', ' |
|
+ this.path + '] ' + args[0]; |
|
console.log.apply(this, args); |
|
} else { |
|
return false; |
|
} |
|
}; |
|
|
|
RotatingFileStream.prototype._setupNextRot = function () { |
|
this.rotAt = this._calcRotTime(1); |
|
this._setRotationTimer(); |
|
} |
|
|
|
RotatingFileStream.prototype._setRotationTimer = function () { |
|
var self = this; |
|
var delay = this.rotAt - Date.now(); |
|
|
|
|
|
var TIMEOUT_MAX = 2147483647; |
|
if (delay > TIMEOUT_MAX) { |
|
delay = TIMEOUT_MAX; |
|
} |
|
this.timeout = setTimeout( |
|
function () { |
|
self._debug('_setRotationTimer timeout -> call rotate()'); |
|
self.rotate(); |
|
}, |
|
delay); |
|
if (typeof (this.timeout.unref) === 'function') { |
|
this.timeout.unref(); |
|
} |
|
} |
|
|
|
RotatingFileStream.prototype._calcRotTime = |
|
function _calcRotTime(periodOffset) { |
|
this._debug('_calcRotTime: %s%s', this.periodNum, this.periodScope); |
|
var d = new Date(); |
|
|
|
this._debug(' now local: %s', d); |
|
this._debug(' now utc: %s', d.toISOString()); |
|
var rotAt; |
|
switch (this.periodScope) { |
|
case 'ms': |
|
|
|
if (this.rotAt) { |
|
rotAt = this.rotAt + this.periodNum * periodOffset; |
|
} else { |
|
rotAt = Date.now() + this.periodNum * periodOffset; |
|
} |
|
break; |
|
case 'h': |
|
if (this.rotAt) { |
|
rotAt = this.rotAt + this.periodNum * 60 * 60 * 1000 * periodOffset; |
|
} else { |
|
|
|
rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
|
d.getUTCDate(), d.getUTCHours() + periodOffset); |
|
} |
|
break; |
|
case 'd': |
|
if (this.rotAt) { |
|
rotAt = this.rotAt + this.periodNum * 24 * 60 * 60 * 1000 |
|
* periodOffset; |
|
} else { |
|
|
|
rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
|
d.getUTCDate() + periodOffset); |
|
} |
|
break; |
|
case 'w': |
|
|
|
if (this.rotAt) { |
|
rotAt = this.rotAt + this.periodNum * 7 * 24 * 60 * 60 * 1000 |
|
* periodOffset; |
|
} else { |
|
|
|
var dayOffset = (7 - d.getUTCDay()); |
|
if (periodOffset < 1) { |
|
dayOffset = -d.getUTCDay(); |
|
} |
|
if (periodOffset > 1 || periodOffset < -1) { |
|
dayOffset += 7 * periodOffset; |
|
} |
|
rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
|
d.getUTCDate() + dayOffset); |
|
} |
|
break; |
|
case 'm': |
|
if (this.rotAt) { |
|
rotAt = Date.UTC(d.getUTCFullYear(), |
|
d.getUTCMonth() + this.periodNum * periodOffset, 1); |
|
} else { |
|
|
|
rotAt = Date.UTC(d.getUTCFullYear(), |
|
d.getUTCMonth() + periodOffset, 1); |
|
} |
|
break; |
|
case 'y': |
|
if (this.rotAt) { |
|
rotAt = Date.UTC(d.getUTCFullYear() + this.periodNum * periodOffset, |
|
0, 1); |
|
} else { |
|
|
|
rotAt = Date.UTC(d.getUTCFullYear() + periodOffset, 0, 1); |
|
} |
|
break; |
|
default: |
|
assert.fail(format('invalid period scope: "%s"', this.periodScope)); |
|
} |
|
|
|
if (this._debug()) { |
|
this._debug(' **rotAt**: %s (utc: %s)', rotAt, |
|
new Date(rotAt).toUTCString()); |
|
var now = Date.now(); |
|
this._debug(' now: %s (%sms == %smin == %sh to go)', |
|
now, |
|
rotAt - now, |
|
(rotAt-now)/1000/60, |
|
(rotAt-now)/1000/60/60); |
|
} |
|
return rotAt; |
|
}; |
|
|
|
RotatingFileStream.prototype.rotate = function rotate() { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
if (self.rotAt && self.rotAt > Date.now()) { |
|
return self._setRotationTimer(); |
|
} |
|
|
|
this._debug('rotate'); |
|
if (self.rotating) { |
|
throw new TypeError('cannot start a rotation when already rotating'); |
|
} |
|
self.rotating = true; |
|
|
|
self.stream.end(); |
|
|
|
function del() { |
|
var toDel = self.path + '.' + String(n - 1); |
|
if (n === 0) { |
|
toDel = self.path; |
|
} |
|
n -= 1; |
|
self._debug(' rm %s', toDel); |
|
fs.unlink(toDel, function (delErr) { |
|
|
|
moves(); |
|
}); |
|
} |
|
|
|
function moves() { |
|
if (self.count === 0 || n < 0) { |
|
return finish(); |
|
} |
|
var before = self.path; |
|
var after = self.path + '.' + String(n); |
|
if (n > 0) { |
|
before += '.' + String(n - 1); |
|
} |
|
n -= 1; |
|
fs.exists(before, function (exists) { |
|
if (!exists) { |
|
moves(); |
|
} else { |
|
self._debug(' mv %s %s', before, after); |
|
mv(before, after, function (mvErr) { |
|
if (mvErr) { |
|
self.emit('error', mvErr); |
|
finish(); |
|
} else { |
|
moves(); |
|
} |
|
}); |
|
} |
|
}) |
|
} |
|
|
|
function finish() { |
|
self._debug(' open %s', self.path); |
|
self.stream = fs.createWriteStream(self.path, |
|
{flags: 'a', encoding: 'utf8'}); |
|
var q = self.rotQueue, len = q.length; |
|
for (var i = 0; i < len; i++) { |
|
self.stream.write(q[i]); |
|
} |
|
self.rotQueue = []; |
|
self.rotating = false; |
|
self.emit('drain'); |
|
self._setupNextRot(); |
|
} |
|
|
|
var n = this.count; |
|
del(); |
|
}; |
|
|
|
RotatingFileStream.prototype.write = function write(s) { |
|
if (this.rotating) { |
|
this.rotQueue.push(s); |
|
return false; |
|
} else { |
|
return this.stream.write(s); |
|
} |
|
}; |
|
|
|
RotatingFileStream.prototype.end = function end(s) { |
|
this.stream.end(); |
|
}; |
|
|
|
RotatingFileStream.prototype.destroy = function destroy(s) { |
|
this.stream.destroy(); |
|
}; |
|
|
|
RotatingFileStream.prototype.destroySoon = function destroySoon(s) { |
|
this.stream.destroySoon(); |
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function RingBuffer(options) { |
|
this.limit = options && options.limit ? options.limit : 100; |
|
this.writable = true; |
|
this.records = []; |
|
EventEmitter.call(this); |
|
} |
|
|
|
util.inherits(RingBuffer, EventEmitter); |
|
|
|
RingBuffer.prototype.write = function (record) { |
|
if (!this.writable) |
|
throw (new Error('RingBuffer has been ended already')); |
|
|
|
this.records.push(record); |
|
|
|
if (this.records.length > this.limit) |
|
this.records.shift(); |
|
|
|
return (true); |
|
}; |
|
|
|
RingBuffer.prototype.end = function () { |
|
if (arguments.length > 0) |
|
this.write.apply(this, Array.prototype.slice.call(arguments)); |
|
this.writable = false; |
|
}; |
|
|
|
RingBuffer.prototype.destroy = function () { |
|
this.writable = false; |
|
this.emit('close'); |
|
}; |
|
|
|
RingBuffer.prototype.destroySoon = function () { |
|
this.destroy(); |
|
}; |
|
|
|
|
|
|
|
|
|
module.exports = Logger; |
|
|
|
module.exports.TRACE = TRACE; |
|
module.exports.DEBUG = DEBUG; |
|
module.exports.INFO = INFO; |
|
module.exports.WARN = WARN; |
|
module.exports.ERROR = ERROR; |
|
module.exports.FATAL = FATAL; |
|
module.exports.resolveLevel = resolveLevel; |
|
module.exports.levelFromName = levelFromName; |
|
module.exports.nameFromLevel = nameFromLevel; |
|
|
|
module.exports.VERSION = VERSION; |
|
module.exports.LOG_VERSION = LOG_VERSION; |
|
|
|
module.exports.createLogger = function createLogger(options) { |
|
return new Logger(options); |
|
}; |
|
|
|
module.exports.RingBuffer = RingBuffer; |
|
module.exports.RotatingFileStream = RotatingFileStream; |
|
|
|
|
|
|
|
|
|
module.exports.safeCycles = safeCycles; |
|
|