Detect startup after recent crashes
This commit is contained in:
parent
02a732c511
commit
91f1b62bc7
23 changed files with 650 additions and 101 deletions
127
app/crashReports.ts
Normal file
127
app/crashReports.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { app, clipboard, crashReporter, ipcMain as ipc } from 'electron';
|
||||
import { realpath, readdir, readFile, unlink } from 'fs-extra';
|
||||
import { basename, join } from 'path';
|
||||
|
||||
import type { LoggerType } from '../ts/types/Logging';
|
||||
import * as Errors from '../ts/types/errors';
|
||||
import { isProduction } from '../ts/util/version';
|
||||
import { upload as uploadDebugLog } from '../ts/logging/uploadDebugLog';
|
||||
import { SignalService as Proto } from '../ts/protobuf';
|
||||
|
||||
async function getPendingDumps(): Promise<ReadonlyArray<string>> {
|
||||
const crashDumpsPath = await realpath(app.getPath('crashDumps'));
|
||||
const pendingDir = join(crashDumpsPath, 'pending');
|
||||
const files = await readdir(pendingDir);
|
||||
|
||||
return files.map(file => join(pendingDir, file));
|
||||
}
|
||||
|
||||
async function eraseDumps(
|
||||
logger: LoggerType,
|
||||
files: ReadonlyArray<string>
|
||||
): Promise<void> {
|
||||
logger.warn(`crashReports: erasing ${files.length} pending dumps`);
|
||||
await Promise.all(
|
||||
files.map(async fullPath => {
|
||||
try {
|
||||
await unlink(fullPath);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`crashReports: failed to unlink crash report ${fullPath} due to error`,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function setup(getLogger: () => LoggerType): void {
|
||||
const isEnabled = !isProduction(app.getVersion());
|
||||
|
||||
if (isEnabled) {
|
||||
getLogger().info('crashReporter: enabled');
|
||||
crashReporter.start({ uploadToServer: false });
|
||||
}
|
||||
|
||||
ipc.handle('crash-reports:get-count', async () => {
|
||||
if (!isEnabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const pendingDumps = await getPendingDumps();
|
||||
if (pendingDumps.length !== 0) {
|
||||
getLogger().warn(
|
||||
`crashReports: ${pendingDumps.length} pending dumps found`
|
||||
);
|
||||
}
|
||||
return pendingDumps.length;
|
||||
});
|
||||
|
||||
ipc.handle('crash-reports:upload', async () => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingDumps = await getPendingDumps();
|
||||
if (pendingDumps.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logger = getLogger();
|
||||
logger.warn(`crashReports: uploading ${pendingDumps.length} dumps`);
|
||||
|
||||
const maybeDumps = await Promise.all(
|
||||
pendingDumps.map(async fullPath => {
|
||||
try {
|
||||
return {
|
||||
filename: basename(fullPath),
|
||||
content: await readFile(fullPath),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`crashReports: failed to read crash report ${fullPath} due to error`,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const content = Proto.CrashReportList.encode({
|
||||
reports: maybeDumps.filter(
|
||||
(dump): dump is { filename: string; content: Buffer } => {
|
||||
return dump !== undefined;
|
||||
}
|
||||
),
|
||||
}).finish();
|
||||
|
||||
try {
|
||||
const url = await uploadDebugLog({
|
||||
content,
|
||||
appVersion: app.getVersion(),
|
||||
logger,
|
||||
extension: 'dmp',
|
||||
contentType: 'application/octet-stream',
|
||||
compress: false,
|
||||
});
|
||||
|
||||
logger.info('crashReports: upload complete');
|
||||
clipboard.writeText(url);
|
||||
} finally {
|
||||
await eraseDumps(logger, pendingDumps);
|
||||
}
|
||||
});
|
||||
|
||||
ipc.handle('crash-reports:erase', async () => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingDumps = await getPendingDumps();
|
||||
|
||||
await eraseDumps(getLogger(), pendingDumps);
|
||||
});
|
||||
}
|
11
app/main.ts
11
app/main.ts
|
@ -7,7 +7,6 @@ import * as os from 'os';
|
|||
import { chmod, realpath, writeFile } from 'fs-extra';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
import pify from 'pify';
|
||||
import normalizePath from 'normalize-path';
|
||||
import fastGlob from 'fast-glob';
|
||||
import PQueue from 'p-queue';
|
||||
|
@ -30,6 +29,7 @@ import { z } from 'zod';
|
|||
|
||||
import packageJson from '../package.json';
|
||||
import * as GlobalErrors from './global_errors';
|
||||
import { setup as setupCrashReports } from './crashReports';
|
||||
import { setup as setupSpellChecker } from './spell_check';
|
||||
import { redactAll, addSensitivePath } from '../ts/util/privacy';
|
||||
import { strictAssert } from '../ts/util/assert';
|
||||
|
@ -100,7 +100,6 @@ import { load as loadLocale } from './locale';
|
|||
import type { LoggerType } from '../ts/types/Logging';
|
||||
|
||||
const animationSettings = systemPreferences.getAnimationSettings();
|
||||
const getRealPath = pify(realpath);
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
|
@ -1402,10 +1401,12 @@ function getAppLocale(): string {
|
|||
// Some APIs can only be used after this event occurs.
|
||||
let ready = false;
|
||||
app.on('ready', async () => {
|
||||
const userDataPath = await getRealPath(app.getPath('userData'));
|
||||
const userDataPath = await realpath(app.getPath('userData'));
|
||||
|
||||
logger = await logging.initialize(getMainWindow);
|
||||
|
||||
setupCrashReports(getLogger);
|
||||
|
||||
if (!locale) {
|
||||
const appLocale = getAppLocale();
|
||||
locale = loadLocale({ appLocale, logger });
|
||||
|
@ -1447,7 +1448,7 @@ app.on('ready', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
const installPath = await getRealPath(app.getAppPath());
|
||||
const installPath = await realpath(app.getAppPath());
|
||||
|
||||
addSensitivePath(userDataPath);
|
||||
|
||||
|
@ -2081,7 +2082,7 @@ async function ensureFilePermissions(onlyFiles?: Array<string>) {
|
|||
getLogger().info('Begin ensuring permissions');
|
||||
|
||||
const start = Date.now();
|
||||
const userDataPath = await getRealPath(app.getPath('userData'));
|
||||
const userDataPath = await realpath(app.getPath('userData'));
|
||||
// fast-glob uses `/` for all platforms
|
||||
const userDataGlob = normalizePath(join(userDataPath, '**', '*'));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue