зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1293656 - Store all crash annotations in the crash manager's database and filter out privacy-sensitive ones when sending a crash ping r=bsmedberg
This commit is contained in:
Родитель
6cc37ab472
Коммит
d55bb9094c
|
@ -39,6 +39,36 @@ function dateToDays(date) {
|
|||
return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the string stored in the specified field as JSON and then remove the
|
||||
* field from the object. The string might also be returned without parsing.
|
||||
*
|
||||
* @param obj {Object} The object holding the field
|
||||
* @param field {String} The name of the field to be parsed and removed
|
||||
* @param [parseAsJson=true] {Boolean} If true parse the field's contents as if
|
||||
* it were JSON code, otherwise return the rew string.
|
||||
*
|
||||
* @returns {Object|String} the parsed object or the raw string
|
||||
*/
|
||||
function parseAndRemoveField(obj, field, parseAsJson = true) {
|
||||
let value = null;
|
||||
|
||||
if (field in obj) {
|
||||
if (!parseAsJson) {
|
||||
value = obj[field];
|
||||
} else {
|
||||
try {
|
||||
value = JSON.parse(obj[field]);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
delete obj[field];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A gateway to crash-related data.
|
||||
|
@ -174,6 +204,41 @@ this.CrashManager.prototype = Object.freeze({
|
|||
// The type of event is unknown.
|
||||
EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
|
||||
|
||||
// A whitelist of crash annotations which do not contain sensitive data
|
||||
// and are saved in the crash record and sent with Firefox Health Report.
|
||||
ANNOTATION_WHITELIST: [
|
||||
"AsyncShutdownTimeout",
|
||||
"BuildID",
|
||||
"ProductID",
|
||||
"ProductName",
|
||||
"ReleaseChannel",
|
||||
"SecondsSinceLastCrash",
|
||||
"ShutdownProgress",
|
||||
"StartupCrash",
|
||||
"TelemetryEnvironment",
|
||||
"Version",
|
||||
// The following entries are not normal annotations that can be found in
|
||||
// the .extra file but are included in the crash record/FHR:
|
||||
"AvailablePageFile",
|
||||
"AvailablePhysicalMemory",
|
||||
"AvailableVirtualMemory",
|
||||
"BlockedDllList",
|
||||
"BlocklistInitFailed",
|
||||
"ContainsMemoryReport",
|
||||
"CrashTime",
|
||||
"EventLoopNestingLevel",
|
||||
"IsGarbageCollecting",
|
||||
"MozCrashReason",
|
||||
"OOMAllocationSize",
|
||||
"SystemMemoryUsePercentage",
|
||||
"TextureUsage",
|
||||
"TotalPageFile",
|
||||
"TotalPhysicalMemory",
|
||||
"TotalVirtualMemory",
|
||||
"UptimeTS",
|
||||
"User32BeforeBlocklist",
|
||||
],
|
||||
|
||||
/**
|
||||
* Obtain a list of all dumps pending upload.
|
||||
*
|
||||
|
@ -544,6 +609,51 @@ this.CrashManager.prototype = Object.freeze({
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
_filterAnnotations: function (annotations) {
|
||||
let filteredAnnotations = {};
|
||||
|
||||
for (let line in annotations) {
|
||||
if (this.ANNOTATION_WHITELIST.includes(line)) {
|
||||
filteredAnnotations[line] = annotations[line];
|
||||
}
|
||||
}
|
||||
|
||||
return filteredAnnotations;
|
||||
},
|
||||
|
||||
_sendCrashPing: function (crashId, type, date, metadata) {
|
||||
// If we have a saved environment, use it. Otherwise report
|
||||
// the current environment.
|
||||
let reportMeta = Cu.cloneInto(metadata, myScope);
|
||||
let crashEnvironment = parseAndRemoveField(reportMeta,
|
||||
"TelemetryEnvironment");
|
||||
let sessionId = parseAndRemoveField(reportMeta, "TelemetrySessionId",
|
||||
/* parseAsJson */ false);
|
||||
let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
|
||||
|
||||
// Filter the remaining annotations to remove privacy-sensitive ones
|
||||
reportMeta = this._filterAnnotations(reportMeta);
|
||||
|
||||
TelemetryController.submitExternalPing("crash",
|
||||
{
|
||||
version: 1,
|
||||
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
|
||||
sessionId: sessionId,
|
||||
crashId: crashId,
|
||||
processType: type,
|
||||
stackTraces: stackTraces,
|
||||
metadata: reportMeta,
|
||||
hasCrashEnvironment: (crashEnvironment !== null),
|
||||
},
|
||||
{
|
||||
retentionDays: 180,
|
||||
addClientId: true,
|
||||
addEnvironment: true,
|
||||
overrideEnvironment: crashEnvironment,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_handleEventFilePayload: function(store, entry, type, date, payload) {
|
||||
// The payload types and formats are documented in docs/crash-events.rst.
|
||||
// Do not change the format of an existing type. Instead, invent a new
|
||||
|
@ -565,48 +675,7 @@ this.CrashManager.prototype = Object.freeze({
|
|||
store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
|
||||
crashID, date, metadata);
|
||||
|
||||
// If we have a saved environment, use it. Otherwise report
|
||||
// the current environment.
|
||||
let crashEnvironment = null;
|
||||
let sessionId = null;
|
||||
let stackTraces = null;
|
||||
let reportMeta = Cu.cloneInto(metadata, myScope);
|
||||
if ('TelemetryEnvironment' in reportMeta) {
|
||||
try {
|
||||
crashEnvironment = JSON.parse(reportMeta.TelemetryEnvironment);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
delete reportMeta.TelemetryEnvironment;
|
||||
}
|
||||
if ('TelemetrySessionId' in reportMeta) {
|
||||
sessionId = reportMeta.TelemetrySessionId;
|
||||
delete reportMeta.TelemetrySessionId;
|
||||
}
|
||||
if ('StackTraces' in reportMeta) {
|
||||
try {
|
||||
stackTraces = JSON.parse(reportMeta.StackTraces);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
delete reportMeta.StackTraces;
|
||||
}
|
||||
TelemetryController.submitExternalPing("crash",
|
||||
{
|
||||
version: 1,
|
||||
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
|
||||
sessionId: sessionId,
|
||||
crashId: entry.id,
|
||||
stackTraces: stackTraces,
|
||||
metadata: reportMeta,
|
||||
hasCrashEnvironment: (crashEnvironment !== null),
|
||||
},
|
||||
{
|
||||
retentionDays: 180,
|
||||
addClientId: true,
|
||||
addEnvironment: true,
|
||||
overrideEnvironment: crashEnvironment,
|
||||
});
|
||||
this._sendCrashPing(crashID, this.PROCESS_TYPE_MAIN, date, metadata);
|
||||
break;
|
||||
|
||||
case "crash.submission.1":
|
||||
|
|
|
@ -15,6 +15,12 @@ From JavaScript, the service can be accessed via::
|
|||
That will give you an instance of ``CrashManager`` from ``CrashManager.jsm``.
|
||||
From there, you can access and manipulate crash data.
|
||||
|
||||
The crash manager stores statistical information about crashes as well as
|
||||
detailed information for both browser and content crashes. The crash manager
|
||||
automatically detects new browser crashes at startup by scanning for
|
||||
:ref:`crash-events`. Content process crash information on the other hand is
|
||||
provided externally.
|
||||
|
||||
Other Documents
|
||||
===============
|
||||
|
||||
|
|
|
@ -209,11 +209,15 @@ add_task(function* test_schedule_maintenance() {
|
|||
Assert.equal(crashes[0].id, "id1");
|
||||
});
|
||||
|
||||
const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
|
||||
const productName = "Firefox";
|
||||
const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
|
||||
|
||||
add_task(function* test_main_crash_event_file() {
|
||||
let ac = new TelemetryArchiveTesting.Checker();
|
||||
yield ac.promiseInit();
|
||||
let theEnvironment = TelemetryEnvironment.currentEnvironment;
|
||||
let sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
|
||||
const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
|
||||
let stackTraces = { status: "OK" };
|
||||
|
||||
// To test proper escaping, add data to the environment with an embedded
|
||||
|
@ -221,36 +225,43 @@ add_task(function* test_main_crash_event_file() {
|
|||
theEnvironment.testValue = "MyValue\"";
|
||||
|
||||
let m = yield getManager();
|
||||
const fileContent = "id1\nk1=v1\nk2=v2\n" +
|
||||
const fileContent = crashId + "\n" +
|
||||
"ProductName=" + productName + "\n" +
|
||||
"ProductID=" + productId + "\n" +
|
||||
"TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
|
||||
"TelemetrySessionId=" + sessionId + "\n" +
|
||||
"StackTraces=" + JSON.stringify(stackTraces) + "\n";
|
||||
"StackTraces=" + JSON.stringify(stackTraces) + "\n" +
|
||||
"ThisShouldNot=end-up-in-the-ping\n";
|
||||
|
||||
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, fileContent);
|
||||
yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
|
||||
let count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 1);
|
||||
|
||||
let crashes = yield m.getCrashes();
|
||||
Assert.equal(crashes.length, 1);
|
||||
Assert.equal(crashes[0].id, "id1");
|
||||
Assert.equal(crashes[0].id, crashId);
|
||||
Assert.equal(crashes[0].type, "main-crash");
|
||||
Assert.equal(crashes[0].metadata.k1, "v1");
|
||||
Assert.equal(crashes[0].metadata.k2, "v2");
|
||||
Assert.equal(crashes[0].metadata.ProductName, productName);
|
||||
Assert.equal(crashes[0].metadata.ProductID, productId);
|
||||
Assert.ok(crashes[0].metadata.TelemetryEnvironment);
|
||||
Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 5);
|
||||
Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 6);
|
||||
Assert.equal(crashes[0].metadata.TelemetrySessionId, sessionId);
|
||||
Assert.ok(crashes[0].metadata.StackTraces);
|
||||
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
|
||||
|
||||
let found = yield ac.promiseFindPing("crash", [
|
||||
[["payload", "hasCrashEnvironment"], true],
|
||||
[["payload", "metadata", "k1"], "v1"],
|
||||
[["payload", "crashId"], "1"],
|
||||
[["payload", "metadata", "ProductName"], productName],
|
||||
[["payload", "metadata", "ProductID"], productId],
|
||||
[["payload", "crashId"], crashId],
|
||||
[["payload", "stackTraces", "status"], "OK"],
|
||||
[["payload", "sessionId"], sessionId],
|
||||
]);
|
||||
Assert.ok(found, "Telemetry ping submitted for found crash");
|
||||
Assert.deepEqual(found.environment, theEnvironment, "The saved environment should be present");
|
||||
Assert.deepEqual(found.environment, theEnvironment,
|
||||
"The saved environment should be present");
|
||||
Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
|
||||
"Non-whitelisted fields should be filtered out");
|
||||
|
||||
count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 0);
|
||||
|
@ -259,22 +270,29 @@ add_task(function* test_main_crash_event_file() {
|
|||
add_task(function* test_main_crash_event_file_noenv() {
|
||||
let ac = new TelemetryArchiveTesting.Checker();
|
||||
yield ac.promiseInit();
|
||||
const fileContent = crashId + "\n" +
|
||||
"ProductName=" + productName + "\n" +
|
||||
"ProductID=" + productId + "\n";
|
||||
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v3\nk2=v2");
|
||||
yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
|
||||
let count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 1);
|
||||
|
||||
let crashes = yield m.getCrashes();
|
||||
Assert.equal(crashes.length, 1);
|
||||
Assert.equal(crashes[0].id, "id1");
|
||||
Assert.equal(crashes[0].id, crashId);
|
||||
Assert.equal(crashes[0].type, "main-crash");
|
||||
Assert.deepEqual(crashes[0].metadata, { k1: "v3", k2: "v2"});
|
||||
Assert.deepEqual(crashes[0].metadata, {
|
||||
ProductName: productName,
|
||||
ProductID: productId
|
||||
});
|
||||
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
|
||||
|
||||
let found = yield ac.promiseFindPing("crash", [
|
||||
[["payload", "hasCrashEnvironment"], false],
|
||||
[["payload", "metadata", "k1"], "v3"],
|
||||
[["payload", "metadata", "ProductName"], productName],
|
||||
[["payload", "metadata", "ProductID"], productId],
|
||||
]);
|
||||
Assert.ok(found, "Telemetry ping submitted for found crash");
|
||||
Assert.ok(found.environment, "There is an environment");
|
||||
|
|
|
@ -183,34 +183,6 @@ static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra");
|
|||
static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz");
|
||||
static xpstring *defaultMemoryReportPath = nullptr;
|
||||
|
||||
// A whitelist of crash annotations which do not contain sensitive data
|
||||
// and are saved in the crash record and sent with Firefox Health Report.
|
||||
static char const * const kCrashEventAnnotations[] = {
|
||||
"AsyncShutdownTimeout",
|
||||
"BuildID",
|
||||
"ProductID",
|
||||
"ProductName",
|
||||
"ReleaseChannel",
|
||||
"SecondsSinceLastCrash",
|
||||
"ShutdownProgress",
|
||||
"StartupCrash",
|
||||
"TelemetryEnvironment",
|
||||
"Version",
|
||||
// The following entries are not normal annotations but are included
|
||||
// in the crash record/FHR:
|
||||
// "ContainsMemoryReport"
|
||||
// "EventLoopNestingLevel"
|
||||
// "IsGarbageCollecting"
|
||||
// "AvailablePageFile"
|
||||
// "AvailableVirtualMemory"
|
||||
// "SystemMemoryUsePercentage"
|
||||
// "OOMAllocationSize"
|
||||
// "TotalPageFile"
|
||||
// "TotalPhysicalMemory"
|
||||
// "TotalVirtualMemory"
|
||||
// "MozCrashReason"
|
||||
};
|
||||
|
||||
static const char kCrashMainID[] = "crash.main.2\n";
|
||||
|
||||
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
|
||||
|
@ -2163,17 +2135,6 @@ static void ReplaceChar(nsCString& str, const nsACString& character,
|
|||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
IsInWhitelist(const nsACString& key)
|
||||
{
|
||||
for (size_t i = 0; i < ArrayLength(kCrashEventAnnotations); ++i) {
|
||||
if (key.EqualsASCII(kCrashEventAnnotations[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function is miscompiled with MSVC 2005/2008 when PGO is on.
|
||||
#ifdef _MSC_VER
|
||||
#pragma optimize("", off)
|
||||
|
@ -2310,11 +2271,9 @@ nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data)
|
|||
nsAutoCString line = key + kEquals + entry + kNewline;
|
||||
|
||||
crashReporterAPIData->Append(line);
|
||||
if (IsInWhitelist(key)) {
|
||||
crashEventAPIData->Append(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче