mirror of
https://github.com/atom/atom.git
synced 2026-02-08 05:35:04 -05:00
268 lines
8.2 KiB
JavaScript
268 lines
8.2 KiB
JavaScript
/** @babel */
|
|
|
|
import _ from 'underscore-plus'
|
|
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
|