You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Attach log to crash dump when the application crashes
This commit is contained in:
		| @@ -390,6 +390,8 @@ class Application extends BaseApplication { | ||||
|  | ||||
| 		argv = await super.start(argv, startOptions); | ||||
|  | ||||
| 		bridge().setLogFilePath(Logger.globalLogger.logFilePath()); | ||||
|  | ||||
| 		await this.applySettingsSideEffects(); | ||||
|  | ||||
| 		if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) { | ||||
|   | ||||
| @@ -6,11 +6,14 @@ import { dirname, toSystemSlashes } from '@joplin/lib/path-utils'; | ||||
| import { fileUriToPath } from '@joplin/utils/url'; | ||||
| import { urlDecode } from '@joplin/lib/string-utils'; | ||||
| import * as Sentry from '@sentry/electron/main'; | ||||
| import { ErrorEvent } from '@sentry/types/types'; | ||||
| import { homedir } from 'os'; | ||||
| 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 isSafeToOpen from './utils/isSafeToOpen'; | ||||
| import { closeSync, openSync, readSync, statSync } from 'fs'; | ||||
| import { KB } from '@joplin/utils/bytes'; | ||||
|  | ||||
| interface LastSelectedPath { | ||||
| 	file: string; | ||||
| @@ -38,6 +41,7 @@ export class Bridge { | ||||
| 	private rootProfileDir_: string; | ||||
| 	private appName_: string; | ||||
| 	private appId_: string; | ||||
| 	private logFilePath_ = ''; | ||||
|  | ||||
| 	private extraAllowedExtensions_: string[] = []; | ||||
| 	private onAllowedExtensionsChangeListener_: OnAllowedExtensionsChange = ()=>{}; | ||||
| @@ -56,12 +60,53 @@ export class Bridge { | ||||
| 		this.sentryInit(); | ||||
| 	} | ||||
|  | ||||
| 	public setLogFilePath(v: string) { | ||||
| 		this.logFilePath_ = v; | ||||
| 	} | ||||
|  | ||||
| 	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 = { | ||||
| 			beforeSend: event => { | ||||
| 			beforeSend: (event, hint) => { | ||||
| 				try { | ||||
| 					const logAttachment = getLogAttachment(); | ||||
| 					if (logAttachment) hint.attachments = [logAttachment]; | ||||
| 					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) { | ||||
| 					// Ignore the error since we can't handle it here | ||||
| 				} | ||||
|   | ||||
| @@ -54,36 +54,33 @@ describe('Logger', () => { | ||||
| 			'', | ||||
| 		].join('\n')); | ||||
|  | ||||
| 		// Shouldn't have kept any line, since keptLineCount was not set | ||||
| 		expect(logger.keptLines).toEqual([]); | ||||
|  | ||||
| 		jest.useRealTimers(); | ||||
| 	}); | ||||
|  | ||||
| 	it('should keep the last lines', async () => { | ||||
| 		jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); | ||||
| 	// it('should keep the last lines', async () => { | ||||
| 	// 	jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); | ||||
|  | ||||
| 		const logger = createLogger(); | ||||
| 		logger.keptLineCount = 2; | ||||
| 		logger.info('one'); | ||||
| 		logger.info('two'); | ||||
| 		logger.info('three'); | ||||
| 	// 	const logger = createLogger(); | ||||
| 	// 	logger.keptLineCount = 2; | ||||
| 	// 	logger.info('one'); | ||||
| 	// 	logger.info('two'); | ||||
| 	// 	logger.info('three'); | ||||
|  | ||||
| 		await logger.waitForFileWritesToComplete_(); | ||||
| 	// 	await logger.waitForFileWritesToComplete_(); | ||||
|  | ||||
| 		expect(await getLogContent()).toBe([ | ||||
| 			'2020-01-01 00:00:00: testing: one', | ||||
| 			'2020-01-01 00:00:00: testing: two', | ||||
| 			'2020-01-01 00:00:00: testing: three', | ||||
| 			'', | ||||
| 		].join('\n')); | ||||
| 	// 	expect(await getLogContent()).toBe([ | ||||
| 	// 		'2020-01-01 00:00:00: testing: one', | ||||
| 	// 		'2020-01-01 00:00:00: testing: two', | ||||
| 	// 		'2020-01-01 00:00:00: testing: three', | ||||
| 	// 		'', | ||||
| 	// 	].join('\n')); | ||||
|  | ||||
| 		expect(logger.keptLines).toEqual([ | ||||
| 			'2020-01-01 00:00:00: testing: two', | ||||
| 			'2020-01-01 00:00:00: testing: three', | ||||
| 		]); | ||||
| 	// 	expect(logger.keptLines).toEqual([ | ||||
| 	// 		'2020-01-01 00:00:00: testing: two', | ||||
| 	// 		'2020-01-01 00:00:00: testing: three', | ||||
| 	// 	]); | ||||
|  | ||||
| 		jest.useRealTimers(); | ||||
| 	}); | ||||
| 	// 	jest.useRealTimers(); | ||||
| 	// }); | ||||
|  | ||||
| }); | ||||
|   | ||||
| @@ -78,8 +78,6 @@ class Logger { | ||||
| 	private level_: LogLevel = LogLevel.Info; | ||||
| 	private lastDbCleanup_: number = Date.now(); | ||||
| 	private enabled_ = true; | ||||
| 	private keptLineCount_ = 0; | ||||
| 	private keptLines_: string[] = []; | ||||
|  | ||||
| 	public static fsDriver() { | ||||
| 		if (!Logger.fsDriver_) Logger.fsDriver_ = dummyFsDriver; | ||||
| @@ -94,18 +92,6 @@ class Logger { | ||||
| 		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 { | ||||
| 		const output: string[] = []; | ||||
| 		output.push(`Enabled: ${this.enabled}`); | ||||
| @@ -118,6 +104,12 @@ class 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 { | ||||
| 		if (!this.globalLogger_) { | ||||
| 			// The global logger normally is initialized early, so we shouldn't | ||||
| @@ -361,13 +353,6 @@ class Logger { | ||||
| 				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 | ||||
|   | ||||
| @@ -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) => { | ||||
| 	const units = ['Bytes', 'KB', 'MB', 'GB']; | ||||
| 	let unitIndex = 0; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|   "repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils", | ||||
|   "exports": { | ||||
|     ".": "./dist/index.js", | ||||
|     "./bytes": "./dist/bytes.js", | ||||
|     "./dom": "./dist/dom.js", | ||||
|     "./env": "./dist/env.js", | ||||
|     "./object": "./dist/object.js", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user