add tags to logging, use classes

This commit is contained in:
Spencer Alger 2015-07-07 14:40:58 -07:00
parent 522a00ec5d
commit c95e315f46
16 changed files with 348 additions and 334 deletions

View file

@ -58,10 +58,8 @@
"file-loader": "^0.8.4",
"font-awesome": "^4.3.0",
"glob": "^4.3.2",
"good": "^5.1.2",
"good-console": "^4.1.0",
"good-file": "^4.0.2",
"good-reporter": "^3.1.0",
"good": "^6.2.0",
"good-squeeze": "^2.1.0",
"hapi": "^8.6.1",
"http-auth": "^2.2.5",
"imports-loader": "^0.6.4",

View file

@ -7,8 +7,6 @@ function Config(schema, defaults) {
this.schema = schema || Joi.object({}).default();
this.config = {};
this.set(defaults);
console.log(this.get('env'));
}
Config.prototype.extendSchema = function (key, schema) {

View file

@ -4,22 +4,6 @@ var path = require('path');
var package = require('../utils/closestPackageJson').getSync();
var fromRoot = require('../utils/fromRoot');
var env = (function () {
switch (process.env.NODE_ENV) {
case 'production':
case 'prod':
case undefined:
return 'production';
case 'development':
case 'dev':
return 'development';
default:
throw new TypeError(`Unexpected NODE_ENV "${process.env.NODE_ENV}", expected production or development.`);
}
}());
var dev = env === 'development';
var prod = env === 'production';
module.exports = Joi.object({
env: Joi.object({
name: Joi.string().default(Joi.ref('$env')),
@ -67,14 +51,24 @@ module.exports = Joi.object({
logging: Joi.object({
quiet: Joi.boolean().default(false),
file: Joi.string(),
console: Joi.object({
ops: Joi.any(),
log: Joi.any().default('*'),
response: Joi.any().default('*'),
error: Joi.any().default('*'),
json: Joi.boolean().default(false),
}).default()
// not nested under a kbnLogger key so that we can ref "quiet"
kbnLogger: Joi.boolean().default(true),
kbnLoggerConfig: Joi.object({
dest: Joi.string().default('stdout'),
json: Joi.boolean().default(Joi.ref('$prod'))
}).default(),
kbnLoggerEvents: Joi.when('quiet', {
is: true,
then: Joi.object({
error: Joi.string().default('*')
}).default(),
otherwise: Joi.object({
log: Joi.string().default('*'),
response: Joi.string().default('*'),
error: Joi.string().default('*')
}).default()
})
}).default(),
plugins: Joi.object({

View file

@ -0,0 +1,133 @@
'use strict';
let Stream = require('stream');
let moment = require('moment');
let _ = require('lodash');
let numeral = require('numeral');
let ansicolors = require('ansicolors');
let stringify = require('json-stringify-safe');
let querystring = require('querystring');
let inspect = require('util').inspect;
function serializeError(err) {
return {
message: err.message,
name: err.name,
stack: err.stack,
code: err.code,
signal: err.signal
};
}
let levelColor = function (code) {
if (code < 299) return ansicolors.green(code);
if (code < 399) return ansicolors.yellow(code);
if (code < 499) return ansicolors.magenta(code);
return ansicolors.red(code);
};
module.exports = class TransformObjStream extends Stream.Transform {
constructor() {
super({
readableObjectMode: false,
writableObjectMode: true
});
}
_transform(event, enc, next) {
var data = this.readEvent(event);
this.push(this.format(data) + '\n');
next();
}
readEvent(event) {
var data = {
type: event.event,
'@timestamp': moment.utc(event.timestamp).format(),
tags: [].concat(event.tags || []),
pid: event.pid
};
if (data.type === 'response') {
_.defaults(data, _.pick(event, [
'method',
'statusCode'
]));
data.req = {
url: event.path,
method: event.method,
headers: event.headers,
remoteAddress: event.source.remoteAddress,
userAgent: event.source.remoteAddress,
referer: event.source.referer
};
var contentLength = 0;
if (typeof event.responsePayload === 'object') {
contentLength = stringify(event.responsePayload).length;
} else {
contentLength = event.responsePayload.toString().length;
}
data.res = {
statusCode: event.statusCode,
responseTime: event.responseTime,
contentLength: contentLength
};
var query = querystring.stringify(event.query);
if (query) data.req.url += '?' + query;
data.message = data.req.method.toUpperCase() + ' ';
data.message += data.req.url;
data.message += ' ';
data.message += levelColor(data.res.statusCode);
data.message += ' ';
data.message += ansicolors.brightBlack(data.res.responseTime + 'ms');
data.message += ansicolors.brightBlack(' - ' + numeral(contentLength).format('0.0b'));
}
else if (data.type === 'ops') {
_.defaults(data, _.pick(event, [
'pid',
'os',
'proc',
'load'
]));
data.message = ansicolors.brightBlack('memory: ');
data.message += numeral(data.proc.mem.heapUsed).format('0.0b');
data.message += ' ';
data.message += ansicolors.brightBlack('uptime: ');
data.message += numeral(data.proc.uptime).format('00:00:00');
data.message += ' ';
data.message += ansicolors.brightBlack('load: [');
data.message += data.os.load.map(function (val) {
return numeral(val).format('0.00');
}).join(' ');
data.message += ansicolors.brightBlack(']');
data.message += ' ';
data.message += ansicolors.brightBlack('delay: ');
data.message += numeral(data.proc.delay).format('0.000');
}
else if (data.type === 'error') {
data.level = 'error';
data.message = event.error.message;
data.error = serializeError(event.error);
data.url = event.url;
}
else if (event.data instanceof Error) {
data.level = _.contains(event.tags, 'fatal') ? 'fatal' : 'error';
data.message = event.data.message;
data.error = serializeError(event.data);
}
else if (_.isPlainObject(event.data) && event.data.message) {
_.assign(data, event.data);
data.message = _.template(event.data.message)(event.data);
}
else {
data.message = _.isString(event.data) ? event.data : inspect(event.data);
}
return data;
}
};

View file

@ -0,0 +1,15 @@
'use strict';
let LogFormat = require('./LogFormat');
let stringify = require('json-stringify-safe');
let stripColors = function (string) {
return string.replace(/\u001b[^m]+m/g, '');
};
module.exports = class KbnLoggerJsonFormat extends LogFormat {
format(data) {
data.message = stripColors(data.message);
return stringify(data);
}
};

View file

@ -0,0 +1,36 @@
'use strict';
let _ = require('lodash');
let ansicolors = require('ansicolors');
let moment = require('moment');
let LogFormat = require('./LogFormat');
let typeColors = {
log: 'blue',
req: 'green',
res: 'green',
ops: 'cyan',
err: 'red',
info: 'blue',
error: 'red',
fatal: 'magenta'
};
let color = _.memoize(function (name) {
return ansicolors[typeColors[name]] || _.identity;
});
module.exports = class KbnLoggerJsonFormat extends LogFormat {
format(data) {
let type = color(data.type)(_.padLeft(data.type, 6));
let time = color('time')(moment(data.timestamp).format());
let msg = data.error ? color('error')(data.error.stack) : color('message')(data.message);
let tags = data.tags.reduce(function (s, t) {
return s + `[${ color(t)(t) }]`;
}, '');
return `${type}: [ ${time} ] ${tags} ${msg}`;
}
};

View file

@ -0,0 +1,35 @@
'use strict';
let _ = require('lodash');
let Squeeze = require('good-squeeze').Squeeze;
let writeStr = require('fs').createWriteStream;
let LogFormatJson = require('./LogFormatJson');
let LogFormatString = require('./LogFormatString');
module.exports = class KbnLogger {
constructor(events, config) {
this.squeeze = new Squeeze(events);
this.format = config.json ? new LogFormatJson() : new LogFormatString();
if (config.dest === 'stdout') {
this.dest = process.stdout;
} else {
this.dest = writeStr(config.dest, {
mode: 'a',
encoding: 'utf8'
});
}
}
init(readstream, emitter, callback) {
readstream
.pipe(this.squeeze)
.pipe(this.format)
.pipe(this.dest);
emitter.on('stop', _.noop);
callback();
}
};

View file

@ -1,133 +0,0 @@
var moment = require('moment');
var _ = require('lodash');
var env = process.env.NODE_ENV || 'development';
var numeral = require('numeral');
var ansicolors = require('ansicolors');
var stringify = require('json-stringify-safe');
var querystring = require('querystring');
var format = require('util').format;
var inspect = require('util').inspect;
function serializeError(err) {
return {
message: err.message,
name: err.name,
stack: err.stack,
code: err.code,
signal: err.signal
};
}
var levelColor = function (code) {
if (code < 299) {
return ansicolors.green(code);
}
if (code < 399) {
return ansicolors.yellow(code);
}
if (code < 499) {
return ansicolors.magenta(code);
}
return ansicolors.red(code);
};
function lookup(name) {
switch (name) {
case 'error':
return 'error';
default:
return 'info';
}
}
module.exports = function (name, event) {
var data = {
'@timestamp': moment.utc(event.timestamp).format(),
level: lookup(event),
node_env: env,
tags: event.tags,
pid: event.pid
};
if (name === 'response') {
_.defaults(data, _.pick(event, [
'method',
'statusCode'
]));
data.req = {
url: event.path,
method: event.method,
headers: event.headers,
remoteAddress: event.source.remoteAddress,
userAgent: event.source.remoteAddress,
referer: event.source.referer
};
var contentLength = 0;
if (typeof event.responsePayload === 'object') {
contentLength = stringify(event.responsePayload).length;
} else {
contentLength = event.responsePayload.toString().length;
}
data.res = {
statusCode: event.statusCode,
responseTime: event.responseTime,
contentLength: contentLength
};
var query = querystring.stringify(event.query);
if (query) data.req.url += '?' + query;
data.message = data.req.method.toUpperCase() + ' ';
data.message += data.req.url;
data.message += ' ';
data.message += levelColor(data.res.statusCode);
data.message += ' ';
data.message += ansicolors.brightBlack(data.res.responseTime + 'ms');
data.message += ansicolors.brightBlack(' - ' + numeral(contentLength).format('0.0b'));
}
else if (name === 'ops') {
_.defaults(data, _.pick(event, [
'pid',
'os',
'proc',
'load'
]));
data.message = ansicolors.brightBlack('memory: ');
data.message += numeral(data.proc.mem.heapUsed).format('0.0b');
data.message += ' ';
data.message += ansicolors.brightBlack('uptime: ');
data.message += numeral(data.proc.uptime).format('00:00:00');
data.message += ' ';
data.message += ansicolors.brightBlack('load: [');
data.message += data.os.load.map(function (val) {
return numeral(val).format('0.00');
}).join(' ');
data.message += ansicolors.brightBlack(']');
data.message += ' ';
data.message += ansicolors.brightBlack('delay: ');
data.message += numeral(data.proc.delay).format('0.000');
}
else if (name === 'error') {
data.level = 'error';
data.message = event.error.message;
data.error = serializeError(event.error);
data.url = event.url;
}
else if (event.data instanceof Error) {
data.level = _.contains(event.tags, 'fatal') ? 'fatal' : 'error';
data.message = event.data.message;
data.error = serializeError(event.data);
}
else if (_.isPlainObject(event.data) && event.data.message) {
_.assign(data, event.data);
data.message = _.template(event.data.message)(event.data);
}
else {
data.message = _.isString(event.data) ? event.data : inspect(event.data);
}
return data;
};

View file

@ -1,58 +0,0 @@
var ansicolors = require('ansicolors');
var eventToJson = require('./_event_to_json');
var GoodReporter = require('good-reporter');
var util = require('util');
var moment = require('moment');
var stringify = require('json-stringify-safe');
var querystring = require('querystring');
var numeral = require('numeral');
var colors = {
log: 'blue',
req: 'green',
res: 'green',
ops: 'cyan',
err: 'red',
info: 'blue',
error: 'red',
fatal: 'magenta'
};
function stripColors(string) {
return string.replace(/\u001b[^m]+m/g, '');
}
var Console = module.exports = function (events, options) {
this._json = options.json;
GoodReporter.call(this, events);
};
util.inherits(Console, GoodReporter);
Console.prototype.stop = function () { };
Console.prototype._report = function (name, data) {
data = eventToJson(name, data);
var nameCrayon = ansicolors[colors[name.substr(0, 3)]];
var typeCrayon = ansicolors[colors[data.level]];
var output;
if (this._json) {
data.message = stripColors(data.message);
output = stringify(data);
} else {
output = nameCrayon(name.substr(0, 3));
output += ': ';
output += typeCrayon(data.level.toUpperCase());
output += ' ';
output += '[ ';
output += ansicolors.brightBlack(moment(data.timestamp).format());
output += ' ] ';
if (data.error) {
output += ansicolors.red(data.error.stack);
} else {
output += data.message;
}
}
console.log(output);
};

View file

@ -1,37 +1,21 @@
var Promise = require('bluebird');
var good = require('good');
var path = require('path');
var join = path.join;
var Console = require('./good_reporters/console');
var fromNode = require('bluebird').fromNode;
module.exports = function (kbnServer, server, config) {
if (!config.get('logging.kbnLogger')) return;
module.exports = function (kbnServer) {
var server = kbnServer.server;
return new Promise(function (resolve, reject) {
var reporters = [];
var config = server.config();
// If we are not quite then add the console logger
var filters = {};
if (!config.get('logging.quiet')) {
if (config.get('logging.console.ops') != null) filters.ops = config.get('logging.console.ops');
if (config.get('logging.console.log') != null) filters.log = config.get('logging.console.log');
if (config.get('logging.console.response') != null) filters.response = config.get('logging.console.response');
if (config.get('logging.console.error') != null) filters.error = config.get('logging.console.error');
}
reporters.push({ reporter: Console, args: [filters, { json: config.get('logging.console.json') } ] });
return fromNode(function (cb) {
server.register({
register: good,
register: require('good'),
options: {
opsInterval: 5000,
logRequestHeaders: true,
logResponsePayload: true,
reporters: reporters
reporters: [
{
reporter: require('./LogReporter'),
config: config.get('logging.kbnLoggerConfig'),
events: config.get('logging.kbnLoggerEvents'),
}
]
}
}, function (err) {
if (err) return reject(err);
resolve(server);
});
}, cb);
});
};

View file

@ -19,8 +19,8 @@ function OptmzUiExports(plugins) {
// webpack loaders map loader configuration to regexps
var loaders = this.loaders = [
{ test: /\.less$/, loader: 'style!css!less' },
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.less$/, loader: 'style/url!file!less' },
{ test: /\.css$/, loader: 'style/url!file' },
{ test: /\.html$/, loader: 'raw' },
{
test: /\.(woff|woff2|png)(\?v=[0-9]\.[0-9]\.[0-9])?$/,

View file

@ -32,7 +32,7 @@ module.exports = function (kbnServer, server, config) {
status.green('Optimization complete');
})
.on('error', function (err) {
server.log(['fatal'], err);
server.log('fatal', err);
status.red('Optimization failure! ' + err.message);
})
.init();

View file

@ -1,86 +1,96 @@
var _ = require('lodash');
var inherits = require('util').inherits;
var Joi = require('joi');
var Promise = require('bluebird');
var join = require('path').join;
'use strict';
function Plugin(kbnServer, path, package, opts) {
this.kbnServer = kbnServer;
this.package = package;
this.path = path;
let _ = require('lodash');
let inherits = require('util').inherits;
let Joi = require('joi');
let Promise = require('bluebird');
let join = require('path').join;
this.id = opts.id || package.name;
this.uiExportSpecs = opts.uiExports || {};
this.requiredIds = opts.require || [];
this.version = opts.version || package.version;
this.publicDir = _.get(opts, 'publicDir', join(path, 'public'));
this.externalInit = opts.init || _.noop;
this.getConfig = opts.config || _.noop;
this.init = _.once(this.init);
}
module.exports = class Plugin {
constructor(kbnServer, path, pkg, opts) {
this.kbnServer = kbnServer;
this.pkg = pkg;
this.path = path;
Plugin.scoped = function (kbnServer, path, package) {
function ScopedPlugin(opts) {
ScopedPlugin.super_.call(this, kbnServer, path, package, opts || {});
this.id = opts.id || pkg.name;
this.uiExportSpecs = opts.uiExports || {};
this.requiredIds = opts.require || [];
this.version = opts.version || pkg.version;
this.publicDir = _.get(opts, 'publicDir', join(path, 'public'));
this.externalInit = opts.init || _.noop;
this.getConfig = opts.config || _.noop;
this.init = _.once(this.init);
}
inherits(ScopedPlugin, Plugin);
return ScopedPlugin;
};
Plugin.prototype.init = function () {
var self = this;
static scoped(kbnServer, path, pkg) {
return class ScopedPlugin extends Plugin {
constructor(opts) {
super(kbnServer, path, pkg, opts || {});
}
};
}
var id = self.id;
var version = self.version;
var server = self.kbnServer.server;
var status = self.kbnServer.status;
init() {
let self = this;
var config = server.config();
server.log(['plugin', 'init', 'debug'], {
message: 'initializing plugin <%= plugin.id %>',
plugin: self
});
let id = self.id;
let version = self.version;
let server = self.kbnServer.server;
let status = self.kbnServer.status;
return Promise.try(function () {
return self.getConfig(Joi);
})
.then(function (schema) {
if (schema) config.extendSchema(id, schema);
})
.then(function () {
return status.decoratePlugin(self);
})
.then(function () {
return self.kbnServer.uiExports.consumePlugin(self);
})
.then(function () {
let basetags = ['plugin', id];
let log = function (tags, data, timestamp) {
var register = function (server, options, next) {
Promise.try(self.externalInit, [server, options], self).nodeify(next);
};
register.attributes = { name: id, version: version };
return Promise.fromNode(function (cb) {
server.register({
register: register,
options: config.has(id) ? config.get(id) : null
}, cb);
let config = server.config();
server.log(['plugin', 'debug', 'init'], {
message: 'Initializing plugin',
plugin: self
});
})
.then(function () {
// Only change the plugin status to green if the
// intial status has not been updated
if (self.status.state === 'uninitialized') {
self.status.green('Ready');
}
});
};
return Promise.try(function () {
return self.getConfig(Joi);
})
.then(function (schema) {
if (schema) config.extendSchema(id, schema);
})
.then(function () {
return status.decoratePlugin(self);
})
.then(function () {
return self.kbnServer.uiExports.consumePlugin(self);
})
.then(function () {
Plugin.prototype.toString = function () {
return `${this.id}@${this.version}`;
};
let register = function (server, options, next) {
Promise.try(self.externalInit, [server, options], self).nodeify(next);
};
module.exports = Plugin;
register.attributes = { name: id, version: version };
return Promise.fromNode(function (cb) {
server.register({
register: register,
options: config.has(id) ? config.get(id) : null
}, cb);
});
})
.then(function () {
// Only change the plugin status to green if the
// intial status has not been updated
if (self.status.state === 'uninitialized') {
self.status.green('Ready');
}
});
}
toJSON() {
return this.pkg;
}
toString() {
return `${this.id}@${this.version}`;
}
};

View file

@ -1,12 +1,14 @@
'use strict';
var Plugin = require('./Plugin');
var basename = require('path').basename;
var join = require('path').join;
function PluginApi(kibana, pluginPath) {
this.rootDir = kibana.rootDir;
this.package = require(join(pluginPath, 'package.json'));
this.autoload = require('../ui/autoload');
this.Plugin = Plugin.scoped(kibana, pluginPath, this.package);
}
module.exports = PluginApi;
module.exports = class PluginApi {
constructor(kibana, pluginPath) {
this.rootDir = kibana.rootDir;
this.package = require(join(pluginPath, 'package.json'));
this.autoload = require('../ui/autoload');
this.Plugin = Plugin.scoped(kibana, pluginPath, this.package);
}
};

View file

@ -10,8 +10,8 @@ function Status(name, server) {
this.message = 'uninitialized';
this.on('change', function (current, previous) {
server.log(['status'], {
message: '[ <%= name %> ] Change status from <%= prev %> to <%= cur %> - <%= curMsg %>',
server.log(['plugin', name, 'status'], {
message: 'Change status from <%= prev %> to <%= cur %> - <%= curMsg %>',
name: name,
prev: previous.state,
cur: current.state,