mirror of
https://github.com/atom/atom.git
synced 2026-01-15 01:48:15 -05:00
303 lines
8.5 KiB
JavaScript
303 lines
8.5 KiB
JavaScript
/** @babel */
|
|
|
|
import os from 'os';
|
|
import stackTrace from 'stack-trace';
|
|
import fs from 'fs-plus';
|
|
import path from 'path';
|
|
|
|
const API_KEY = '7ddca14cb60cbd1cd12d1b252473b076';
|
|
const LIB_VERSION = require('../package.json')['version'];
|
|
const StackTraceCache = new WeakMap();
|
|
|
|
export default class Reporter {
|
|
constructor(params = {}) {
|
|
this.request = params.request || window.fetch;
|
|
this.alwaysReport = params.hasOwnProperty('alwaysReport')
|
|
? params.alwaysReport
|
|
: false;
|
|
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors')
|
|
? params.reportPreviousErrors
|
|
: true;
|
|
this.resourcePath = this.normalizePath(
|
|
params.resourcePath || process.resourcesPath
|
|
);
|
|
this.reportedErrors = [];
|
|
this.reportedAssertionFailures = [];
|
|
}
|
|
|
|
buildNotificationJSON(error, params) {
|
|
return {
|
|
apiKey: API_KEY,
|
|
notifier: {
|
|
name: 'Atom',
|
|
version: LIB_VERSION,
|
|
url: 'https://www.atom.io'
|
|
},
|
|
events: [
|
|
{
|
|
payloadVersion: '2',
|
|
exceptions: [this.buildExceptionJSON(error, params.projectRoot)],
|
|
severity: params.severity,
|
|
user: {
|
|
id: params.userId
|
|
},
|
|
app: {
|
|
version: params.appVersion,
|
|
releaseStage: params.releaseStage
|
|
},
|
|
device: {
|
|
osVersion: params.osVersion
|
|
},
|
|
metaData: error.metadata
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
buildExceptionJSON(error, projectRoot) {
|
|
return {
|
|
errorClass: error.constructor.name,
|
|
message: error.message,
|
|
stacktrace: this.buildStackTraceJSON(error, projectRoot)
|
|
};
|
|
}
|
|
|
|
buildStackTraceJSON(error, projectRoot) {
|
|
return this.parseStackTrace(error).map(callSite => {
|
|
return {
|
|
file: this.scrubPath(callSite.getFileName()),
|
|
method:
|
|
callSite.getMethodName() || callSite.getFunctionName() || 'none',
|
|
lineNumber: callSite.getLineNumber(),
|
|
columnNumber: callSite.getColumnNumber(),
|
|
inProject: !/node_modules/.test(callSite.getFileName())
|
|
};
|
|
});
|
|
}
|
|
|
|
normalizePath(pathToNormalize) {
|
|
return pathToNormalize
|
|
.replace('file:///', '') // Sometimes it's a uri
|
|
.replace(/\\/g, '/'); // Unify path separators across Win/macOS/Linux
|
|
}
|
|
|
|
scrubPath(pathToScrub) {
|
|
const absolutePath = this.normalizePath(pathToScrub);
|
|
|
|
if (this.isBundledFile(absolutePath)) {
|
|
return this.normalizePath(path.relative(this.resourcePath, absolutePath));
|
|
} else {
|
|
return absolutePath
|
|
.replace(this.normalizePath(fs.getHomeDirectory()), '~') // Remove users home dir
|
|
.replace(/.*(\/packages\/.*)/, '$1'); // Remove everything before app.asar or packages
|
|
}
|
|
}
|
|
|
|
getDefaultNotificationParams() {
|
|
return {
|
|
userId: atom.config.get('exception-reporting.userId'),
|
|
appVersion: atom.getVersion(),
|
|
releaseStage: this.getReleaseChannel(atom.getVersion()),
|
|
projectRoot: atom.getLoadSettings().resourcePath,
|
|
osVersion: `${os.platform()}-${os.arch()}-${os.release()}`
|
|
};
|
|
}
|
|
|
|
getReleaseChannel(version) {
|
|
return version.indexOf('beta') > -1
|
|
? 'beta'
|
|
: version.indexOf('dev') > -1
|
|
? 'dev'
|
|
: 'stable';
|
|
}
|
|
|
|
performRequest(json) {
|
|
this.request.call(null, 'https://notify.bugsnag.com', {
|
|
method: 'POST',
|
|
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
body: JSON.stringify(json)
|
|
});
|
|
}
|
|
|
|
shouldReport(error) {
|
|
if (this.alwaysReport) return true; // Used in specs
|
|
if (atom.config.get('core.telemetryConsent') !== 'limited') return false;
|
|
if (atom.inDevMode()) return false;
|
|
|
|
const topFrame = this.parseStackTrace(error)[0];
|
|
const fileName = topFrame ? topFrame.getFileName() : null;
|
|
return (
|
|
fileName &&
|
|
(this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
|
|
);
|
|
}
|
|
|
|
parseStackTrace(error) {
|
|
let callSites = StackTraceCache.get(error);
|
|
if (callSites) {
|
|
return callSites;
|
|
} else {
|
|
callSites = stackTrace.parse(error);
|
|
StackTraceCache.set(error, callSites);
|
|
return callSites;
|
|
}
|
|
}
|
|
|
|
requestPrivateMetadataConsent(error, message, reportFn) {
|
|
let notification, dismissSubscription;
|
|
|
|
function reportWithoutPrivateMetadata() {
|
|
if (dismissSubscription) {
|
|
dismissSubscription.dispose();
|
|
}
|
|
delete error.privateMetadata;
|
|
delete error.privateMetadataDescription;
|
|
reportFn(error);
|
|
if (notification) {
|
|
notification.dismiss();
|
|
}
|
|
}
|
|
|
|
function reportWithPrivateMetadata() {
|
|
if (error.metadata == null) {
|
|
error.metadata = {};
|
|
}
|
|
for (let key in error.privateMetadata) {
|
|
let value = error.privateMetadata[key];
|
|
error.metadata[key] = value;
|
|
}
|
|
reportWithoutPrivateMetadata();
|
|
}
|
|
|
|
const name = error.privateMetadataRequestName;
|
|
if (name != null) {
|
|
if (localStorage.getItem(`private-metadata-request:${name}`)) {
|
|
return reportWithoutPrivateMetadata(error);
|
|
} else {
|
|
localStorage.setItem(`private-metadata-request:${name}`, true);
|
|
}
|
|
}
|
|
|
|
notification = atom.notifications.addInfo(message, {
|
|
detail: error.privateMetadataDescription,
|
|
description:
|
|
'Are you willing to submit this information to a private server for debugging purposes?',
|
|
dismissable: true,
|
|
buttons: [
|
|
{
|
|
text: 'No',
|
|
onDidClick: reportWithoutPrivateMetadata
|
|
},
|
|
{
|
|
text: 'Yes, Submit for Debugging',
|
|
onDidClick: reportWithPrivateMetadata
|
|
}
|
|
]
|
|
});
|
|
|
|
dismissSubscription = notification.onDidDismiss(
|
|
reportWithoutPrivateMetadata
|
|
);
|
|
}
|
|
|
|
addPackageMetadata(error) {
|
|
let activePackages = atom.packages.getActivePackages();
|
|
const availablePackagePaths = atom.packages.getPackageDirPaths();
|
|
if (activePackages.length > 0) {
|
|
let userPackages = {};
|
|
let bundledPackages = {};
|
|
for (let pack of atom.packages.getActivePackages()) {
|
|
if (availablePackagePaths.includes(path.dirname(pack.path))) {
|
|
userPackages[pack.name] = pack.metadata.version;
|
|
} else {
|
|
bundledPackages[pack.name] = pack.metadata.version;
|
|
}
|
|
}
|
|
|
|
if (error.metadata == null) {
|
|
error.metadata = {};
|
|
}
|
|
error.metadata.bundledPackages = bundledPackages;
|
|
error.metadata.userPackages = userPackages;
|
|
}
|
|
}
|
|
|
|
addPreviousErrorsMetadata(error) {
|
|
if (!this.reportPreviousErrors) return;
|
|
if (!error.metadata) error.metadata = {};
|
|
error.metadata.previousErrors = this.reportedErrors.map(
|
|
error => error.message
|
|
);
|
|
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(
|
|
error => error.message
|
|
);
|
|
}
|
|
|
|
reportUncaughtException(error) {
|
|
if (!this.shouldReport(error)) return;
|
|
|
|
this.addPackageMetadata(error);
|
|
this.addPreviousErrorsMetadata(error);
|
|
|
|
if (
|
|
error.privateMetadata != null &&
|
|
error.privateMetadataDescription != null
|
|
) {
|
|
this.requestPrivateMetadataConsent(
|
|
error,
|
|
'The Atom team would like to collect the following information to resolve this error:',
|
|
error => this.reportUncaughtException(error)
|
|
);
|
|
return;
|
|
}
|
|
|
|
let params = this.getDefaultNotificationParams();
|
|
params.severity = 'error';
|
|
this.performRequest(this.buildNotificationJSON(error, params));
|
|
this.reportedErrors.push(error);
|
|
}
|
|
|
|
reportFailedAssertion(error) {
|
|
if (!this.shouldReport(error)) return;
|
|
|
|
this.addPackageMetadata(error);
|
|
this.addPreviousErrorsMetadata(error);
|
|
|
|
if (
|
|
error.privateMetadata != null &&
|
|
error.privateMetadataDescription != null
|
|
) {
|
|
this.requestPrivateMetadataConsent(
|
|
error,
|
|
'The Atom team would like to collect some information to resolve an unexpected condition:',
|
|
error => this.reportFailedAssertion(error)
|
|
);
|
|
return;
|
|
}
|
|
|
|
let params = this.getDefaultNotificationParams();
|
|
params.severity = 'warning';
|
|
this.performRequest(this.buildNotificationJSON(error, params));
|
|
this.reportedAssertionFailures.push(error);
|
|
}
|
|
|
|
// Used in specs
|
|
setRequestFunction(requestFunction) {
|
|
this.request = requestFunction;
|
|
}
|
|
|
|
isBundledFile(fileName) {
|
|
return this.normalizePath(fileName).indexOf(this.resourcePath) === 0;
|
|
}
|
|
|
|
isTeletypeFile(fileName) {
|
|
const teletypePath = atom.packages.resolvePackagePath('teletype');
|
|
return (
|
|
teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
|
|
);
|
|
}
|
|
}
|
|
|
|
Reporter.API_KEY = API_KEY;
|
|
Reporter.LIB_VERSION = LIB_VERSION;
|