// This file cannot be Coffeescript because it loads before the Coffeescript // interpreter. Note that it runs in both browser and renderer processes. var ErrorReporter, raven, _, fs, path, app, os, remote; raven = require('raven'); os = require('os'); _ = require('underscore'); fs = require('fs-plus'); path = require('path'); if (process.type === 'renderer') { remote = require('remote'); app = remote.require('app'); } else { app = require('app'); } var tmpPath = app.getPath('temp'); var logpid = process.pid; if (process.type === 'renderer') { logpid = remote.process.pid + "." + process.pid; } var logpath = path.join(tmpPath, 'Nylas-N1-' + logpid + '.log'); // globally define Error.toJSON. This allows us to pass errors via IPC // and through the Action Bridge. Note:they are not re-inflated into // Error objects automatically. Object.defineProperty(Error.prototype, 'toJSON', { value: function () { var alt = {}; Object.getOwnPropertyNames(this).forEach(function (key) { alt[key] = this[key]; }, this); return alt; }, configurable: true }); module.exports = ErrorReporter = (function() { function ErrorReporter(modes) { var self = this; this.inSpecMode = modes.inSpecMode this.inDevMode = modes.inDevMode if (!this.inSpecMode) { this._setupSentry(); this._cleanOldLogFiles(); this._setupNewLogFile(); this._hookProcessOutputs(); this._catchUncaughtErrors(); } console.debug = _.bind(this.consoleDebug, this); } ErrorReporter.prototype._setupSentry = function() { // Initialize the Sentry connector this.client = new raven.Client('https://7a32cb0189ff4595a55c98ffb7939c46:f791c3c402b343068bed056b8b504dd5@sentry.nylas.com/4'); this.client.on('error', function(e) { console.log(e.reason); console.log(e.statusCode); return console.log(e.response); }); } // If we're the browser process, remove log files that are more than // two days old. These log files get pretty big because we're logging // so verbosely. ErrorReporter.prototype._cleanOldLogFiles = function() { if (process.type === 'browser') { fs.readdir(tmpPath, function(err, files) { if (err) { console.error(err); return; } var logFilter = new RegExp("Nylas-N1-[.0-9]*.log$"); files.forEach(function(file) { if (logFilter.test(file) === true) { var filepath = path.join(tmpPath, file); fs.stat(filepath, function(err, stats) { var lastModified = new Date(stats['mtime']); var fileAge = Date.now() - lastModified.getTime(); if (fileAge > (1000 * 60 * 60 * 24 * 2)) { // two days fs.unlink(filepath); } }); } }); }); } } ErrorReporter.prototype._setupNewLogFile = function() { this.shipLogsQueued = false; this.shipLogsTime = 0; // Open a file write stream to log output from this process console.log("Streaming log data to "+logpath); this.loghost = os.hostname(); this.logstream = fs.createWriteStream(logpath, { flags: 'a', encoding: 'utf8', fd: null, mode: 0666 }); } ErrorReporter.prototype._hookProcessOutputs = function() { var self = this; // Override stdout and stderr to pipe their output to the file // in addition to calling through to the existing implementation function hook_process_output(channel, callback) { var old_write = process[channel].write; process[channel].write = (function(write) { return function(string, encoding, fd) { write.apply(process[channel], arguments) callback(string, encoding, fd) } })(process[channel].write) // Return a function that can be used to undo this change return function() { process[channel].write = old_write }; } hook_process_output('stdout', function(string, encoding, fd) { self.appendLog.apply(self, [string]); }); hook_process_output('stderr', function(string, encoding, fd) { self.appendLog.apply(self, [string]); }); } ErrorReporter.prototype._catchUncaughtErrors = function() { var self = this; // Link to the appropriate error handlers for the browser // or renderer process if (process.type === 'renderer') { atom.onDidThrowError(function(_arg) { return self.reportError(_arg.originalError, { 'message': _arg.message }); }); } else if (process.type === 'browser') { var nslog = require('nslog'); console.log = nslog; process.on('uncaughtException', function(error) { if (error == null) { error = {}; } self.reportError(error); if (error.message != null) { nslog(error.message); } if (error.stack != null) { return nslog(error.stack); } }); } } // Create a new console.debug option, which takes `true` (print) // or `false`, don't print in console as the first parameter. // This makes it easy for developers to turn on and off // "verbose console" mode. ErrorReporter.prototype.consoleDebug = function() { var args = []; var showIt = arguments[0]; for (var ii = 1; ii < arguments.length; ii++) { args.push(arguments[ii]); } if ((this.inDevMode === true) && (showIt === true)) { console.log.apply(console, args); } this.appendLog.apply(this, [args]); } ErrorReporter.prototype.appendLog = function(obj) { if (this.inSpecMode) { return }; try { var message = JSON.stringify({ host: this.loghost, timestamp: (new Date()).toISOString(), payload: obj })+"\n"; this.logstream.write(message, 'utf8', function (err) { if (err) { console.error("ErrorReporter: Unable to write to the log stream!" + err.toString()); } }); } catch (err) { console.error("ErrorReporter: Unable to write to the log stream." + err.toString()); } }; ErrorReporter.prototype.openLogs = function() { var shell = require('shell'); shell.openItem(logpath); }; ErrorReporter.prototype.shipLogs = function(reason) { if (this.inSpecMode) { return }; if (!this.shipLogsQueued) { var timeSinceLogShip = Date.now() - this.shipLogsTime; if (timeSinceLogShip > 20000) { this.runShipLogsTask(reason); } else { this.shipLogsQueued = true; var self = this; setTimeout(function() { self.runShipLogsTask(reason); self.shipLogsQueued = false; }, 20000 - timeSinceLogShip); } } }; ErrorReporter.prototype.runShipLogsTask = function(reason) { if (this.inSpecMode) { return }; var self = this; this.shipLogsTime = Date.now(); if (!reason) { reason = ""; } var logPattern = null; if (process.type === 'renderer') { logPattern = "Nylas-N1-"+remote.process.pid+"[.0-9]*.log$"; } else { logPattern = "Nylas-N1-"+process.pid+"[.0-9]*.log$"; } console.log("ErrorReporter: Shipping Logs. " + reason); Task = require('./task'); ship = Task.once(fs.absolute('./tasks/ship-logs-task'), tmpPath, logPattern, function() { self.appendLog("ErrorReporter: Shipped Logs."); }); }; ErrorReporter.prototype.getVersion = function() { var _ref; return (typeof atom !== "undefined" && atom !== null ? atom.getVersion() : void 0) || ((_ref = require('app')) != null ? _ref.getVersion() : void 0); }; ErrorReporter.prototype.reportError = function(err, metadata) { if (this.inSpecMode || this.inDevMode) { return }; // Never send user auth tokens if (err.requestOptions && err.requestOptions.auth) { delete err.requestOptions['auth']; } // Never send message bodies if (err.requestOptions && err.requestOptions.body && err.requestOptions.body.body) { delete err.requestOptions.body['body']; } this.client.captureError(err, { extra: metadata, tags: { 'platform': process.platform, 'version': this.getVersion() } }); this.appendLog(err, metadata); this.shipLogs('Exception occurred'); }; return ErrorReporter; })();