mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
feat(log): extract reporting and log API failures
Summary: We now log API failures Test Plan: manual Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2216
This commit is contained in:
parent
bfb16bbe1a
commit
455b4563a9
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,7 +21,6 @@ yoursway-create-dmg
|
|||
/_site
|
||||
/.sass-cache
|
||||
|
||||
|
||||
!spec/fixtures/packages/package-with-incompatible-native-module/node_modules
|
||||
|
||||
#emacs
|
||||
|
|
|
@ -69,8 +69,7 @@
|
|||
{ label: 'Run Plugin Specs...', command: 'application:run-package-specs' }
|
||||
{ label: 'Run N1 Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
{ label: 'Ship Detailed Logs to Nylas', command: 'application:ship-logs' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorlogger-logs' }
|
||||
]
|
||||
}
|
||||
{
|
||||
|
|
|
@ -49,8 +49,7 @@
|
|||
{ label: 'Run Plugin &Specs...', command: 'application:run-package-specs' }
|
||||
{ label: 'Run &N1 Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
{ label: 'Ship Detailed Logs to Nylas', command: 'application:ship-logs' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorlogger-logs' }
|
||||
]
|
||||
}
|
||||
{
|
||||
|
|
|
@ -38,8 +38,7 @@
|
|||
{ label: 'Run Plugin &Specs...', command: 'application:run-package-specs' }
|
||||
{ label: 'Run &N1 Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
{ label: 'Ship Detailed Logs to Nylas', command: 'application:ship-logs' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorlogger-logs' }
|
||||
]
|
||||
}
|
||||
{
|
||||
|
|
|
@ -144,7 +144,7 @@ class Atom extends Model
|
|||
unless @inDevMode() or @inSpecMode()
|
||||
require('grim').deprecate = ->
|
||||
|
||||
@setupErrorHandling()
|
||||
@setupErrorLogger()
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
|
@ -222,11 +222,12 @@ class Atom extends Model
|
|||
# Start our error reporting to the backend and attach error handlers
|
||||
# to the window and the Bluebird Promise library, converting things
|
||||
# back through the sourcemap as necessary.
|
||||
setupErrorHandling: ->
|
||||
ErrorReporter = require './error-reporter'
|
||||
@errorReporter = new ErrorReporter
|
||||
setupErrorLogger: ->
|
||||
ErrorLogger = require './error-logger'
|
||||
@errorLogger = new ErrorLogger
|
||||
inSpecMode: @inSpecMode()
|
||||
inDevMode: @inDevMode()
|
||||
resourcePath: @getLoadSettings().resourcePath
|
||||
|
||||
sourceMapCache = {}
|
||||
|
||||
|
|
|
@ -220,14 +220,6 @@ class Application
|
|||
resourcePath: @resourcePath
|
||||
safeMode: @windowManager.focusedWindow()?.safeMode
|
||||
|
||||
@on 'application:ship-logs', ->
|
||||
global.errorReporter.shipLogs("User triggered.")
|
||||
dialog.showMessageBox
|
||||
type: 'warning'
|
||||
buttons: ['OK']
|
||||
message: 'Your local N1 logs have been sent to LogStash.'
|
||||
title: 'Logs Shipped'
|
||||
|
||||
@on 'application:run-package-specs', ->
|
||||
dialog.showOpenDialog {
|
||||
title: 'Choose a Package Directory'
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
global.shellStartTime = Date.now()
|
||||
|
||||
app = require 'app'
|
||||
fs = require 'fs'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
optimist = require 'optimist'
|
||||
|
||||
start = ->
|
||||
args = parseCommandLine()
|
||||
|
||||
global.errorReporter = setupErrorReporter(args)
|
||||
global.errorLogger = setupErrorLogger(args)
|
||||
|
||||
setupCoffeeScript()
|
||||
|
||||
|
@ -59,9 +59,12 @@ global.devResourcePath = process.env.N1_PATH ? process.cwd()
|
|||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
global.devResourcePath = path.normalize(global.devResourcePath) if global.devResourcePath
|
||||
|
||||
setupErrorReporter = (args={}) ->
|
||||
ErrorReporter = require '../error-reporter'
|
||||
return new ErrorReporter({inSpecMode: args.test, inDevMode: args.devMode})
|
||||
setupErrorLogger = (args={}) ->
|
||||
ErrorLogger = require '../error-logger'
|
||||
return new ErrorLogger
|
||||
inSpecMode: args.test
|
||||
inDevMode: args.devMode
|
||||
resourcePath: args.resourcePath
|
||||
|
||||
setupCrashReporter = ->
|
||||
# In the future, we may want to collect actual native crash reports,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Generated by CoffeeScript 1.9.2
|
||||
(function() {
|
||||
var app, fs, optimist, parseCommandLine, path, ref, setupCoffeeScript, setupCrashReporter, setupErrorReporter, start;
|
||||
var app, fs, optimist, parseCommandLine, path, ref, setupCoffeeScript, setupCrashReporter, setupErrorLogger, start;
|
||||
|
||||
global.shellStartTime = Date.now();
|
||||
|
||||
app = require('app');
|
||||
|
||||
fs = require('fs');
|
||||
fs = require('fs-plus');
|
||||
|
||||
path = require('path');
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
|||
start = function() {
|
||||
var SquirrelUpdate, addPathToOpen, addUrlToOpen, args, squirrelCommand;
|
||||
args = parseCommandLine();
|
||||
global.errorReporter = setupErrorReporter(args);
|
||||
global.errorLogger = setupErrorLogger(args);
|
||||
setupCoffeeScript();
|
||||
if (process.platform === 'win32') {
|
||||
SquirrelUpdate = require('./squirrel-update');
|
||||
|
@ -69,15 +69,16 @@
|
|||
global.devResourcePath = path.normalize(global.devResourcePath);
|
||||
}
|
||||
|
||||
setupErrorReporter = function(args) {
|
||||
var ErrorReporter;
|
||||
setupErrorLogger = function(args) {
|
||||
var ErrorLogger;
|
||||
if (args == null) {
|
||||
args = {};
|
||||
}
|
||||
ErrorReporter = require('../error-reporter');
|
||||
return new ErrorReporter({
|
||||
ErrorLogger = require('../error-logger');
|
||||
return new ErrorLogger({
|
||||
inSpecMode: args.test,
|
||||
inDevMode: args.devMode
|
||||
inDevMode: args.devMode,
|
||||
resourcePath: args.resourcePath
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -103,7 +104,7 @@
|
|||
var args, devMode, executedFrom, logFile, newWindow, options, packageDirectoryPath, packageManifest, packageManifestPath, pathsToOpen, pidToKillWhenClosed, resourcePath, safeMode, specDirectory, specFilePattern, specsOnCommandLine, test, version;
|
||||
version = app.getVersion();
|
||||
options = optimist(process.argv.slice(1));
|
||||
options.usage("Atom Editor v" + version + "\n\nUsage: atom [options] [path ...]\n\nOne or more paths to files or folders to open may be specified.\n\nFile paths will open in the current window.\n\nFolder paths will open in an existing window if that folder has already been\nopened or a new window if it hasn't.\n\nEnvironment Variables:\nN1_PATH The path from which Atom loads source code in dev mode.\n Defaults to `cwd`.");
|
||||
options.usage("Atom Editor v" + version + "\n\nUsage: atom [options] [path ...]\n\nOne or more paths to files or folders to open may be specified.\n\nFile paths will open in the current window.\n\nFolder paths will open in an existing window if that folder has already been\nopened or a new window if it hasn't.\n\nEnvironment Variables:\nN1_PATH The path from which Atom loads source code in dev mode.\n Defaults to `cwd`.");
|
||||
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.');
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.');
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.');
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// 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');
|
||||
var ErrorLogger, _, fs, path, app, os, remote;
|
||||
os = require('os');
|
||||
_ = require('underscore');
|
||||
fs = require('fs-plus');
|
||||
|
@ -38,16 +37,18 @@ Object.defineProperty(Error.prototype, 'toJSON', {
|
|||
configurable: true
|
||||
});
|
||||
|
||||
module.exports = ErrorReporter = (function() {
|
||||
module.exports = ErrorLogger = (function() {
|
||||
|
||||
function ErrorReporter(modes) {
|
||||
function ErrorLogger(args) {
|
||||
var self = this;
|
||||
|
||||
this.inSpecMode = modes.inSpecMode
|
||||
this.inDevMode = modes.inDevMode
|
||||
this.inSpecMode = args.inSpecMode
|
||||
this.inDevMode = args.inDevMode
|
||||
this.resourcePath = args.resourcePath
|
||||
|
||||
this.extensions = this.setupErrorLoggerExtensions(args)
|
||||
|
||||
if (!this.inSpecMode) {
|
||||
this._setupSentry();
|
||||
this._cleanOldLogFiles();
|
||||
this._setupNewLogFile();
|
||||
this._hookProcessOutputs();
|
||||
|
@ -57,20 +58,37 @@ module.exports = ErrorReporter = (function() {
|
|||
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);
|
||||
});
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
// 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() {
|
||||
ErrorLogger.prototype._cleanOldLogFiles = function() {
|
||||
if (process.type === 'browser') {
|
||||
fs.readdir(tmpPath, function(err, files) {
|
||||
if (err) {
|
||||
|
@ -95,10 +113,7 @@ module.exports = ErrorReporter = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
ErrorReporter.prototype._setupNewLogFile = function() {
|
||||
this.shipLogsQueued = false;
|
||||
this.shipLogsTime = 0;
|
||||
|
||||
ErrorLogger.prototype._setupNewLogFile = function() {
|
||||
// Open a file write stream to log output from this process
|
||||
console.log("Streaming log data to "+logpath);
|
||||
|
||||
|
@ -111,7 +126,7 @@ module.exports = ErrorReporter = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
ErrorReporter.prototype._hookProcessOutputs = function() {
|
||||
ErrorLogger.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
|
||||
|
@ -138,15 +153,30 @@ module.exports = ErrorReporter = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
ErrorReporter.prototype._catchUncaughtErrors = function() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorLogger.prototype.apiDebug = function(error) {
|
||||
this.appendLog(error, error.statusCode, error.message);
|
||||
this.notifyExtensions("onDidLogAPIError", error);
|
||||
}
|
||||
|
||||
ErrorLogger.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
|
||||
});
|
||||
if (self.inSpecMode || self.inDevMode) { return };
|
||||
self.appendLog(_arg.originalError, _arg.message);
|
||||
self.notifyExtensions("onDidThrowError", _arg.originalError, _arg.message)
|
||||
});
|
||||
|
||||
} else if (process.type === 'browser') {
|
||||
|
@ -157,7 +187,7 @@ module.exports = ErrorReporter = (function() {
|
|||
if (error == null) {
|
||||
error = {};
|
||||
}
|
||||
self.reportError(error);
|
||||
self.notifyExtensions("onDidThrowError", error)
|
||||
if (error.message != null) {
|
||||
nslog(error.message);
|
||||
}
|
||||
|
@ -172,7 +202,7 @@ module.exports = ErrorReporter = (function() {
|
|||
// 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() {
|
||||
ErrorLogger.prototype.consoleDebug = function() {
|
||||
var args = [];
|
||||
var showIt = arguments[0];
|
||||
for (var ii = 1; ii < arguments.length; ii++) {
|
||||
|
@ -184,7 +214,7 @@ module.exports = ErrorReporter = (function() {
|
|||
this.appendLog.apply(this, [args]);
|
||||
}
|
||||
|
||||
ErrorReporter.prototype.appendLog = function(obj) {
|
||||
ErrorLogger.prototype.appendLog = function(obj) {
|
||||
if (this.inSpecMode) { return };
|
||||
|
||||
try {
|
||||
|
@ -196,94 +226,19 @@ module.exports = ErrorReporter = (function() {
|
|||
|
||||
this.logstream.write(message, 'utf8', function (err) {
|
||||
if (err) {
|
||||
console.error("ErrorReporter: Unable to write to the log stream!" + err.toString());
|
||||
console.error("ErrorLogger: Unable to write to the log stream!" + err.toString());
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("ErrorReporter: Unable to write to the log stream." + err.toString());
|
||||
console.error("ErrorLogger: Unable to write to the log stream." + err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
ErrorReporter.prototype.openLogs = function() {
|
||||
ErrorLogger.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;
|
||||
return ErrorLogger;
|
||||
|
||||
})();
|
|
@ -97,6 +97,7 @@ class NylasAPIRequest
|
|||
response ?= {}
|
||||
response.statusCode = TimeoutErrorCode
|
||||
apiError = new APIError({error, response, body, requestOptions: @options})
|
||||
atom.errorLogger.apiDebug(apiError)
|
||||
@options.error?(apiError)
|
||||
reject(apiError)
|
||||
else
|
||||
|
|
|
@ -189,7 +189,7 @@ class DatabaseStore extends NylasStore
|
|||
_handleSetupError: (err) =>
|
||||
console.error(err)
|
||||
console.log(atom.getWindowType())
|
||||
atom.errorReporter.reportError(err)
|
||||
atom.emitError(err)
|
||||
app = require('remote').getGlobal('application')
|
||||
app.rebuildDatabase()
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
request = require 'request'
|
||||
|
||||
detailedLogging = true
|
||||
detailedLog = (msg) ->
|
||||
console.log(msg) if detailedLogging
|
||||
|
||||
module.exports = (dir, regexPattern) ->
|
||||
callback = @async()
|
||||
|
||||
console.log("Running log ship: #{dir}, #{regexPattern}")
|
||||
|
||||
fs.readdir dir, (err, files) ->
|
||||
log("readdir error: #{err}") if err
|
||||
logs = []
|
||||
logFilter = new RegExp(regexPattern)
|
||||
for file in files
|
||||
if logFilter.test(file)
|
||||
filepath = path.join(dir, file)
|
||||
stats = fs.statSync(filepath)
|
||||
logs.push(filepath) if stats["size"] > 0
|
||||
|
||||
remaining = 0
|
||||
finished = ->
|
||||
remaining -= 1
|
||||
if remaining is 0
|
||||
callback()
|
||||
|
||||
if logs.length is 0
|
||||
detailedLog("No logs found to upload.")
|
||||
callback()
|
||||
return
|
||||
|
||||
logs.forEach (log) ->
|
||||
remaining += 1
|
||||
url = 'https://edgehill.nylas.com/ingest-log'
|
||||
formData =
|
||||
file:
|
||||
value: fs.createReadStream(log, {flags: 'r'})
|
||||
options:
|
||||
filename: 'log.txt',
|
||||
contentType: 'text/plain'
|
||||
|
||||
request.post {url, formData}, (err, response, body) ->
|
||||
if err
|
||||
detailedLog("Error uploading #{log}: #{err.toString()}")
|
||||
else if response.statusCode isnt 200
|
||||
detailedLog("Error uploading #{log}: status code #{response.statusCode}")
|
||||
else
|
||||
detailedLog("Successfully uploaded #{log}")
|
||||
fs.truncate log, (err) =>
|
||||
console.log(err) if err
|
||||
fs.unlink log, (err) =>
|
||||
console.log(err) if err
|
||||
finished()
|
|
@ -71,8 +71,8 @@ class WindowEventHandler
|
|||
@subscribeToCommand $(window), 'window:toggle-dev-tools', ->
|
||||
atom.toggleDevTools()
|
||||
|
||||
@subscribeToCommand $(window), 'window:open-errorreporter-logs', ->
|
||||
atom.errorReporter.openLogs()
|
||||
@subscribeToCommand $(window), 'window:open-errorlogger-logs', ->
|
||||
atom.errorLogger.openLogs()
|
||||
|
||||
@subscribeToCommand $(window), 'window:toggle-component-regions', ->
|
||||
ComponentRegistry = require './component-registry'
|
||||
|
|
Loading…
Reference in a new issue