From 49cd17e520987f1755ce192847fc6b78eaa79049 Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:34:11 -0700 Subject: [PATCH] Chore: Allow disabling deletion logging (#10105) --- .eslintignore | 1 + .gitignore | 1 + packages/lib/utils/ActionLogger.test.ts | 63 +++++++++++++++++++++++++ packages/lib/utils/ActionLogger.ts | 25 ++++++++-- packages/utils/Logger.ts | 7 +++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 packages/lib/utils/ActionLogger.test.ts diff --git a/.eslintignore b/.eslintignore index 899950454..cb7cab169 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1112,6 +1112,7 @@ packages/lib/themes/solarizedLight.js packages/lib/themes/type.js packages/lib/time.js packages/lib/types.js +packages/lib/utils/ActionLogger.test.js packages/lib/utils/ActionLogger.js packages/lib/utils/credentialFiles.js packages/lib/utils/ipc/RemoteMessenger.test.js diff --git a/.gitignore b/.gitignore index 0f27c3675..1ad31a618 100644 --- a/.gitignore +++ b/.gitignore @@ -1092,6 +1092,7 @@ packages/lib/themes/solarizedLight.js packages/lib/themes/type.js packages/lib/time.js packages/lib/types.js +packages/lib/utils/ActionLogger.test.js packages/lib/utils/ActionLogger.js packages/lib/utils/credentialFiles.js packages/lib/utils/ipc/RemoteMessenger.test.js diff --git a/packages/lib/utils/ActionLogger.test.ts b/packages/lib/utils/ActionLogger.test.ts new file mode 100644 index 000000000..0bd0d82f8 --- /dev/null +++ b/packages/lib/utils/ActionLogger.test.ts @@ -0,0 +1,63 @@ +import Logger, { LogLevel, TargetType } from '@joplin/utils/Logger'; +import { setupDatabaseAndSynchronizer, switchClient } from '../testing/test-utils'; +import Note from '../models/Note'; +import ActionLogger from './ActionLogger'; +import Setting from '../models/Setting'; +import { pathExists, readFile, remove, writeFile } from 'fs-extra'; + +const getLogPath = () => `${Setting.value('profileDir')}/log.txt`; + +const logContainsEntryWith = async (...terms: string[]) => { + const lines = (await readFile(getLogPath(), 'utf8')).split('\n'); + for (const line of lines) { + if (terms.every(t => line.includes(t))) { + return true; + } + } + + return false; +}; + +describe('ActionLogger', () => { + beforeEach(async () => { + await setupDatabaseAndSynchronizer(1); + await switchClient(1); + + const logPath = getLogPath(); + if (await pathExists(logPath)) { + await remove(logPath); + } + await writeFile(logPath, '', 'utf8'); + + const logger = new Logger(); + logger.addTarget(TargetType.File, { path: logPath }); + logger.setLevel(LogLevel.Info); + + Logger.initializeGlobalLogger(logger); + }); + + it('should log deletions', async () => { + const note = await Note.save({ title: 'MyTestNote' }); + await Note.delete(note.id, { toTrash: false }); + await Logger.globalLogger.waitForFileWritesToComplete_(); + + expect( + await logContainsEntryWith('DeleteAction', note.id, note.title), + ).toBe(true); + }); + + it('should be possible to disable ActionLogger globally', async () => { + const note1 = await Note.save({ title: 'testNote1' }); + const note2 = await Note.save({ title: 'testNote2' }); + + ActionLogger.enabled = true; + await Note.delete(note1.id, { toTrash: false }); + ActionLogger.enabled = false; + await Note.delete(note2.id, { toTrash: false }); + ActionLogger.enabled = true; + await Logger.globalLogger.waitForFileWritesToComplete_(); + + expect(await logContainsEntryWith('DeleteAction', note1.id)).toBe(true); + expect(await logContainsEntryWith('DeleteAction', note2.id)).toBe(false); + }); +}); diff --git a/packages/lib/utils/ActionLogger.ts b/packages/lib/utils/ActionLogger.ts index 77d6c12d1..1031ee4a4 100644 --- a/packages/lib/utils/ActionLogger.ts +++ b/packages/lib/utils/ActionLogger.ts @@ -9,25 +9,29 @@ const actionTypeToLogger = { }; export default class ActionLogger { - private descriptions: string[] = []; + private descriptions_: string[] = []; private constructor(private source: string) { } public clone() { const clone = new ActionLogger(this.source); - clone.descriptions = [...this.descriptions]; + clone.descriptions_ = [...this.descriptions_]; return clone; } // addDescription is used to add labels with information that may not be available // when .log is called. For example, to include the title of a deleted note. public addDescription(description: string) { - this.descriptions.push(description); + this.descriptions_.push(description); } public log(action: ItemActionType, itemIds: string|string[]) { + if (!ActionLogger.enabled_) { + return; + } + const logger = actionTypeToLogger[action]; - logger.info(`${this.source}: ${this.descriptions.join(',')}; Item IDs: ${JSON.stringify(itemIds)}`); + logger.info(`${this.source}: ${this.descriptions_.join(',')}; Item IDs: ${JSON.stringify(itemIds)}`); } public static from(source: ActionLogger|string|undefined) { @@ -41,4 +45,17 @@ export default class ActionLogger { return source; } + + + // Disabling the action logger globally can be useful on Joplin Server/Cloud + // when many deletions are expected (e.g. for email-to-note). + private static enabled_ = true; + + public static set enabled(v: boolean) { + this.enabled_ = v; + } + + public static get enabled() { + return this.enabled_; + } } diff --git a/packages/utils/Logger.ts b/packages/utils/Logger.ts index 2cb780680..2e32dde5a 100644 --- a/packages/utils/Logger.ts +++ b/packages/utils/Logger.ts @@ -328,6 +328,13 @@ class Logger { } } + // For tests + public async waitForFileWritesToComplete_() { + const release = await writeToFileMutex_.acquire(); + release(); + return; + } + public error(...object: any[]) { return this.log(LogLevel.Error, null, ...object); }