1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Attach log to crash dump when the application crashes

This commit is contained in:
Laurent Cozic 2024-04-26 16:07:16 +01:00
parent 74bc9b36aa
commit c5dfa4c055
6 changed files with 81 additions and 48 deletions

View File

@ -390,6 +390,8 @@ class Application extends BaseApplication {
argv = await super.start(argv, startOptions); argv = await super.start(argv, startOptions);
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
await this.applySettingsSideEffects(); await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) { if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {

View File

@ -6,11 +6,14 @@ import { dirname, toSystemSlashes } from '@joplin/lib/path-utils';
import { fileUriToPath } from '@joplin/utils/url'; import { fileUriToPath } from '@joplin/utils/url';
import { urlDecode } from '@joplin/lib/string-utils'; import { urlDecode } from '@joplin/lib/string-utils';
import * as Sentry from '@sentry/electron/main'; import * as Sentry from '@sentry/electron/main';
import { ErrorEvent } from '@sentry/types/types';
import { homedir } from 'os'; import { homedir } from 'os';
import { msleep } from '@joplin/utils/time'; import { msleep } from '@joplin/utils/time';
import { pathExists, writeFileSync } from 'fs-extra'; import { pathExists, pathExistsSync, writeFileSync } from 'fs-extra';
import { extname, normalize } from 'path'; import { extname, normalize } from 'path';
import isSafeToOpen from './utils/isSafeToOpen'; import isSafeToOpen from './utils/isSafeToOpen';
import { closeSync, openSync, readSync, statSync } from 'fs';
import { KB } from '@joplin/utils/bytes';
interface LastSelectedPath { interface LastSelectedPath {
file: string; file: string;
@ -38,6 +41,7 @@ export class Bridge {
private rootProfileDir_: string; private rootProfileDir_: string;
private appName_: string; private appName_: string;
private appId_: string; private appId_: string;
private logFilePath_ = '';
private extraAllowedExtensions_: string[] = []; private extraAllowedExtensions_: string[] = [];
private onAllowedExtensionsChangeListener_: OnAllowedExtensionsChange = ()=>{}; private onAllowedExtensionsChangeListener_: OnAllowedExtensionsChange = ()=>{};
@ -56,12 +60,53 @@ export class Bridge {
this.sentryInit(); this.sentryInit();
} }
public setLogFilePath(v: string) {
this.logFilePath_ = v;
}
private sentryInit() { private sentryInit() {
const getLogLines = () => {
try {
if (!this.logFilePath_ || !pathExistsSync(this.logFilePath_)) return '';
const { size } = statSync(this.logFilePath_);
if (!size) return '';
const bytesToRead = Math.min(size, 100 * KB);
const handle = openSync(this.logFilePath_, 'r');
const position = size - bytesToRead;
const buffer = Buffer.alloc(bytesToRead);
readSync(handle, buffer, 0, bytesToRead, position);
closeSync(handle);
return buffer.toString('utf-8');
} catch (error) {
// Can't do anything in this context
return '';
}
};
const getLogAttachment = () => {
const lines = getLogLines();
if (!lines) return null;
return { filename: 'joplin-log.txt', data: lines };
};
const options: Sentry.ElectronMainOptions = { const options: Sentry.ElectronMainOptions = {
beforeSend: event => { beforeSend: (event, hint) => {
try { try {
const logAttachment = getLogAttachment();
if (logAttachment) hint.attachments = [logAttachment];
const date = (new Date()).toISOString().replace(/[:-]/g, '').split('.')[0]; const date = (new Date()).toISOString().replace(/[:-]/g, '').split('.')[0];
writeFileSync(`${homedir()}/joplin_crash_dump_${date}.json`, JSON.stringify(event, null, '\t'), 'utf-8');
interface ErrorEventWithLog extends ErrorEvent {
log: string[];
}
const errorEventWithLog: ErrorEventWithLog = {
...event,
log: logAttachment ? logAttachment.data.trim().split('\n') : [],
};
writeFileSync(`${homedir()}/joplin_crash_dump_${date}.json`, JSON.stringify(errorEventWithLog, null, '\t'), 'utf-8');
} catch (error) { } catch (error) {
// Ignore the error since we can't handle it here // Ignore the error since we can't handle it here
} }

View File

@ -54,36 +54,33 @@ describe('Logger', () => {
'', '',
].join('\n')); ].join('\n'));
// Shouldn't have kept any line, since keptLineCount was not set
expect(logger.keptLines).toEqual([]);
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should keep the last lines', async () => { // it('should keep the last lines', async () => {
jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); // jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
const logger = createLogger(); // const logger = createLogger();
logger.keptLineCount = 2; // logger.keptLineCount = 2;
logger.info('one'); // logger.info('one');
logger.info('two'); // logger.info('two');
logger.info('three'); // logger.info('three');
await logger.waitForFileWritesToComplete_(); // await logger.waitForFileWritesToComplete_();
expect(await getLogContent()).toBe([ // expect(await getLogContent()).toBe([
'2020-01-01 00:00:00: testing: one', // '2020-01-01 00:00:00: testing: one',
'2020-01-01 00:00:00: testing: two', // '2020-01-01 00:00:00: testing: two',
'2020-01-01 00:00:00: testing: three', // '2020-01-01 00:00:00: testing: three',
'', // '',
].join('\n')); // ].join('\n'));
expect(logger.keptLines).toEqual([ // expect(logger.keptLines).toEqual([
'2020-01-01 00:00:00: testing: two', // '2020-01-01 00:00:00: testing: two',
'2020-01-01 00:00:00: testing: three', // '2020-01-01 00:00:00: testing: three',
]); // ]);
jest.useRealTimers(); // jest.useRealTimers();
}); // });
}); });

View File

@ -78,8 +78,6 @@ class Logger {
private level_: LogLevel = LogLevel.Info; private level_: LogLevel = LogLevel.Info;
private lastDbCleanup_: number = Date.now(); private lastDbCleanup_: number = Date.now();
private enabled_ = true; private enabled_ = true;
private keptLineCount_ = 0;
private keptLines_: string[] = [];
public static fsDriver() { public static fsDriver() {
if (!Logger.fsDriver_) Logger.fsDriver_ = dummyFsDriver; if (!Logger.fsDriver_) Logger.fsDriver_ = dummyFsDriver;
@ -94,18 +92,6 @@ class Logger {
this.enabled_ = v; this.enabled_ = v;
} }
public get keptLineCount() {
return this.keptLineCount_;
}
public set keptLineCount(v: number) {
this.keptLineCount_ = v;
}
public get keptLines() {
return this.keptLines_;
}
public status(): string { public status(): string {
const output: string[] = []; const output: string[] = [];
output.push(`Enabled: ${this.enabled}`); output.push(`Enabled: ${this.enabled}`);
@ -118,6 +104,12 @@ class Logger {
this.globalLogger_ = logger; this.globalLogger_ = logger;
} }
public logFilePath() {
const fileTarget = this.targets().find(t => t.type === TargetType.File);
if (!fileTarget) return '';
return fileTarget.path || '';
}
public static get globalLogger(): Logger { public static get globalLogger(): Logger {
if (!this.globalLogger_) { if (!this.globalLogger_) {
// The global logger normally is initialized early, so we shouldn't // The global logger normally is initialized early, so we shouldn't
@ -361,13 +353,6 @@ class Logger {
target.database.transactionExecBatch(queries); target.database.transactionExecBatch(queries);
} }
} }
if (this.keptLineCount) {
if (!logLine) logLine = this.logInfoToString(level, prefix, ...object);
this.keptLines_.push(logLine);
const toDelete = this.keptLines_.length - this.keptLineCount;
if (toDelete >= 0) this.keptLines_.splice(0, toDelete);
}
} }
// For tests // For tests

View File

@ -1,4 +1,7 @@
// eslint-disable-next-line import/prefer-default-export export const KB = 1024;
export const MB = KB * KB;
export const GB = KB * MB;
export const bytesToHuman = (bytes: number) => { export const bytesToHuman = (bytes: number) => {
const units = ['Bytes', 'KB', 'MB', 'GB']; const units = ['Bytes', 'KB', 'MB', 'GB'];
let unitIndex = 0; let unitIndex = 0;

View File

@ -5,6 +5,7 @@
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils", "repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
"./bytes": "./dist/bytes.js",
"./dom": "./dist/dom.js", "./dom": "./dist/dom.js",
"./env": "./dist/env.js", "./env": "./dist/env.js",
"./object": "./dist/object.js", "./object": "./dist/object.js",