mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-27 02:23:28 +08:00
282 lines
9 KiB
JavaScript
282 lines
9 KiB
JavaScript
// This file cannot be Coffeescript because it loads before the
|
|
// Coffeescript interpreter. Note that it runs in both browser and
|
|
// renderer processes.
|
|
|
|
var ErrorLogger, _, fs, path, app, os, remote;
|
|
os = require('os');
|
|
fs = require('fs-plus');
|
|
path = require('path');
|
|
ipcRenderer = null;
|
|
if (process.type === 'renderer') {
|
|
ipcRenderer = require('electron').ipcRenderer;
|
|
remote = require('electron').remote;
|
|
app = remote.app;
|
|
} else {
|
|
app = require('electron').app;
|
|
}
|
|
|
|
// A globally available ErrorLogger that can report errors to various
|
|
// sources and enhance error functionality.
|
|
//
|
|
// This runs in both the backend browser process and each and every
|
|
// renderer process.
|
|
//
|
|
// This is available as `global.errorLogger` in the backend browser
|
|
// process.
|
|
//
|
|
// It is available at `NylasEnv.errorLogger` in each renderer process.
|
|
// You should almost always use `NylasEnv.reportError` in the renderer
|
|
// processes instead of manually accessing the `errorLogger`
|
|
//
|
|
// The errorLogger will report errors to a log file as well as to 3rd
|
|
// party reporting services if enabled.
|
|
module.exports = ErrorLogger = (function() {
|
|
|
|
function ErrorLogger(args) {
|
|
this.reportError = this.reportError.bind(this)
|
|
this.inSpecMode = args.inSpecMode
|
|
this.inDevMode = args.inDevMode
|
|
this.resourcePath = args.resourcePath
|
|
|
|
this._extendErrorObject()
|
|
|
|
this._extendNativeConsole()
|
|
|
|
this.extensions = this._setupErrorLoggerExtensions(args)
|
|
|
|
if (this.inSpecMode) { return }
|
|
|
|
this._cleanOldLogFiles();
|
|
this._setupNewLogFile();
|
|
this._hookProcessOutputsToLogFile();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////// PUBLIC METHODS //////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
ErrorLogger.prototype.reportError = function(error, extra) {
|
|
if (!error) { error = {stack: ""} }
|
|
this._appendLog(error.stack)
|
|
if (extra) { this._appendLog(extra) }
|
|
if (process.type === "renderer") {
|
|
var errorJSON = JSON.stringify(error);
|
|
|
|
/**
|
|
* We synchronously send all errors to the backend main process.
|
|
*
|
|
* This is important because errors can frequently happen right
|
|
* before a renderer window is closing. Since error reporting hits
|
|
* APIs and is asynchronous it's possible for the window to be
|
|
* destroyed before the report makes it.
|
|
*
|
|
* This is a rare use of `sendSync` to ensure the command has made
|
|
* it before the window closes.
|
|
*/
|
|
ipcRenderer.sendSync("report-error", {errorJSON: errorJSON, extra: extra})
|
|
|
|
} else {
|
|
var nslog = require('nslog');
|
|
this._notifyExtensions("reportError", error, extra)
|
|
nslog(error.stack)
|
|
}
|
|
}
|
|
|
|
ErrorLogger.prototype.openLogs = function() {
|
|
var shell = require('electron').shell;
|
|
shell.openItem(this._logPath());
|
|
};
|
|
|
|
ErrorLogger.prototype.apiDebug = function(error) {
|
|
this._appendLog(error, error.statusCode, error.message);
|
|
this._notifyExtensions("onDidLogAPIError", error);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
////////////////////////// PRIVATE METHODS //////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
ErrorLogger.prototype._extendNativeConsole = function(args) {
|
|
console.debug = this._consoleDebug.bind(this)
|
|
|
|
if (process.type === 'browser' && process.platform === 'darwin') {
|
|
var nslog = require('nslog');
|
|
console.log = nslog;
|
|
console.error = nslog;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
ErrorLogger.prototype._extendErrorObject = function(args) {
|
|
Object.defineProperty(Error.prototype, 'toJSON', {
|
|
value: function () {
|
|
var alt = {};
|
|
|
|
Object.getOwnPropertyNames(this).forEach(function (key) {
|
|
alt[key] = this[key];
|
|
}, this);
|
|
|
|
return alt;
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
ErrorLogger.prototype._setupErrorLoggerExtensions = function(args) {
|
|
var extension, extensionConstructor, extensionPath, extensions, extensionsPath, i, len, ref;
|
|
if (args == null) {
|
|
args = {};
|
|
}
|
|
extensions = [];
|
|
extensionsPath = path.join(args.resourcePath, 'src', 'error-logger-extensions');
|
|
ref = fs.listSync(extensionsPath);
|
|
for (i = 0, len = ref.length; i < len; i++) {
|
|
extensionPath = ref[i];
|
|
if (path.basename(extensionPath)[0] === '.') {
|
|
continue;
|
|
}
|
|
extensionConstructor = require(extensionPath);
|
|
if (!(typeof extensionConstructor === "function")) {
|
|
throw new Error("Logger Extensions must return an extension constructor");
|
|
}
|
|
extension = new extensionConstructor({
|
|
inSpecMode: args.inSpecMode,
|
|
inDevMode: args.inDevMode,
|
|
resourcePath: args.resourcePath
|
|
});
|
|
extensions.push(extension);
|
|
}
|
|
return extensions;
|
|
};
|
|
|
|
ErrorLogger.prototype._logPath = function() {
|
|
var tmpPath = app.getPath('temp');
|
|
|
|
var logpid = process.pid;
|
|
if (process.type === 'renderer') {
|
|
logpid = remote.process.pid + "." + process.pid;
|
|
}
|
|
return path.join(tmpPath, 'Nylas-N1-' + logpid + '.log');
|
|
}
|
|
|
|
// 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.
|
|
ErrorLogger.prototype._cleanOldLogFiles = function() {
|
|
if (process.type === 'browser') {
|
|
var tmpPath = app.getPath('temp');
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
ErrorLogger.prototype._setupNewLogFile = function() {
|
|
// Open a file write stream to log output from this process
|
|
console.log("Streaming log data to "+this._logPath());
|
|
|
|
this.loghost = os.hostname();
|
|
this.logstream = fs.createWriteStream(this._logPath(), {
|
|
flags: 'a',
|
|
encoding: 'utf8',
|
|
fd: null,
|
|
mode: 0666
|
|
});
|
|
}
|
|
|
|
ErrorLogger.prototype._hookProcessOutputsToLogFile = 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]);
|
|
});
|
|
}
|
|
|
|
ErrorLogger.prototype._notifyExtensions = function() {
|
|
var command, args;
|
|
command = arguments[0]
|
|
args = 2 <= arguments.length ? Array.prototype.slice.call(arguments, 1) : [];
|
|
for (var i=0; i < this.extensions.length; i++) {
|
|
extension = this.extensions[i]
|
|
extension[command].apply(this, args);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
ErrorLogger.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]);
|
|
}
|
|
|
|
ErrorLogger.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("ErrorLogger: Unable to write to the log stream!" + err.toString());
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error("ErrorLogger: Unable to write to the log stream." + err.toString());
|
|
}
|
|
};
|
|
|
|
return ErrorLogger;
|
|
|
|
})();
|