Fix crash reporter
Summary: Changelog: Make crash reporter pick up and parse .ips crash reports Reviewed By: LukeDefeo Differential Revision: D38355089 fbshipit-source-id: dc1b46257ea12c2e67d8a9baab7f35a905be2204
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a998ecb956
commit
f132d95bee
@@ -200,12 +200,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||||||
{gutter ? (
|
{gutter ? (
|
||||||
<GutterWrapper position={position}>
|
<GutterWrapper position={position}>
|
||||||
{/* Stop propagating mousedown events to prevent SidebarInteractiveContainer from resizing whenever a user starts selecting text in a child */}
|
{/* Stop propagating mousedown events to prevent SidebarInteractiveContainer from resizing whenever a user starts selecting text in a child */}
|
||||||
<Layout.Container onMouseDown={(e) => e.stopPropagation()}>
|
<Layout.Container grow onMouseDown={(e) => e.stopPropagation()}>
|
||||||
{children}
|
{children}
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
</GutterWrapper>
|
</GutterWrapper>
|
||||||
) : (
|
) : (
|
||||||
<Layout.Container onMouseDown={(e) => e.stopPropagation()}>
|
<Layout.Container grow onMouseDown={(e) => e.stopPropagation()}>
|
||||||
{children}
|
{children}
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,15 +8,41 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parseIosCrash,
|
parseIosCrashLegacy,
|
||||||
parsePath,
|
parsePathLegacy,
|
||||||
shouldShowiOSCrashNotification,
|
shouldShowiOSCrashNotification,
|
||||||
} from '../iOSCrashUtils';
|
} from '../iOSCrashUtils';
|
||||||
|
|
||||||
|
const modernCrashReportPartial = `{"app_name":"Sample","timestamp":"2022-08-02 16:00:06.00 +0100","app_version":"1.0","slice_uuid":"6f0b8b0f-45d8-3a37-9f85-f1b07ccc121e","build_version":"1","platform":7,"bundleID":"com.facebook.flipper","share_with_app_devs":0,"is_first_party":0,"bug_type":"309","os_version":"macOS 12.4 (21F79)","incident_id":"DEE97E5A-409F-472F-96BC-E6B2D3FBB84B","name":"Sample"}
|
||||||
|
{
|
||||||
|
"uptime" : 560000,
|
||||||
|
"procLaunch" : "2022-08-02 16:00:04.3267 +0100",
|
||||||
|
"procRole" : "Foreground",
|
||||||
|
"version" : 2,
|
||||||
|
"userID" : 501,
|
||||||
|
"deployVersion" : 210,
|
||||||
|
"modelCode" : "MacPro7,1",
|
||||||
|
"procStartAbsTime" : 568274646258426,
|
||||||
|
"coalitionID" : 260443,
|
||||||
|
"osVersion" : {
|
||||||
|
"train" : "macOS 12.4",
|
||||||
|
"build" : "21F79",
|
||||||
|
"releaseType" : "User"
|
||||||
|
},
|
||||||
|
"captureTime" : "2022-08-02 16:00:06.4551 +0100",
|
||||||
|
"incident" : "DEE97E5A-409F-472F-96BC-E6B2D3FBB84B",
|
||||||
|
"bug_type" : "309",
|
||||||
|
"pid" : 59618,
|
||||||
|
"procExitAbsTime" : 568276773623039,
|
||||||
|
"cpuType" : "X86-64",
|
||||||
|
"procName" : "Sample",
|
||||||
|
"procPath" : "\/Users\/USER\/Library\/Developer\/CoreSimulator\/Devices\/543083BF-313B-422B-A817-377078C830AF\/data\/Containers\/Bundle\/Application\/22053C61-10A5-49AE-9B09-00B0B9EAC36B\/Sample.app\/Sample"
|
||||||
|
}`;
|
||||||
|
|
||||||
test('test the parsing of the date and crash info for the log which matches the predefined regex', () => {
|
test('test the parsing of the date and crash info for the log which matches the predefined regex', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa Date/Time: 2019-03-21 12:07:00.861 +0000 \n Blaa balaaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa Date/Time: 2019-03-21 12:07:00.861 +0000 \n Blaa balaaa';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('SIGSEGV');
|
expect(crash.reason).toEqual('SIGSEGV');
|
||||||
expect(crash.name).toEqual('SIGSEGV');
|
expect(crash.name).toEqual('SIGSEGV');
|
||||||
@@ -26,7 +52,7 @@ test('test the parsing of the date and crash info for the log which matches the
|
|||||||
test('test the parsing of the reason for crash when log matches the crash regex, but there is no mention of date', () => {
|
test('test the parsing of the reason for crash when log matches the crash regex, but there is no mention of date', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('SIGSEGV');
|
expect(crash.reason).toEqual('SIGSEGV');
|
||||||
expect(crash.name).toEqual('SIGSEGV');
|
expect(crash.name).toEqual('SIGSEGV');
|
||||||
@@ -34,7 +60,7 @@ test('test the parsing of the reason for crash when log matches the crash regex,
|
|||||||
|
|
||||||
test('test the parsing of the crash log when log does not match the predefined regex but is alphanumeric', () => {
|
test('test the parsing of the crash log when log does not match the predefined regex but is alphanumeric', () => {
|
||||||
const log = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaaa';
|
const log = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaaa';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Unknown');
|
expect(crash.reason).toEqual('Unknown');
|
||||||
expect(crash.name).toEqual('Unknown');
|
expect(crash.name).toEqual('Unknown');
|
||||||
@@ -43,14 +69,14 @@ test('test the parsing of the crash log when log does not match the predefined r
|
|||||||
test('test the parsing of the reason for crash when log does not match the predefined regex contains unicode character', () => {
|
test('test the parsing of the reason for crash when log does not match the predefined regex contains unicode character', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: 🍕🐬 \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: 🍕🐬 \n Blaa Blaa \n Blaa Blaa';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Unknown');
|
expect(crash.reason).toEqual('Unknown');
|
||||||
expect(crash.name).toEqual('Unknown');
|
expect(crash.name).toEqual('Unknown');
|
||||||
});
|
});
|
||||||
test('test the parsing of the reason for crash when log is empty', () => {
|
test('test the parsing of the reason for crash when log is empty', () => {
|
||||||
const log = '';
|
const log = '';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Unknown');
|
expect(crash.reason).toEqual('Unknown');
|
||||||
expect(crash.name).toEqual('Unknown');
|
expect(crash.name).toEqual('Unknown');
|
||||||
@@ -59,7 +85,7 @@ test('test the parsing of the reason for crash when log is empty', () => {
|
|||||||
test('test the parsing of the Android crash log with os being iOS', () => {
|
test('test the parsing of the Android crash log with os being iOS', () => {
|
||||||
const log =
|
const log =
|
||||||
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
||||||
const crash = parseIosCrash(log);
|
const crash = parseIosCrashLegacy(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Unknown');
|
expect(crash.reason).toEqual('Unknown');
|
||||||
expect(crash.name).toEqual('Unknown');
|
expect(crash.name).toEqual('Unknown');
|
||||||
@@ -68,32 +94,32 @@ test('test the parsing of the Android crash log with os being iOS', () => {
|
|||||||
test('test parsing of path when inputs are correct', () => {
|
test('test parsing of path when inputs are correct', () => {
|
||||||
const content =
|
const content =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName \n Blaa Blaa \n Blaa Blaa';
|
||||||
const id = parsePath(content);
|
const id = parsePathLegacy(content);
|
||||||
expect(id).toEqual('path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName');
|
expect(id).toEqual('path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName');
|
||||||
});
|
});
|
||||||
test('test parsing of path when path has special characters in it', () => {
|
test('test parsing of path when path has special characters in it', () => {
|
||||||
let content =
|
let content =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App Name.app/App Name \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App Name.app/App Name \n Blaa Blaa \n Blaa Blaa';
|
||||||
let id = parsePath(content);
|
let id = parsePathLegacy(content);
|
||||||
expect(id).toEqual(
|
expect(id).toEqual(
|
||||||
'path/to/simulator/TH1S-15DEV1CE-1D/App Name.app/App Name',
|
'path/to/simulator/TH1S-15DEV1CE-1D/App Name.app/App Name',
|
||||||
);
|
);
|
||||||
content =
|
content =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App_Name.app/App_Name \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App_Name.app/App_Name \n Blaa Blaa \n Blaa Blaa';
|
||||||
id = parsePath(content);
|
id = parsePathLegacy(content);
|
||||||
expect(id).toEqual(
|
expect(id).toEqual(
|
||||||
'path/to/simulator/TH1S-15DEV1CE-1D/App_Name.app/App_Name',
|
'path/to/simulator/TH1S-15DEV1CE-1D/App_Name.app/App_Name',
|
||||||
);
|
);
|
||||||
content =
|
content =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App%20Name.app/App%20Name \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/App%20Name.app/App%20Name \n Blaa Blaa \n Blaa Blaa';
|
||||||
id = parsePath(content);
|
id = parsePathLegacy(content);
|
||||||
expect(id).toEqual(
|
expect(id).toEqual(
|
||||||
'path/to/simulator/TH1S-15DEV1CE-1D/App%20Name.app/App%20Name',
|
'path/to/simulator/TH1S-15DEV1CE-1D/App%20Name.app/App%20Name',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('test parsing of path when a regex is not present', () => {
|
test('test parsing of path when a regex is not present', () => {
|
||||||
const content = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaa \n Blaa Blaa';
|
const content = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaa \n Blaa Blaa';
|
||||||
const id = parsePath(content);
|
const id = parsePathLegacy(content);
|
||||||
expect(id).toEqual(null);
|
expect(id).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,6 +129,7 @@ test('test shouldShowCrashNotification function for all correct inputs', () => {
|
|||||||
const shouldShowNotification = shouldShowiOSCrashNotification(
|
const shouldShowNotification = shouldShowiOSCrashNotification(
|
||||||
'TH1S-15DEV1CE-1D',
|
'TH1S-15DEV1CE-1D',
|
||||||
content,
|
content,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
expect(shouldShowNotification).toEqual(true);
|
expect(shouldShowNotification).toEqual(true);
|
||||||
});
|
});
|
||||||
@@ -112,6 +139,7 @@ test('test shouldShowiOSCrashNotification function for all correct inputs but in
|
|||||||
const shouldShowNotification = shouldShowiOSCrashNotification(
|
const shouldShowNotification = shouldShowiOSCrashNotification(
|
||||||
'TH1S-15DEV1CE-1D',
|
'TH1S-15DEV1CE-1D',
|
||||||
content,
|
content,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
expect(shouldShowNotification).toEqual(false);
|
expect(shouldShowNotification).toEqual(false);
|
||||||
});
|
});
|
||||||
@@ -121,6 +149,7 @@ test('test shouldShowiOSCrashNotification function for undefined device', () =>
|
|||||||
const shouldShowNotification = shouldShowiOSCrashNotification(
|
const shouldShowNotification = shouldShowiOSCrashNotification(
|
||||||
null as any,
|
null as any,
|
||||||
content,
|
content,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
expect(shouldShowNotification).toEqual(false);
|
expect(shouldShowNotification).toEqual(false);
|
||||||
});
|
});
|
||||||
@@ -136,12 +165,29 @@ test('only crashes from the correct device are picked up', () => {
|
|||||||
Responsible: SimulatorTrampoline [1246]
|
Responsible: SimulatorTrampoline [1246]
|
||||||
User ID: 501`;
|
User ID: 501`;
|
||||||
|
|
||||||
expect(shouldShowiOSCrashNotification(serial, crash)).toBe(true);
|
expect(shouldShowiOSCrashNotification(serial, crash, true)).toBe(true);
|
||||||
// wrong serial
|
// wrong serial
|
||||||
expect(
|
expect(
|
||||||
shouldShowiOSCrashNotification(
|
shouldShowiOSCrashNotification(
|
||||||
'XC9482A2-26A4-404F-A179-A9FB60B077F6',
|
'XC9482A2-26A4-404F-A179-A9FB60B077F6',
|
||||||
crash,
|
crash,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('modern crashes from the correct device are picked up', () => {
|
||||||
|
const serial = '543083BF-313B-422B-A817-377078C830AF';
|
||||||
|
|
||||||
|
expect(
|
||||||
|
shouldShowiOSCrashNotification(serial, modernCrashReportPartial, false),
|
||||||
|
).toBe(true);
|
||||||
|
// wrong serial
|
||||||
|
expect(
|
||||||
|
shouldShowiOSCrashNotification(
|
||||||
|
'XC9482A2-26A4-404F-A179-A9FB60B077F6',
|
||||||
|
modernCrashReportPartial,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import os from 'os';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {ServerDevice} from '../ServerDevice';
|
import {ServerDevice} from '../ServerDevice';
|
||||||
|
|
||||||
export function parseIosCrash(content: string) {
|
export function parseIosCrashLegacy(content: string) {
|
||||||
const regex = /Exception Type: *\w*/;
|
const regex = /Exception Type: *\w*/;
|
||||||
const arr = regex.exec(content);
|
const arr = regex.exec(content);
|
||||||
const exceptionString = arr ? arr[0] : '';
|
const exceptionString = arr ? arr[0] : '';
|
||||||
@@ -42,11 +42,28 @@ export function parseIosCrash(content: string) {
|
|||||||
return crash;
|
return crash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseIosCrashModern(content: string) {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
// Skip the first line of the .ips file as it is useless
|
||||||
|
const reportBodyString = lines.slice(1).join('\n');
|
||||||
|
const reportBody = JSON.parse(reportBodyString);
|
||||||
|
const exception = `${reportBody.exception.type} (${reportBody.exception.signal})`;
|
||||||
|
|
||||||
|
const crash: CrashLog = {
|
||||||
|
callstack: content,
|
||||||
|
name: exception,
|
||||||
|
reason: exception,
|
||||||
|
date: new Date(reportBody.captureTime).getTime(),
|
||||||
|
};
|
||||||
|
return crash;
|
||||||
|
}
|
||||||
|
|
||||||
export function shouldShowiOSCrashNotification(
|
export function shouldShowiOSCrashNotification(
|
||||||
serial: string,
|
serial: string,
|
||||||
content: string,
|
content: string,
|
||||||
|
legacy: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
const appPath = parsePath(content);
|
const appPath = legacy ? parsePathLegacy(content) : parsePathModern(content);
|
||||||
if (!appPath || !appPath.includes(serial)) {
|
if (!appPath || !appPath.includes(serial)) {
|
||||||
// Do not show notifications for the app which are not running on this device
|
// Do not show notifications for the app which are not running on this device
|
||||||
return false;
|
return false;
|
||||||
@@ -54,7 +71,7 @@ export function shouldShowiOSCrashNotification(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePath(content: string): string | null {
|
export function parsePathLegacy(content: string): string | null {
|
||||||
const regex = /(?<=.*Path: *)[^\n]*/;
|
const regex = /(?<=.*Path: *)[^\n]*/;
|
||||||
const arr = regex.exec(content);
|
const arr = regex.exec(content);
|
||||||
if (!arr || arr.length <= 0) {
|
if (!arr || arr.length <= 0) {
|
||||||
@@ -64,6 +81,14 @@ export function parsePath(content: string): string | null {
|
|||||||
return path.trim();
|
return path.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parsePathModern(content: string): string | null {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
// Skip the first line of the .ips file as it is useless
|
||||||
|
const reportBodyString = lines.slice(1).join('\n');
|
||||||
|
const reportBody = JSON.parse(reportBodyString);
|
||||||
|
return reportBody.procPath;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export class iOSCrashWatcher extends DeviceListener {
|
export class iOSCrashWatcher extends DeviceListener {
|
||||||
constructor(private readonly device: ServerDevice) {
|
constructor(private readonly device: ServerDevice) {
|
||||||
@@ -77,11 +102,12 @@ export class iOSCrashWatcher extends DeviceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const watcher = fs.watch(dir, async (_eventType, filename) => {
|
const watcher = fs.watch(dir, async (_eventType, filename) => {
|
||||||
// We just parse the crash logs with extension `.crash`
|
const checkFileExtensionLegacy = /.crash$/.exec(filename);
|
||||||
// TODO: Make it work on MacOS 12. ASAP!
|
const checkFileExtensionModern = /.ips$/.exec(filename);
|
||||||
// MacOS 12 does not create .crash reports, but uses new .ips files instead with different format.
|
if (
|
||||||
const checkFileExtension = /.crash$/.exec(filename);
|
!filename ||
|
||||||
if (!filename || !checkFileExtension) {
|
!(checkFileExtensionLegacy || checkFileExtensionModern)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filepath = path.join(dir, filename);
|
const filepath = path.join(dir, filename);
|
||||||
@@ -94,9 +120,17 @@ export class iOSCrashWatcher extends DeviceListener {
|
|||||||
console.warn('Failed to read crash file', err);
|
console.warn('Failed to read crash file', err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shouldShowiOSCrashNotification(this.device.serial, data)) {
|
if (
|
||||||
|
shouldShowiOSCrashNotification(
|
||||||
|
this.device.serial,
|
||||||
|
data,
|
||||||
|
!!checkFileExtensionLegacy,
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.device.flipperServer.emit('device-crash', {
|
this.device.flipperServer.emit('device-crash', {
|
||||||
crash: parseIosCrash(data),
|
crash: checkFileExtensionLegacy
|
||||||
|
? parseIosCrashLegacy(data)
|
||||||
|
: parseIosCrashModern(data),
|
||||||
serial: this.device.serial,
|
serial: this.device.serial,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user