diff --git a/packages/rocketchat-logger/client/viewLogs.coffee b/packages/rocketchat-logger/client/viewLogs.coffee
deleted file mode 100644
index b325b4502def..000000000000
--- a/packages/rocketchat-logger/client/viewLogs.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-@stdout = new Mongo.Collection 'stdout'
-
-Meteor.startup ->
- RocketChat.AdminBox.addOption
- href: 'admin-view-logs'
- i18nLabel: 'View_Logs'
- permissionGranted: ->
- return RocketChat.authz.hasAllPermission('view-logs')
-
-FlowRouter.route '/admin/view-logs',
- name: 'admin-view-logs'
- action: (params) ->
- BlazeLayout.render 'main',
- center: 'pageSettingsContainer'
- pageTitle: t('View_Logs')
- pageTemplate: 'viewLogs'
- noScroll: true
diff --git a/packages/rocketchat-logger/client/viewLogs.js b/packages/rocketchat-logger/client/viewLogs.js
new file mode 100644
index 000000000000..680ff3813fd8
--- /dev/null
+++ b/packages/rocketchat-logger/client/viewLogs.js
@@ -0,0 +1,24 @@
+
+this.stdout = new Mongo.Collection('stdout');
+
+Meteor.startup(function() {
+ return RocketChat.AdminBox.addOption({
+ href: 'admin-view-logs',
+ i18nLabel: 'View_Logs',
+ permissionGranted() {
+ return RocketChat.authz.hasAllPermission('view-logs');
+ }
+ });
+});
+
+FlowRouter.route('/admin/view-logs', {
+ name: 'admin-view-logs',
+ action() {
+ return BlazeLayout.render('main', {
+ center: 'pageSettingsContainer',
+ pageTitle: t('View_Logs'),
+ pageTemplate: 'viewLogs',
+ noScroll: true
+ });
+ }
+});
diff --git a/packages/rocketchat-logger/client/views/viewLogs.coffee b/packages/rocketchat-logger/client/views/viewLogs.coffee
deleted file mode 100644
index 29a96bf9e15a..000000000000
--- a/packages/rocketchat-logger/client/views/viewLogs.coffee
+++ /dev/null
@@ -1,106 +0,0 @@
-import moment from 'moment'
-
-Template.viewLogs.onCreated ->
- @subscribe 'stdout'
- @atBottom = true
-
-
-Template.viewLogs.helpers
- hasPermission: ->
- return RocketChat.authz.hasAllPermission 'view-logs'
-
- logs: ->
- return stdout.find({}, {sort: {ts: 1}})
-
- ansispan: (string) ->
- string = ansispan(string.replace(/\s/g, ' ').replace(/(\\n|\n)/g, '
'))
- string = string.replace(/(.\d{8}-\d\d:\d\d:\d\d\.\d\d\d\(?.{0,2}\)?)/, '$1')
- return string
-
- formatTS: (date) ->
- return moment(date).format('YMMDD-HH:mm:ss.SSS(ZZ)')
-
-
-Template.viewLogs.events
- 'click .new-logs': (e) ->
- Template.instance().atBottom = true
- Template.instance().sendToBottomIfNecessary()
-
-
-Template.viewLogs.onRendered ->
- wrapper = this.find('.terminal')
- wrapperUl = this.find('.terminal')
- newLogs = this.find('.new-logs')
-
- template = this
-
- template.isAtBottom = (scrollThreshold) ->
- if not scrollThreshold? then scrollThreshold = 0
- if wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight
- newLogs.className = "new-logs not"
- return true
- return false
-
- template.sendToBottom = ->
- wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
- newLogs.className = "new-logs not"
-
- template.checkIfScrollIsAtBottom = ->
- template.atBottom = template.isAtBottom(100)
- readMessage.enable()
- readMessage.read()
-
- template.sendToBottomIfNecessary = ->
- if template.atBottom is true and template.isAtBottom() isnt true
- template.sendToBottom()
- else if template.atBottom is false
- newLogs.className = "new-logs"
-
- template.sendToBottomIfNecessaryDebounced = _.debounce template.sendToBottomIfNecessary, 10
-
- template.sendToBottomIfNecessary()
-
- if not window.MutationObserver?
- wrapperUl.addEventListener 'DOMSubtreeModified', ->
- template.sendToBottomIfNecessaryDebounced()
- else
- observer = new MutationObserver (mutations) ->
- mutations.forEach (mutation) ->
- template.sendToBottomIfNecessaryDebounced()
-
- observer.observe wrapperUl,
- childList: true
-
- template.onWindowResize = ->
- Meteor.defer ->
- template.sendToBottomIfNecessaryDebounced()
-
- window.addEventListener 'resize', template.onWindowResize
-
- wrapper.addEventListener 'mousewheel', ->
- template.atBottom = false
- Meteor.defer ->
- template.checkIfScrollIsAtBottom()
-
- wrapper.addEventListener 'wheel', ->
- template.atBottom = false
- Meteor.defer ->
- template.checkIfScrollIsAtBottom()
-
- wrapper.addEventListener 'touchstart', ->
- template.atBottom = false
-
- wrapper.addEventListener 'touchend', ->
- Meteor.defer ->
- template.checkIfScrollIsAtBottom()
- Meteor.setTimeout ->
- template.checkIfScrollIsAtBottom()
- , 1000
- Meteor.setTimeout ->
- template.checkIfScrollIsAtBottom()
- , 2000
-
- wrapper.addEventListener 'scroll', ->
- template.atBottom = false
- Meteor.defer ->
- template.checkIfScrollIsAtBottom()
diff --git a/packages/rocketchat-logger/client/views/viewLogs.js b/packages/rocketchat-logger/client/views/viewLogs.js
new file mode 100644
index 000000000000..d6664b34af75
--- /dev/null
+++ b/packages/rocketchat-logger/client/views/viewLogs.js
@@ -0,0 +1,124 @@
+import moment from 'moment';
+// TODO: remove this globals
+/* globals ansispan stdout readMessage*/
+
+Template.viewLogs.onCreated(function() {
+ this.subscribe('stdout');
+ return this.atBottom = true;
+});
+
+Template.viewLogs.helpers({
+ hasPermission() {
+ return RocketChat.authz.hasAllPermission('view-logs');
+ },
+ logs() {
+ return stdout.find({}, {
+ sort: {
+ ts: 1
+ }
+ });
+ },
+ ansispan(string) {
+ string = ansispan(string.replace(/\s/g, ' ').replace(/(\\n|\n)/g, '
'));
+ string = string.replace(/(.\d{8}-\d\d:\d\d:\d\d\.\d\d\d\(?.{0,2}\)?)/, '$1');
+ return string;
+ },
+ formatTS(date) {
+ return moment(date).format('YMMDD-HH:mm:ss.SSS(ZZ)');
+ }
+});
+
+Template.viewLogs.events({
+ 'click .new-logs'() {
+ Template.instance().atBottom = true;
+ return Template.instance().sendToBottomIfNecessary();
+ }
+});
+
+Template.viewLogs.onRendered(function() {
+
+ const wrapper = this.find('.terminal');
+ const wrapperUl = this.find('.terminal');
+ const newLogs = this.find('.new-logs');
+ const template = this;
+ template.isAtBottom = function(scrollThreshold) {
+ if (scrollThreshold == null) {
+ scrollThreshold = 0;
+ }
+ if (wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight) {
+ newLogs.className = 'new-logs not';
+ return true;
+ }
+ return false;
+ };
+ template.sendToBottom = function() {
+ wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight;
+ return newLogs.className = 'new-logs not';
+ };
+ template.checkIfScrollIsAtBottom = function() {
+ template.atBottom = template.isAtBottom(100);
+ readMessage.enable();
+ return readMessage.read();
+ };
+ template.sendToBottomIfNecessary = function() {
+ if (template.atBottom === true && template.isAtBottom() !== true) {
+ return template.sendToBottom();
+ } else if (template.atBottom === false) {
+ return newLogs.className = 'new-logs';
+ }
+ };
+ template.sendToBottomIfNecessaryDebounced = _.debounce(template.sendToBottomIfNecessary, 10);
+ template.sendToBottomIfNecessary();
+ if (window.MutationObserver == null) {
+ wrapperUl.addEventListener('DOMSubtreeModified', function() {
+ return template.sendToBottomIfNecessaryDebounced();
+ });
+ } else {
+ const observer = new MutationObserver(function(mutations) {
+ return mutations.forEach(function() {
+ return template.sendToBottomIfNecessaryDebounced();
+ });
+ });
+ observer.observe(wrapperUl, {
+ childList: true
+ });
+ }
+ template.onWindowResize = function() {
+ return Meteor.defer(function() {
+ return template.sendToBottomIfNecessaryDebounced();
+ });
+ };
+ window.addEventListener('resize', template.onWindowResize);
+ wrapper.addEventListener('mousewheel', function() {
+ template.atBottom = false;
+ return Meteor.defer(function() {
+ return template.checkIfScrollIsAtBottom();
+ });
+ });
+ wrapper.addEventListener('wheel', function() {
+ template.atBottom = false;
+ return Meteor.defer(function() {
+ return template.checkIfScrollIsAtBottom();
+ });
+ });
+ wrapper.addEventListener('touchstart', function() {
+ return template.atBottom = false;
+ });
+ wrapper.addEventListener('touchend', function() {
+ Meteor.defer(function() {
+ return template.checkIfScrollIsAtBottom();
+ });
+ Meteor.setTimeout(function() {
+ return template.checkIfScrollIsAtBottom();
+ }, 1000);
+ return Meteor.setTimeout(function() {
+ return template.checkIfScrollIsAtBottom();
+ }, 2000);
+ });
+ return wrapper.addEventListener('scroll', function() {
+ template.atBottom = false;
+ return Meteor.defer(function() {
+ return template.checkIfScrollIsAtBottom();
+ });
+ });
+});
diff --git a/packages/rocketchat-logger/logger.coffee b/packages/rocketchat-logger/logger.coffee
deleted file mode 100644
index dd6a6983cc7e..000000000000
--- a/packages/rocketchat-logger/logger.coffee
+++ /dev/null
@@ -1,73 +0,0 @@
-Template = Package.templating.Template
-
-Template.log = false
-Template.logMatch = /.*/
-
-Template.enableLogs = (log) ->
- Template.logMatch = /.*/
-
- if log is false
- Template.log = false
- else
- Template.log = true
- if log instanceof RegExp
- Template.logMatch = log
-
-
-wrapHelpersAndEvents = (original, prefix, color) ->
- return (dict) ->
- template = @
-
- for name, fn of dict
- do (name, fn) ->
- if fn instanceof Function
- dict[name] = ->
- result = fn.apply @, arguments
-
- if Template.log is true
- completeName = "#{prefix}:#{template.viewName.replace('Template.', '')}.#{name}"
-
- if Template.logMatch.test completeName
- console.log "%c#{completeName}", "color: #{color}", {args: arguments, scope: @, result: result}
-
- return result
-
- original.call template, dict
-
-
-Template.prototype.helpers = wrapHelpersAndEvents Template.prototype.helpers, 'helper', 'blue'
-Template.prototype.events = wrapHelpersAndEvents Template.prototype.events, 'event', 'green'
-
-
-wrapLifeCycle = (original, prefix, color) ->
- return (fn) ->
- template = @
-
- if fn instanceof Function
- wrap = ->
- result = fn.apply @, arguments
-
- if Template.log is true
- completeName = "#{prefix}:#{template.viewName.replace('Template.', '')}"
-
- if Template.logMatch.test completeName
- console.log "%c#{completeName}", "color: #{color}; font-weight: bold", {args: arguments, scope: @, result: result}
-
- return result
-
- original.call template, wrap
- else
- original.call template, fn
-
-
-Template.prototype.onCreated = wrapLifeCycle Template.prototype.onCreated, 'onCreated', 'blue'
-Template.prototype.onRendered = wrapLifeCycle Template.prototype.onRendered, 'onRendered', 'green'
-Template.prototype.onDestroyed = wrapLifeCycle Template.prototype.onDestroyed, 'onDestroyed', 'red'
-
-# stdout = new Mongo.Collection 'stdout'
-
-# Meteor.subscribe 'stdout'
-
-# stdout.find().observe
-# added: (record) ->
-# console.log ansispan record.string
diff --git a/packages/rocketchat-logger/logger.js b/packages/rocketchat-logger/logger.js
new file mode 100644
index 000000000000..2533b23828d1
--- /dev/null
+++ b/packages/rocketchat-logger/logger.js
@@ -0,0 +1,82 @@
+const Template = Package.templating.Template;
+
+Template.log = false;
+
+Template.logMatch = /.*/;
+
+Template.enableLogs = function(log) {
+ Template.logMatch = /.*/;
+ if (log === false) {
+ return Template.log = false;
+ } else {
+ Template.log = true;
+ if (log instanceof RegExp) {
+ return Template.logMatch = log;
+ }
+ }
+};
+
+const wrapHelpersAndEvents = function(original, prefix, color) {
+ return function(dict) {
+
+ const template = this;
+ const fn1 = function(name, fn) {
+ if (fn instanceof Function) {
+ return dict[name] = function() {
+
+ const result = fn.apply(this, arguments);
+ if (Template.log === true) {
+ const completeName = `${ prefix }:${ template.viewName.replace('Template.', '') }.${ name }`;
+ if (Template.logMatch.test(completeName)) {
+ console.log(`%c${ completeName }`, `color: ${ color }`, {
+ args: arguments,
+ scope: this,
+ result
+ });
+ }
+ }
+ return result;
+ };
+ }
+ };
+ _.each(name, (fn, name) => {
+ fn1(name, fn);
+ });
+ return original.call(template, dict);
+ };
+};
+
+Template.prototype.helpers = wrapHelpersAndEvents(Template.prototype.helpers, 'helper', 'blue');
+
+Template.prototype.events = wrapHelpersAndEvents(Template.prototype.events, 'event', 'green');
+
+const wrapLifeCycle = function(original, prefix, color) {
+ return function(fn) {
+ const template = this;
+ if (fn instanceof Function) {
+ const wrap = function() {
+ const result = fn.apply(this, arguments);
+ if (Template.log === true) {
+ const completeName = `${ prefix }:${ template.viewName.replace('Template.', '') }.${ name }`;
+ if (Template.logMatch.test(completeName)) {
+ console.log(`%c${ completeName }`, `color: ${ color }; font-weight: bold`, {
+ args: arguments,
+ scope: this,
+ result
+ });
+ }
+ }
+ return result;
+ };
+ return original.call(template, wrap);
+ } else {
+ return original.call(template, fn);
+ }
+ };
+};
+
+Template.prototype.onCreated = wrapLifeCycle(Template.prototype.onCreated, 'onCreated', 'blue');
+
+Template.prototype.onRendered = wrapLifeCycle(Template.prototype.onRendered, 'onRendered', 'green');
+
+Template.prototype.onDestroyed = wrapLifeCycle(Template.prototype.onDestroyed, 'onDestroyed', 'red');
diff --git a/packages/rocketchat-logger/package.js b/packages/rocketchat-logger/package.js
index 33bc00344bfd..f42adb6ff48c 100644
--- a/packages/rocketchat-logger/package.js
+++ b/packages/rocketchat-logger/package.js
@@ -7,7 +7,6 @@ Package.describe({
Package.onUse(function(api) {
api.use('mongo');
api.use('ecmascript');
- api.use('coffeescript');
api.use('underscore');
api.use('random');
api.use('logging');
@@ -17,12 +16,14 @@ Package.onUse(function(api) {
api.use('kadira:flow-router', 'client');
api.addFiles('ansispan.js', 'client');
- api.addFiles('logger.coffee', 'client');
- api.addFiles('client/viewLogs.coffee', 'client');
+ api.addFiles('logger.js', 'client');
+ api.addFiles('client/viewLogs.js', 'client');
api.addFiles('client/views/viewLogs.html', 'client');
- api.addFiles('client/views/viewLogs.coffee', 'client');
+ api.addFiles('client/views/viewLogs.js', 'client');
- api.addFiles('server.coffee', 'server');
+ api.addFiles('server.js', 'server');
api.export('Logger');
+ api.export('SystemLogger');
+ api.export('LoggerManager');
});
diff --git a/packages/rocketchat-logger/server.coffee b/packages/rocketchat-logger/server.coffee
deleted file mode 100644
index ceae9e07d4f8..000000000000
--- a/packages/rocketchat-logger/server.coffee
+++ /dev/null
@@ -1,352 +0,0 @@
-@LoggerManager = new class extends EventEmitter
- constructor: ->
- @enabled = false
- @loggers = {}
- @queue = []
-
- @showPackage = false
- @showFileAndLine = false
- @logLevel = 0
-
- register: (logger) ->
- if not logger instanceof Logger
- return
-
- @loggers[logger.name] = logger
-
- @emit 'register', logger
-
- addToQueue: (logger, args)->
- @queue.push
- logger: logger
- args: args
-
- dispatchQueue: ->
- for item in @queue
- item.logger._log.apply item.logger, item.args
-
- @clearQueue()
-
- clearQueue: ->
- @queue = []
-
- disable: ->
- @enabled = false
-
- enable: (dispatchQueue=false) ->
- @enabled = true
- if dispatchQueue is true
- @dispatchQueue()
- else
- @clearQueue()
-
-
-# @LoggerManager.on 'register', ->
-# console.log('on register', arguments)
-
-
-@Logger = class Logger
- defaultTypes:
- debug:
- name: 'debug'
- color: 'blue'
- level: 2
- log:
- name: 'info'
- color: 'blue'
- level: 1
- info:
- name: 'info'
- color: 'blue'
- level: 1
- success:
- name: 'info'
- color: 'green'
- level: 1
- warn:
- name: 'warn'
- color: 'magenta'
- level: 1
- error:
- name: 'error'
- color: 'red'
- level: 0
-
- constructor: (@name, config={}) ->
- self = @
- @config = {}
-
- _.extend @config, config
-
- if LoggerManager.loggers[@name]?
- LoggerManager.loggers[@name].warn 'Duplicated instance'
- return LoggerManager.loggers[@name]
-
- for type, typeConfig of @defaultTypes
- do (type, typeConfig) ->
- self[type] = (args...) ->
- self._log.call self,
- section: this.__section
- type: type
- level: typeConfig.level
- method: typeConfig.name
- arguments: args
-
- self[type+"_box"] = (args...) ->
- self._log.call self,
- section: this.__section
- type: type
- box: true
- level: typeConfig.level
- method: typeConfig.name
- arguments: args
-
- if @config.methods?
- for method, typeConfig of @config.methods
- do (method, typeConfig) ->
- if self[method]?
- self.warn "Method", method, "already exists"
-
- if not self.defaultTypes[typeConfig.type]?
- self.warn "Method type", typeConfig.type, "does not exist"
-
- self[method] = (args...) ->
- self._log.call self,
- section: this.__section
- type: typeConfig.type
- level: if typeConfig.level? then typeConfig.level else self.defaultTypes[typeConfig.type]?.level
- method: method
- arguments: args
-
- self[method+"_box"] = (args...) ->
- self._log.call self,
- section: this.__section
- type: typeConfig.type
- box: true
- level: if typeConfig.level? then typeConfig.level else self.defaultTypes[typeConfig.type]?.level
- method: method
- arguments: args
-
- if @config.sections?
- for section, name of @config.sections
- do (section, name) ->
- self[section] = {}
- for type, typeConfig of self.defaultTypes
- do (type, typeConfig) =>
- self[section][type] = =>
- self[type].apply {__section: name}, arguments
-
- self[section][type+"_box"] = =>
- self[type+"_box"].apply {__section: name}, arguments
-
- for method, typeConfig of self.config.methods
- do (method, typeConfig) =>
- self[section][method] = =>
- self[method].apply {__section: name}, arguments
-
- self[section][method+"_box"] = =>
- self[method+"_box"].apply {__section: name}, arguments
-
- LoggerManager.register @
- return @
-
- getPrefix: (options) ->
- if options.section?
- prefix = "#{@name} ➔ #{options.section}.#{options.method}"
- else
- prefix = "#{@name} ➔ #{options.method}"
-
- details = @_getCallerDetails()
-
- detailParts = []
- if details.package? and (LoggerManager.showPackage is true or options.type is 'error')
- detailParts.push details.package
-
- if LoggerManager.showFileAndLine is true or options.type is 'error'
- if details.file? and details.line?
- detailParts.push "#{details.file}:#{details.line}"
- else
- if details.file?
- detailParts.push details.file
- if details.line?
- detailParts.push details.line
-
- if @defaultTypes[options.type]?
- prefix = prefix[@defaultTypes[options.type].color]
-
- if detailParts.length > 0
- prefix = "#{detailParts.join(' ')} #{prefix}"
-
- return prefix
-
- # @returns {Object: { line: Number, file: String }}
- _getCallerDetails: ->
- getStack = () ->
- # We do NOT use Error.prepareStackTrace here (a V8 extension that gets us a
- # pre-parsed stack) since it's impossible to compose it with the use of
- # Error.prepareStackTrace used on the server for source maps.
- err = new Error
- stack = err.stack
- return stack
-
- stack = getStack()
-
- if not stack
- return {}
-
- lines = stack.split('\n')
-
- # looking for the first line outside the logging package (or an
- # eval if we find that first)
- line = undefined
- for item, index in lines when index > 0
- line = item
- if line.match(/^\s*at eval \(eval/)
- return {file: "eval"}
-
- if not line.match(/packages\/rocketchat_logger(?:\/|\.js)/)
- break
-
- details = {}
-
- # The format for FF is 'functionName@filePath:lineNumber'
- # The format for V8 is 'functionName (packages/logging/logging.js:81)' or
- # 'packages/logging/logging.js:81'
- match = /(?:[@(]| at )([^(]+?):([0-9:]+)(?:\)|$)/.exec(line)
- if not match
- return details
- # in case the matched block here is line:column
- details.line = match[2].split(':')[0]
-
- # Possible format: https://foo.bar.com/scripts/file.js?random=foobar
- # XXX: if you can write the following in better way, please do it
- # XXX: what about evals?
- details.file = match[1].split('/').slice(-1)[0].split('?')[0]
-
- packageMatch = match[1].match(/packages\/([^\.\/]+)(?:\/|\.)/)
- if packageMatch?
- details.package = packageMatch[1]
-
- return details
-
- makeABox: (message, title) ->
- if not _.isArray(message)
- message = message.split("\n")
-
- len = 0
- for line in message
- len = Math.max(len, line.length)
-
- topLine = "+--" + s.pad('', len, '-') + "--+"
- separator = "| " + s.pad('', len, '') + " |"
- lines = []
-
- lines.push topLine
- if title?
- lines.push "| " + s.lrpad(title, len) + " |"
- lines.push topLine
-
- lines.push separator
-
- for line in message
- lines.push "| " + s.rpad(line, len) + " |"
-
- lines.push separator
- lines.push topLine
- return lines
-
-
- _log: (options) ->
- if LoggerManager.enabled is false
- LoggerManager.addToQueue @, arguments
- return
-
- options.level ?= 1
-
- if LoggerManager.logLevel < options.level
- return
-
- prefix = @getPrefix(options)
-
- if options.box is true and _.isString(options.arguments[0])
- color = undefined
- if @defaultTypes[options.type]?
- color = @defaultTypes[options.type].color
-
- box = @makeABox options.arguments[0], options.arguments[1]
- subPrefix = '➔'
- if color?
- subPrefix = subPrefix[color]
-
- console.log subPrefix, prefix
- for line in box
- if color?
- console.log subPrefix, line[color]
- else
- console.log subPrefix, line
- else
- options.arguments.unshift prefix
- console.log.apply console, options.arguments
-
- return
-
-
-@SystemLogger = new Logger 'System',
- methods:
- startup:
- type: 'success'
- level: 0
-
-
-processString = (string, date) ->
- if string[0] is '{'
- try
- return Log.format EJSON.parse(string), {color: true}
-
- try
- return Log.format {message: string, time: date, level: 'info'}, {color: true}
-
- return string
-
-StdOut = new class extends EventEmitter
- constructor: ->
- @queue = []
- write = process.stdout.write
- process.stdout.write = (string, encoding, fd) =>
- write.apply(process.stdout, arguments)
- date = new Date
- string = processString string, date
-
- item =
- id: Random.id()
- string: string
- ts: date
-
- @queue.push item
-
- if RocketChat?.settings?.get('Log_View_Limit')? and @queue.length > RocketChat.settings.get('Log_View_Limit')
- @queue.shift()
-
- @emit 'write', string, item
-
-
-Meteor.publish 'stdout', ->
- unless @userId
- return @ready()
-
- if RocketChat.authz.hasPermission(@userId, 'view-logs') isnt true
- return @ready()
-
- for item in StdOut.queue
- @added 'stdout', item.id,
- string: item.string
- ts: item.ts
-
- @ready()
-
- StdOut.on 'write', (string, item) =>
- @added 'stdout', item.id,
- string: item.string
- ts: item.ts
-
- return
diff --git a/packages/rocketchat-logger/server.js b/packages/rocketchat-logger/server.js
new file mode 100644
index 000000000000..564751f6f7e1
--- /dev/null
+++ b/packages/rocketchat-logger/server.js
@@ -0,0 +1,373 @@
+/* globals EventEmitter LoggerManager SystemLogger Log*/
+
+//TODO: change this global to import
+LoggerManager = new class extends EventEmitter { // eslint-disable-line no-undef
+ constructor() {
+ super();
+ this.enabled = false;
+ this.loggers = {};
+ this.queue = [];
+ this.showPackage = false;
+ this.showFileAndLine = false;
+ this.logLevel = 0;
+ }
+ register(logger) {
+ if (!logger instanceof Logger) {
+ return;
+ }
+ this.loggers[logger.name] = logger;
+ this.emit('register', logger);
+ }
+ addToQueue(logger, args) {
+ this.queue.push({
+ logger, args
+ });
+ }
+ dispatchQueue() {
+ _.each(this.queue, (item) => item.logger._log.apply(item.logger, item.args));
+ this.clearQueue();
+ }
+ clearQueue() {
+ this.queue = [];
+ }
+
+ disable() {
+ this.enabled = false;
+ }
+
+ enable(dispatchQueue = false) {
+ this.enabled = true;
+ return (dispatchQueue === true) ? this.dispatchQueue() : this.clearQueue();
+ }
+};
+
+
+
+const defaultTypes = {
+ debug: {
+ name: 'debug',
+ color: 'blue',
+ level: 2
+ },
+ log: {
+ name: 'info',
+ color: 'blue',
+ level: 1
+ },
+ info: {
+ name: 'info',
+ color: 'blue',
+ level: 1
+ },
+ success: {
+ name: 'info',
+ color: 'green',
+ level: 1
+ },
+ warn: {
+ name: 'warn',
+ color: 'magenta',
+ level: 1
+ },
+ error: {
+ name: 'error',
+ color: 'red',
+ level: 0
+ }
+};
+
+class _Logger {
+ constructor(name, config = {}) {
+ self = this;
+ this.name = name;
+
+ this.config = Object.assign({}, config);
+ if (LoggerManager.loggers && LoggerManager.loggers[this.name] != null) {
+ LoggerManager.loggers[this.name].warn('Duplicated instance');
+ return LoggerManager.loggers[this.name];
+ }
+ _.each(defaultTypes, (typeConfig, type) => {
+ this[type] = function(...args) {
+ return self._log.call(self, {
+ section: this.__section,
+ type,
+ level: typeConfig.level,
+ method: typeConfig.name,
+ 'arguments': args
+ });
+ };
+
+ self[`${ type }_box`] = function(...args) {
+ return self._log.call(self, {
+ section: this.__section,
+ type,
+ box: true,
+ level: typeConfig.level,
+ method: typeConfig.name,
+ 'arguments': args
+ });
+ };
+ });
+ if (this.config.methods) {
+ _.each(this.config.methods, (typeConfig, method) => {
+ if (this[method] != null) {
+ self.warn(`Method ${ method } already exists`);
+ }
+ if (defaultTypes[typeConfig.type] == null) {
+ self.warn(`Method type ${ typeConfig.type } does not exist`);
+ }
+ this[method] = function(...args) {
+ return self._log.call(self, {
+ section: this.__section,
+ type: typeConfig.type,
+ level: typeConfig.level != null ? typeConfig.level : defaultTypes[typeConfig.type] && defaultTypes[typeConfig.type].level,
+ method,
+ 'arguments': args
+ });
+ };
+ this[`${ method }_box`] = function(...args) {
+ return self._log.call(self, {
+ section: this.__section,
+ type: typeConfig.type,
+ box: true,
+ level: typeConfig.level != null ? typeConfig.level : defaultTypes[typeConfig.type] && defaultTypes[typeConfig.type].level,
+ method,
+ 'arguments': args
+ });
+ };
+ });
+ }
+ if (this.config.sections) {
+ _.each(this.config.sections, (name, section) => {
+ this[section] = {};
+ _.each(defaultTypes, (typeConfig, type) => {
+ self[section][type] = () => self[type].apply({__section: name}, arguments);
+ self[section][`${ type }_box`] = () => self[`${ type }_box`].apply({__section: name}, arguments);
+ });
+ _.each(this.config.methods, (typeConfig, method) => {
+ self[section][method] = () => self[method].apply({__section: name}, arguments);
+ self[section][`${ method }_box`] = () => self[`${ method }_box`].apply({__section: name}, arguments);
+ });
+ });
+ }
+
+ LoggerManager.register(this);
+ }
+ getPrefix(options) {
+ let prefix = `${ this.name } ➔ ${ options.method }`;
+ if (options.section) {
+ prefix = `${ this.name } ➔ ${ options.section }.${ options.method }`;
+ }
+ const details = this._getCallerDetails();
+ const detailParts = [];
+ if (details['package'] && (LoggerManager.showPackage === true || options.type === 'error')) {
+ detailParts.push(details['package']);
+ }
+ if (LoggerManager.showFileAndLine === true || options.type === 'error') {
+ if ((details.file != null) && (details.line != null)) {
+ detailParts.push(`${ details.file }:${ details.line }`);
+ } else {
+ if (details.file != null) {
+ detailParts.push(details.file);
+ }
+ if (details.line != null) {
+ detailParts.push(details.line);
+ }
+ }
+ }
+ if (defaultTypes[options.type]) {
+ // format the message to a colored message
+ prefix = prefix[defaultTypes[options.type].color];
+ }
+ if (detailParts.length > 0) {
+ prefix = `${ detailParts.join(' ') } ${ prefix }`;
+ }
+ return prefix;
+ }
+ _getCallerDetails() {
+ const getStack = () => {
+ // We do NOT use Error.prepareStackTrace here (a V8 extension that gets us a
+ // core-parsed stack) since it's impossible to compose it with the use of
+ // Error.prepareStackTrace used on the server for source maps.
+ const {stack} = new Error();
+ return stack;
+ };
+ const stack = getStack();
+ if (!stack) {
+ return {};
+ }
+ const lines = stack.split('\n').splice(1);
+ // looking for the first line outside the logging package (or an
+ // eval if we find that first)
+ let line = lines[0];
+ for (let index = 0, len = lines.length; index < len, index++; line = lines[index]) {
+ if (line.match(/^\s*at eval \(eval/)) {
+ return {file: 'eval'};
+ }
+
+ if (!line.match(/packages\/rocketchat_logger(?:\/|\.js)/)) {
+ break;
+ }
+ }
+
+ const details = {};
+ // The format for FF is 'functionName@filePath:lineNumber'
+ // The format for V8 is 'functionName (packages/logging/logging.js:81)' or
+ // 'packages/logging/logging.js:81'
+ const match = /(?:[@(]| at )([^(]+?):([0-9:]+)(?:\)|$)/.exec(line);
+ if (!match) {
+ return details;
+ }
+ details.line = match[2].split(':')[0];
+ // Possible format: https://foo.bar.com/scripts/file.js?random=foobar
+ // XXX: if you can write the following in better way, please do it
+ // XXX: what about evals?
+ details.file = match[1].split('/').slice(-1)[0].split('?')[0];
+ const packageMatch = match[1].match(/packages\/([^\.\/]+)(?:\/|\.)/);
+ if (packageMatch) {
+ details['package'] = packageMatch[1];
+ }
+ return details;
+ }
+ makeABox(message, title) {
+ if (!_.isArray(message)) {
+ message = message.split('\n');
+ }
+ let len = 0;
+
+ len = Math.max.apply(null, message.map(line => line.length));
+
+ const topLine = `+--${ s.pad('', len, '-') }--+`;
+ const separator = `| ${ s.pad('', len, '') } |`;
+ let lines = [];
+
+ lines.push(topLine);
+ if (title) {
+ lines.push(`| ${ s.lrpad(title, len) } |`);
+ lines.push(topLine);
+ }
+ lines.push(separator);
+
+ lines = [...lines, ...message.map(line => `| ${ s.rpad(line, len) } |`)];
+
+ lines.push(separator);
+ lines.push(topLine);
+ return lines;
+ }
+
+ _log(options) {
+ if (LoggerManager.enabled === false) {
+ LoggerManager.addToQueue(this, arguments);
+ return;
+ }
+ if (options.level == null) {
+ options.level = 1;
+ }
+
+ if (LoggerManager.logLevel < options.level) {
+ return;
+ }
+
+ const prefix = this.getPrefix(options);
+
+ if (options.box === true && _.isString(options.arguments[0])) {
+ let color = undefined;
+ if (defaultTypes[options.type]) {
+ color = defaultTypes[options.type].color;
+ }
+
+ const box = this.makeABox(options.arguments[0], options.arguments[1]);
+ let subPrefix = '➔';
+ if (color) {
+ subPrefix = subPrefix[color];
+ }
+
+ console.log(subPrefix, prefix);
+ box.forEach(line => {
+ console.log(subPrefix, color ? line[color]: line);
+ });
+
+ } else {
+ options.arguments.unshift(prefix);
+ console.log.apply(console, options.arguments);
+ }
+ }
+}
+// TODO: change this global to import
+Logger = global.Logger = _Logger;
+const processString = function(string, date) {
+ let obj;
+ try {
+ if (string[0] === '{') {
+ obj = EJSON.parse(string);
+ } else {
+ obj = {
+ message: string,
+ time: date,
+ level: 'info'
+ };
+ }
+ return Log.format(obj, {color: true});
+ } catch (error) {
+ return string;
+ }
+};
+// TODO: change this global to import
+SystemLogger = new Logger('System', { // eslint-disable-line no-undef
+ methods: {
+ startup: {
+ type: 'success',
+ level: 0
+ }
+ }
+});
+
+
+class StdOut extends EventEmitter {
+ constructor() {
+ super();
+ const write = process.stdout.write;
+ this.queue = [];
+ process.stdout.write = (string) => {
+ write.apply(process.stdout, arguments);
+ const date = new Date;
+ string = processString(string, date);
+ const item = {
+ id: Random.id(),
+ string,
+ ts: date
+ };
+ this.queue.push(item);
+ const limit = RocketChat.settings.get('Log_View_Limit');
+ if (limit && this.queue.length > limit) {
+ this.queue.shift();
+ }
+ this.emit('write', string, item);
+ };
+ }
+}
+
+
+Meteor.publish('stdout', function() {
+ if (!this.userId || RocketChat.authz.hasPermission(this.userId, 'view-logs') !== true) {
+ return this.ready();
+ }
+
+ StdOut.queue.forEach(item => {
+ this.added('stdout', item.id, {
+ string: item.string,
+ ts: item.ts
+ });
+ });
+
+ this.ready();
+ StdOut.on('write', (string, item) => {
+ this.added('stdout', item.id, {
+ string: item.string,
+ ts: item.ts
+ });
+ });
+});
+
+
+export { SystemLogger, StdOut, LoggerManager, processString, Logger };