mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-20 15:26:06 +08:00
Clean up error logging and tie to new Merani Sentry account
This commit is contained in:
parent
d87a602c8b
commit
b75aa778c7
|
@ -55,7 +55,7 @@ Target Ship Date: Late September
|
|||
#### Deployment
|
||||
- [x] Create a new AWS account for Merani project
|
||||
- [x] Register Merani domain(s)
|
||||
- [ ] Setup Sentry for JavaScript error reporting
|
||||
- [x] Setup Sentry for JavaScript error reporting
|
||||
- [x] Obtain Mac Developer Certificate for Merani
|
||||
- [ ] Obtain Windows Verisign Certificate for Merani
|
||||
- [ ] Deploy new identity API to id.getmerani.com
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
"pathwatcher": "~6.2",
|
||||
"pick-react-known-prop": "0.x.x",
|
||||
"proxyquire": "1.3.1",
|
||||
"raven": "1.1.4",
|
||||
"raven": "2.1.1",
|
||||
"react": "15.6.1",
|
||||
"react-addons-css-transition-group": "15.6.0",
|
||||
"react-addons-perf": "15.6.0-rc.1",
|
||||
|
|
|
@ -183,25 +183,6 @@ describe("the `NylasEnv` global", function nylasEnvSpec() {
|
|||
spyOn(console, "error")
|
||||
});
|
||||
|
||||
it("emits will-throw-error", () => {
|
||||
spyOn(NylasEnv.emitter, "emit")
|
||||
NylasEnv.reportError(this.testErr);
|
||||
expect(NylasEnv.emitter.emit).toHaveBeenCalled();
|
||||
expect(NylasEnv.emitter.emit.callCount).toBe(2);
|
||||
expect(NylasEnv.emitter.emit.calls[0].args[0]).toBe("will-throw-error")
|
||||
expect(NylasEnv.emitter.emit.calls[1].args[0]).toBe("did-throw-error")
|
||||
});
|
||||
|
||||
it("returns if the event has its default prevented", () => {
|
||||
spyOn(NylasEnv.emitter, "emit").andCallFake((name, event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
NylasEnv.reportError(this.testErr);
|
||||
expect(NylasEnv.emitter.emit).toHaveBeenCalled();
|
||||
expect(NylasEnv.emitter.emit.callCount).toBe(1);
|
||||
expect(NylasEnv.emitter.emit.calls[0].args[0]).toBe("will-throw-error")
|
||||
});
|
||||
|
||||
it("opens dev tools in dev mode", () => {
|
||||
jasmine.unspy(NylasEnv, "inDevMode")
|
||||
spyOn(NylasEnv, "inDevMode").andReturn(true);
|
||||
|
@ -216,16 +197,6 @@ describe("the `NylasEnv` global", function nylasEnvSpec() {
|
|||
expect(NylasEnv.errorLogger.reportError.callCount).toBe(1);
|
||||
expect(NylasEnv.errorLogger.reportError.calls[0].args[0]).toBe(this.testErr);
|
||||
});
|
||||
|
||||
it("emits did-throw-error", () => {
|
||||
spyOn(NylasEnv.emitter, "emit")
|
||||
NylasEnv.reportError(this.testErr);
|
||||
expect(NylasEnv.openDevTools).not.toHaveBeenCalled();
|
||||
expect(NylasEnv.executeJavaScriptInDevTools).not.toHaveBeenCalled();
|
||||
expect(NylasEnv.emitter.emit.callCount).toBe(2);
|
||||
expect(NylasEnv.emitter.emit.calls[0].args[0]).toBe("will-throw-error")
|
||||
expect(NylasEnv.emitter.emit.calls[1].args[0]).toBe("did-throw-error")
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ if (process.type === 'renderer') {
|
|||
}
|
||||
|
||||
var crashReporter = require('electron').crashReporter
|
||||
var RavenErrorReporter = require('./error-logger-extensions/raven-error-reporter');
|
||||
|
||||
// A globally available ErrorLogger that can report errors to various
|
||||
// sources and enhance error functionality.
|
||||
|
@ -40,13 +41,19 @@ module.exports = ErrorLogger = (function() {
|
|||
this.inDevMode = args.inDevMode
|
||||
this.resourcePath = args.resourcePath
|
||||
|
||||
this._startCrashReporter()
|
||||
this._startCrashReporter();
|
||||
|
||||
this._extendErrorObject()
|
||||
this._extendErrorObject();
|
||||
|
||||
this._extendNativeConsole()
|
||||
this._extendNativeConsole();
|
||||
|
||||
this.extensions = this._setupErrorLoggerExtensions(args)
|
||||
this.extensions = [
|
||||
new RavenErrorReporter({
|
||||
inSpecMode: args.inSpecMode,
|
||||
inDevMode: args.inDevMode,
|
||||
resourcePath: args.resourcePath,
|
||||
}),
|
||||
]
|
||||
|
||||
if (this.inSpecMode) { return }
|
||||
|
||||
|
@ -113,9 +120,10 @@ module.exports = ErrorLogger = (function() {
|
|||
|
||||
ErrorLogger.prototype._startCrashReporter = function(args) {
|
||||
crashReporter.start({
|
||||
productName: 'Nylas Mail',
|
||||
companyName: 'Nylas',
|
||||
submitURL: 'https://electron-crash-report-server.herokuapp.com/',
|
||||
productName: 'Merani',
|
||||
companyName: 'Merani',
|
||||
submitURL: 'http://merani_prod.bugsplat.com/post/bp/crash/postBP.php',
|
||||
uploadToServer: true,
|
||||
autoSubmit: true,
|
||||
})
|
||||
}
|
||||
|
@ -144,37 +152,10 @@ module.exports = ErrorLogger = (function() {
|
|||
|
||||
return alt;
|
||||
},
|
||||
configurable: true
|
||||
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');
|
||||
|
||||
|
@ -203,7 +184,7 @@ module.exports = ErrorLogger = (function() {
|
|||
var filepath = path.join(tmpPath, file);
|
||||
fs.stat(filepath, function(err, stats) {
|
||||
if (!err && stats) {
|
||||
var lastModified = new Date(stats['mtime']);
|
||||
var lastModified = new Date(stats.mtime);
|
||||
var fileAge = Date.now() - lastModified.getTime();
|
||||
if (fileAge > (1000 * 60 * 60 * 24 * 2)) { // two days
|
||||
fs.unlink(filepath, () => {});
|
||||
|
@ -218,7 +199,7 @@ module.exports = ErrorLogger = (function() {
|
|||
|
||||
ErrorLogger.prototype._setupNewLogFile = function() {
|
||||
// Open a file write stream to log output from this process
|
||||
console.log("Streaming log data to "+this._logPath());
|
||||
console.log("Streaming log data to " + this._logPath());
|
||||
|
||||
this.loghost = os.hostname();
|
||||
this.logstream = fs.createWriteStream(this._logPath(), {
|
||||
|
@ -260,9 +241,9 @@ module.exports = ErrorLogger = (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++) {
|
||||
for (var i = 0; i < this.extensions.length; i++) {
|
||||
const extension = this.extensions[i]
|
||||
extension[command].apply(this, args);
|
||||
extension[command].apply(extension, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,14 +264,14 @@ module.exports = ErrorLogger = (function() {
|
|||
}
|
||||
|
||||
ErrorLogger.prototype._appendLog = function(obj) {
|
||||
if (this.inSpecMode) { return };
|
||||
if (this.inSpecMode) { return; }
|
||||
|
||||
try {
|
||||
var message = JSON.stringify({
|
||||
host: this.loghost,
|
||||
timestamp: (new Date()).toISOString(),
|
||||
payload: obj
|
||||
})+"\n";
|
||||
}) + "\n";
|
||||
|
||||
this.logstream.write(message, 'utf8', function (err) {
|
||||
if (err) {
|
||||
|
@ -303,5 +284,4 @@ module.exports = ErrorLogger = (function() {
|
|||
};
|
||||
|
||||
return ErrorLogger;
|
||||
|
||||
})();
|
||||
|
|
|
@ -31,13 +31,7 @@ function trimTo(str, size) {
|
|||
}
|
||||
|
||||
function handleUnrecoverableDatabaseError(err = (new Error(`Manually called handleUnrecoverableDatabaseError`))) {
|
||||
const fingerprint = ["{{ default }}", "unrecoverable database error", err.message];
|
||||
NylasEnv.errorLogger.reportError(err, {fingerprint,
|
||||
rateLimit: {
|
||||
ratePerHour: 30,
|
||||
key: `handleUnrecoverableDatabaseError:${err.message}`,
|
||||
},
|
||||
});
|
||||
NylasEnv.errorLogger.reportError(err);
|
||||
const app = remote.getGlobal('application');
|
||||
if (!app) {
|
||||
throw new Error('handleUnrecoverableDatabaseError: `app` is not ready!')
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
/* eslint global-require: 0 */
|
||||
/* eslint import/no-dynamic-require: 0 */
|
||||
import _ from 'underscore';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { ipcRenderer, remote } from 'electron';
|
||||
import _ from 'underscore';
|
||||
import { Emitter } from 'event-kit';
|
||||
import { convertStackTrace } from 'coffeestack';
|
||||
import { mapSourcePosition } from 'source-map-support';
|
||||
|
||||
import WindowEventHandler from './window-event-handler';
|
||||
|
||||
import Utils from './flux/models/utils';
|
||||
|
||||
function ensureInteger(f, fallback) {
|
||||
|
@ -26,9 +25,7 @@ function ensureInteger(f, fallback) {
|
|||
export default class NylasEnvConstructor {
|
||||
static initClass() {
|
||||
this.version = 1;
|
||||
|
||||
this.prototype.workspaceViewParentSelector = 'body';
|
||||
this.prototype.lastUncaughtError = null;
|
||||
|
||||
/*
|
||||
Section: Properties
|
||||
|
@ -56,10 +53,6 @@ export default class NylasEnvConstructor {
|
|||
this.prototype.styles = null; // Increment this when the serialization format changes
|
||||
}
|
||||
|
||||
assert(bool, msg) {
|
||||
if (!bool) { throw new Error(`Assertion error: ${msg}`); }
|
||||
}
|
||||
|
||||
// Load or create the application environment
|
||||
// Returns an NylasEnv instance, fully initialized
|
||||
static loadOrCreate() {
|
||||
|
@ -139,12 +132,6 @@ export default class NylasEnvConstructor {
|
|||
|
||||
// Call .loadOrCreate instead
|
||||
constructor(savedState = {}) {
|
||||
this.reportError = this.reportError.bind(this);
|
||||
this.getConfigDirPath = this.getConfigDirPath.bind(this);
|
||||
this.storeColumnWidth = this.storeColumnWidth.bind(this);
|
||||
this.getColumnWidth = this.getColumnWidth.bind(this);
|
||||
this.startWindow = this.startWindow.bind(this);
|
||||
this.populateHotWindow = this.populateHotWindow.bind(this);
|
||||
this.savedState = savedState;
|
||||
({version: this.version} = this.savedState);
|
||||
this.emitter = new Emitter();
|
||||
|
@ -201,7 +188,9 @@ export default class NylasEnvConstructor {
|
|||
}
|
||||
this.windowEventHandler = new WindowEventHandler();
|
||||
|
||||
this.extendRxObservables();
|
||||
// We extend nylas observables with our own methods. This happens on
|
||||
// require of nylas-observables
|
||||
require('nylas-observables');
|
||||
|
||||
// Nylas exports is designed to provide a lazy-loaded set of globally
|
||||
// accessible objects to all packages. Upon require, nylas-exports will
|
||||
|
@ -247,28 +236,18 @@ export default class NylasEnvConstructor {
|
|||
return this.reportError(originalError, {url, line: newLine, column: newColumn});
|
||||
};
|
||||
|
||||
process.on('uncaughtException', e => this.reportError(e));
|
||||
|
||||
// We use the native Node 'unhandledRejection' instead of Bluebird's
|
||||
// `Promise.onPossiblyUnhandledRejection`. Testing indicates that
|
||||
// the Node process method is a strict superset of Bluebird's handler.
|
||||
// With the introduction of transpiled async/await, it is now possible
|
||||
// to get a native, non-Bluebird Promise. In that case, Bluebird's
|
||||
// `onPossiblyUnhandledRejection` gets bypassed and we miss some
|
||||
// errors. The Node process handler catches all Bluebird promises plus
|
||||
// those created with a native Promise.
|
||||
process.on('unhandledRejection', error => {
|
||||
this._onUnhandledRejection(error, sourceMapCache)
|
||||
process.on('uncaughtException', e => {
|
||||
this.reportError(e);
|
||||
});
|
||||
|
||||
// Based on testing, there are some unhandled rejections that don't get
|
||||
// caught by `process.on('unhandledRejection')`, so we listen for unhandled
|
||||
// rejections on the`window` as well
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
process.on('unhandledRejection', (error) => {
|
||||
this._onUnhandledRejection(error, sourceMapCache);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
// This event is supposed to look like {reason, promise}, according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
|
||||
// In practice, it can have different shapes, so we try to make our best
|
||||
// guess
|
||||
// In practice, it can have different shapes, so we make our best guess
|
||||
if (!e) {
|
||||
const error = new Error(`Unknown window.unhandledrejection event.`)
|
||||
this._onUnhandledRejection(error, sourceMapCache)
|
||||
|
@ -295,49 +274,19 @@ export default class NylasEnvConstructor {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Given that we listen to unhandled rejections on both the `window` and the
|
||||
// `process`, more often than not both of those will get called almost
|
||||
// immedaitely with the same error. To prevent double reporting the same
|
||||
// error, we debounce this function with a very small interval
|
||||
_onUnhandledRejection = _.debounce((error, sourceMapCache) => {
|
||||
_onUnhandledRejection = (error, sourceMapCache) => {
|
||||
if (this.inDevMode()) {
|
||||
error.stack = convertStackTrace(error.stack, sourceMapCache);
|
||||
}
|
||||
this.reportError(error, {
|
||||
rateLimit: {
|
||||
ratePerHour: 30,
|
||||
key: `UnhandledRejection:${error.stack}`,
|
||||
},
|
||||
})
|
||||
}, 10)
|
||||
|
||||
_createErrorCallbackEvent(error, extraArgs = {}) {
|
||||
const event = Object.assign({}, extraArgs, {
|
||||
message: error.message,
|
||||
originalError: error,
|
||||
defaultPrevented: false,
|
||||
});
|
||||
event.preventDefault = () => { event.defaultPrevented = true; return true };
|
||||
return event;
|
||||
this.reportError(error);
|
||||
}
|
||||
|
||||
// Public: report an error through the `ErrorLogger`
|
||||
//
|
||||
// Takes an error and an extra object to report. Hooks into the
|
||||
// `onWillThrowError` and `onDidThrowError` callbacks. If someone
|
||||
// registered with `onWillThrowError` calls `preventDefault` on the event
|
||||
// object it's given, then no error will be reported.
|
||||
//
|
||||
// The difference between this and `ErrorLogger.reportError` is that
|
||||
// `NylasEnv.reportError` will hook into the event callbacks and handle
|
||||
// test failures and dev tool popups.
|
||||
// `NylasEnv.reportError` hooks into test failures and dev tool popups.
|
||||
//
|
||||
reportError(error, extra = {}, {noWindows} = {}) {
|
||||
const event = this._createErrorCallbackEvent(error, extra);
|
||||
this.emitter.emit('will-throw-error', event);
|
||||
if (event.defaultPrevented) { return; }
|
||||
|
||||
this.lastUncaughtError = error;
|
||||
|
||||
try {
|
||||
extra.pluginIds = this._findPluginsFromError(error);
|
||||
} catch (err) {
|
||||
|
@ -355,8 +304,6 @@ export default class NylasEnvConstructor {
|
|||
}
|
||||
|
||||
this.errorLogger.reportError(error, extra);
|
||||
|
||||
this.emitter.emit('did-throw-error', event);
|
||||
}
|
||||
|
||||
_findPluginsFromError(error) {
|
||||
|
@ -377,38 +324,6 @@ export default class NylasEnvConstructor {
|
|||
Section: Event Subscription
|
||||
*/
|
||||
|
||||
// Extended: Invoke the given callback when there is an unhandled error, but
|
||||
// before the devtools pop open
|
||||
//
|
||||
// * `callback` {Function} to be called whenever there is an unhandled error
|
||||
// * `event` {Object}
|
||||
// * `originalError` {Object} the original error object
|
||||
// * `message` {String} the original error object
|
||||
// * `url` {String} Url to the file where the error originated.
|
||||
// * `line` {Number}
|
||||
// * `column` {Number}
|
||||
// * `preventDefault` {Function} call this to avoid popping up the dev tools.
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onWillThrowError(callback) {
|
||||
return this.emitter.on('will-throw-error', callback);
|
||||
}
|
||||
|
||||
// Extended: Invoke the given callback whenever there is an unhandled error.
|
||||
//
|
||||
// * `callback` {Function} to be called whenever there is an unhandled error
|
||||
// * `event` {Object}
|
||||
// * `originalError` {Object} the original error object
|
||||
// * `message` {String} the original error object
|
||||
// * `url` {String} Url to the file where the error originated.
|
||||
// * `line` {Number}
|
||||
// * `column` {Number}
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidThrowError(callback) {
|
||||
return this.emitter.on('did-throw-error', callback);
|
||||
}
|
||||
|
||||
// Extended: Run the Chromium content-tracing module for five seconds, and save
|
||||
// the output to a file which is printed to the command-line output of the app.
|
||||
// You can take the file exported by this function and load it into Chrome's
|
||||
|
@ -708,10 +623,6 @@ export default class NylasEnvConstructor {
|
|||
return this.setFullScreen(!this.isFullScreen());
|
||||
}
|
||||
|
||||
getAllWindowDimensions() {
|
||||
return remote.getGlobal('application').getAllWindowDimensions();
|
||||
}
|
||||
|
||||
// Get the dimensions of this window.
|
||||
//
|
||||
// Returns an {Object} with the following keys:
|
||||
|
@ -891,12 +802,6 @@ export default class NylasEnvConstructor {
|
|||
}
|
||||
}
|
||||
|
||||
// We extend nylas observables with our own methods. This happens on
|
||||
// require of nylas-observables
|
||||
extendRxObservables() {
|
||||
return require('nylas-observables');
|
||||
}
|
||||
|
||||
// Launches a new window via the browser/WindowLauncher.
|
||||
//
|
||||
// If you pass a `windowKey` in the options, and that windowKey already
|
||||
|
@ -974,8 +879,7 @@ export default class NylasEnvConstructor {
|
|||
}
|
||||
|
||||
exit(status) {
|
||||
const { app } = remote;
|
||||
app.emit('will-exit');
|
||||
remote.app.emit('will-exit');
|
||||
return remote.process.exit(status);
|
||||
}
|
||||
|
||||
|
@ -1096,4 +1000,5 @@ export default class NylasEnvConstructor {
|
|||
this.actionBridge.registerGlobalActions(...args);
|
||||
}
|
||||
}
|
||||
|
||||
NylasEnvConstructor.initClass();
|
||||
|
|
Loading…
Reference in a new issue