diff --git a/.versions b/.versions index 3bf0270..c0df249 100644 --- a/.versions +++ b/.versions @@ -1,18 +1,49 @@ -babel-compiler@6.6.4 -babel-runtime@0.1.8 -base64@1.0.8 -caching-compiler@1.0.4 -check@1.2.1 -coffeescript@1.0.17 -ecmascript@0.4.3 -ecmascript-runtime@0.2.10 -ejson@1.0.11 -meteor@1.1.14 -modules@0.6.1 -modules-runtime@0.6.3 -ostrio:logger@1.1.2 -promise@0.6.7 -random@1.0.9 -reactive-var@1.0.9 -tracker@1.0.13 -underscore@1.0.8 +allow-deny@1.0.5 +babel-compiler@6.18.2 +babel-runtime@1.0.1 +base64@1.0.10 +binary-heap@1.0.10 +blaze@2.1.9 +blaze-tools@1.0.10 +boilerplate-generator@1.0.11 +callback-hook@1.0.10 +check@1.2.5 +ddp@1.2.5 +ddp-client@1.3.4 +ddp-common@1.2.8 +ddp-server@1.3.14 +deps@1.0.12 +diff-sequence@1.0.7 +ecmascript@0.7.3 +ecmascript-runtime@0.3.15 +ejson@1.0.13 +geojson-utils@1.0.10 +html-tools@1.0.11 +htmljs@1.0.11 +id-map@1.0.9 +jquery@1.11.10 +local-test:ostrio:logger@2.0.0 +logging@1.1.17 +meteor@1.6.1 +minimongo@1.0.23 +modules@0.8.2 +modules-runtime@0.7.10 +mongo@1.1.17 +mongo-id@1.0.6 +npm-mongo@2.2.24 +observe-sequence@1.0.16 +ordered-dict@1.0.9 +ostrio:logger@2.0.0 +promise@0.8.8 +random@1.0.10 +reactive-var@1.0.11 +retry@1.0.9 +routepolicy@1.0.12 +spacebars@1.0.13 +spacebars-compiler@1.0.13 +tinytest@1.0.12 +tracker@1.1.3 +ui@1.0.12 +underscore@1.0.10 +webapp@1.3.15 +webapp-hashing@1.0.9 diff --git a/logger.js b/logger.js new file mode 100755 index 0000000..81a2748 --- /dev/null +++ b/logger.js @@ -0,0 +1,273 @@ +import { _ } from 'meteor/underscore'; +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { check, Match } from 'meteor/check'; + +let _inst = 0; + +/* + * @class Logger + * @summary Extend-able Logger class + */ +class Logger { + constructor() { + this.userId = new ReactiveVar(null); + this.prefix = ++_inst; + + if (Meteor.isClient) { + if (Package && Package['accounts-base']) { + Accounts.onLogin(() => { + this.userId.set(Meteor.userId()); + }); + + Accounts.onLogout(() => { + this.userId.set(null); + }); + } + } + + this._emitters = []; + this._rules = {}; + } + + /* + * @memberOf Logger + * @name _log + * @param level {String} - Log level Accepts 'ERROR', 'FATAL', 'WARN', 'DEBUG', 'INFO', 'TRACE', 'LOG' and '*' + * @param message {String} - Text human-readable message + * @param data {Object} - [optional] Any additional info as object + * @param userId {String} - [optional] Current user id + * @summary Pass log's data to Server or/and Client + */ + _log(level, message, data = {}, user) { + const uid = user || this.userId.get(); + + for (let i in this._emitters) { + if (this._rules[this._emitters[i].name] && this._rules[this._emitters[i].name].enable === true) { + if (Meteor.isServer && this._rules[this._emitters[i].name].server === false || Meteor.isClient && this._rules[this._emitters[i].name].client === false) { + continue; + } + + if (this._rules[this._emitters[i].name].allow.indexOf('*') !== -1 || this._rules[this._emitters[i].name].allow.indexOf(level) !== -1) { + if (level === 'TRACE') { + if (_.isString(data)) { + let _data = _.clone(data); + data = {data: _data}; + } + data.stackTrace = this._getStackTrace(); + } + + if (Meteor.isClient && this._emitters[i].denyClient === true && this._emitters[i].denyServer === false) { + Meteor.call(this._emitters[i].method, level, message, data, uid); + } else if (this._rules[this._emitters[i].name].client === true && this._rules[this._emitters[i].name].server === true && this._emitters[i].denyClient === false && this._emitters[i].denyServer === false) { + this._emitters[i].emitter(level, message, data, uid); + if (Meteor.isClient) { + Meteor.call(this._emitters[i].method, level, message, data, uid); + } + } else if (Meteor.isClient && this._rules[this._emitters[i].name].client === false && this._rules[this._emitters[i].name].server === true) { + Meteor.call(this._emitters[i].method, level, message, data, uid); + } else { + this._emitters[i].emitter(level, message, data, uid); + } + } + } + } + + return new LoggerMessage({ + level: level, + error: level, + reason: message, + errorType: level, + message: message, + details: data, + data: data, + user: uid, + userId: uid + }); + } + + /* + * @memberOf Logger + * @name rule + * @param name {String} - Adapter name + * @param options {Object} - Settings object + options.enable {Boolean} - Enable/disable adapter + options.filter {Array} - Array of strings, accepts: + 'ERROR', 'FATAL', 'WARN', 'DEBUG', 'INFO', 'TRACE', 'LOG' and '*' + in lowercase or uppercase + default: ['*'] - Accept all + options.client {Boolean} - Allow execution on Client + options.server {Boolean} - Allow execution on Server + * @summary Enable/disable adapter and set it's settings + */ + rule(name, options) { + check(name, String); + check(options, { + enable: Boolean, + client: Match.Optional(Boolean), + server: Match.Optional(Boolean), + filter: Match.Optional([String]) + }); + + if (options.filter) { + for (let j = 0; j < options.filter.length; j++) { + options.filter[j] = options.filter[j].toUpperCase(); + } + } + + if (!options.filter) { + options.filter = ['*']; + } + + if (!options.client) { + options.client = false; + } + + if (!options.server) { + options.server = true; + } + + if (!options.enable) { + options.enable = true; + } + + this._rules[name] = { + allow: options.filter, + enable: options.enable, + client: options.client, + server: options.server + }; + } + + /* + * @memberOf Logger + * @name add + * @param name {String} - Adapter name + * @param emitter {Function} - Function called on Meteor.log... + * @param init {Function} - Adapter initialization function + * @param denyClient {Boolean} - Strictly deny execution on client, only pass via Meteor.methods + * @param denyServer {Boolean} - Strictly deny execution on server, only client + * @summary Register new adapter to be used within ostrio:logger package + */ + add(name, emitter, init, denyClient = false, denyServer = false) { + if (Meteor.isServer && denyServer || Meteor.isClient && denyClient) { + return; + } + + if (init && _.isFunction(init)) { + init(); + } + + this._emitters.push({ + name: name, + emitter: emitter, + method: `${this.prefix}_logger_emit_${name}`, + denyClient: denyClient + }); + + if (Meteor.isServer) { + const method = {}; + method[`${this.prefix}_logger_emit_${name}`] = (level, message, data, userId) => { + check(level, String); + check(message, Match.Optional(Match.OneOf(Number, String, null))); + check(data, Match.Optional(Match.OneOf(String, Object, null))); + check(userId, Match.Optional(Match.OneOf(String, Number, null))); + emitter(level, message, data, (userId || this.userId)); + }; + Meteor.methods(method); + } + } + + /* + * @memberOf Logger + * @name info; debug; error; fatal; warn; trace; log; _ + * @param message {String} - Any text message + * @param data {Object} - [optional] Any additional info as object + * @param userId {String} - [optional] Current user id + * @summary Methods below is shortcuts for _log() method + */ + info(message, data, userId) { + return this._log('INFO', message, data, userId); + } + debug(message, data, userId) { + return this._log('DEBUG', message, data, userId); + } + error(message, data, userId) { + return this._log('ERROR', message, data, userId); + } + fatal(message, data, userId) { + return this._log('FATAL', message, data, userId); + } + warn(message, data, userId) { + return this._log('WARN', message, data, userId); + } + trace(message, data, userId) { + return this._log('TRACE', message, data, userId); + } + log(message, data, userId) { + return this._log('LOG', message, data, userId); + } + _(message, data, userId) { + return this._log('LOG', message, data, userId); + } + + /* + * @memberOf Logger + * @name antiCircular + * @param data {Object} - Circular or any other object which needs to be non-circular + */ + antiCircular(obj) { + const _cache = []; + const _wrap = (o) => { + for (let v in o) { + if (v !== null && typeof v === 'object') { + if (_cache.indexOf(v) !== -1) { + o[o[v]] = '[Circular]'; + break; + } + _cache.push(v); + _wrap(v); + break; + } + } + }; + + _wrap(obj); + return obj; + } + + /* + * @memberOf Logger + * @name _getStackTrace + * @summary Prepare stack trace message + */ + _getStackTrace() { + let obj = {}; + Error.captureStackTrace(obj, this._getStackTrace); + return obj.stack; + } +} + +/* + * @class LoggerMessage + * @param data {Object} + * @summary Construct message object, ready to be thrown and stringified + */ +class LoggerMessage { + constructor(data) { + this.data = data.data; + this.user = data.user; + this.level = data.level; + this.error = data.error; + this.userId = data.userId; + this.reason = data.reason; + this.details = data.details; + this.message = data.message; + + this.toString = () => { + return `[${this.reason}] \r\nLevel: ${this.level}; \r\nDetails: ${JSON.stringify(Logger.prototype.antiCircular(this.data))}; \r\nUserId: ${this.userId};`; + }; + } +} + +export { Logger, LoggerMessage }; diff --git a/package.js b/package.js index 4a47be8..5b9bd1f 100755 --- a/package.js +++ b/package.js @@ -1,15 +1,19 @@ Package.describe({ name: 'ostrio:logger', - version: '1.1.2', + version: '2.0.0', summary: 'Logging: isomorphic driver with support of MongoDB, File (FS) and Console', git: 'https://github.com/VeliovGroup/Meteor-logger', documentation: 'README.md' }); Package.onUse(function(api) { - api.versionsFrom('1.0'); - api.use(['coffeescript', 'reactive-var', 'check', 'underscore'], ['client', 'server']); - api.use('tracker', 'client'); - api.addFiles('logger.coffee', ['client', 'server']); - api.export('Logger'); + api.versionsFrom('1.4'); + api.use(['ecmascript', 'reactive-var', 'check', 'underscore'], ['client', 'server']); + api.mainModule('logger.js', ['client', 'server']); +}); + +Package.onTest(function(api) { + api.use('tinytest'); + api.use(['ecmascript', 'underscore', 'ostrio:logger']); + api.addFiles('logger-tests.js'); });