From 1251f2f65041143866732ce422b2033da33f1ed4 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sun, 25 Jun 2023 22:58:17 +0200 Subject: [PATCH] fix(frontend): dont log debug messages in production build Signed-off-by: Tilman Vatteroth --- frontend/src/utils/logger.spec.ts | 209 +++++++++++++++++++++++++----- frontend/src/utils/logger.ts | 49 +++---- 2 files changed, 193 insertions(+), 65 deletions(-) diff --git a/frontend/src/utils/logger.spec.ts b/frontend/src/utils/logger.spec.ts index bf4af52e7..8547c1e76 100644 --- a/frontend/src/utils/logger.spec.ts +++ b/frontend/src/utils/logger.spec.ts @@ -3,68 +3,207 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { Logger, LogLevel } from './logger' +import { Logger } from './logger' import { Settings } from 'luxon' +import { Mock } from 'ts-mockery' + +let testMode = false +let devMode = false +jest.mock('./test-modes', () => ({ + get isTestMode() { + return testMode + }, + get isDevMode() { + return devMode + } +})) describe('Logger', () => { - let consoleMock: jest.SpyInstance let originalNow: () => number let dateShift = 0 - function mockConsole(methodToMock: LogLevel, onResult: (result: string) => void) { - consoleMock = jest.spyOn(console, methodToMock).mockImplementation((...data: string[]) => { - const result = data.reduce((state, current) => state + ' ' + current) - onResult(result) + let infoLogMock: jest.SpyInstance + let warnLogMock: jest.SpyInstance + let errorLogMock: jest.SpyInstance + let debugLogMock: jest.SpyInstance + let defaultLogMock: jest.SpyInstance + let isLocalStorageAccessDenied = false + + const mockLocalStorage = () => { + const storage = new Map() + const localStorageMock = Mock.of({ + getItem: (key) => { + if (isLocalStorageAccessDenied) { + throw new Error('Access Denied!') + } else { + return storage.get(key) ?? null + } + }, + setItem: (key: string, value: string) => storage.set(key, value), + removeItem: (key: string) => storage.delete(key) + }) + Object.defineProperty(window, 'localStorage', { + value: localStorageMock }) } beforeEach(() => { + jest.resetModules() + jest.restoreAllMocks() + + testMode = false + devMode = false + isLocalStorageAccessDenied = false + mockLocalStorage() + + infoLogMock = jest.spyOn(console, 'info').mockReturnValue() + warnLogMock = jest.spyOn(console, 'warn').mockReturnValue() + errorLogMock = jest.spyOn(console, 'error').mockReturnValue() + debugLogMock = jest.spyOn(console, 'debug').mockReturnValue() + defaultLogMock = jest.spyOn(console, 'log').mockReturnValue() + originalNow = Settings.now Settings.now = () => new Date(2021, 9, 25, dateShift, 1 + dateShift, 2 + dateShift, 3 + dateShift).valueOf() }) afterEach(() => { Settings.now = originalNow - consoleMock.mockReset() }) - it('logs a debug message into the console', (done) => { - dateShift = 0 - mockConsole(LogLevel.DEBUG, (result) => { - expect(consoleMock).toBeCalled() - expect(result).toEqual('%c[2021-10-25 00:01:02] %c(prefix) color: yellow color: orange beans') - done() + describe('debug logging', () => { + it('wont log without test mode, dev mode and local storage', () => { + dateShift = 0 + + new Logger('prefix').debug('beans') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).not.toBeCalled() + expect(defaultLogMock).not.toBeCalled() + }) + + it('will enable debug logging in production mode but with local storage setting', () => { + dateShift = 1 + + window.localStorage.setItem('debugLogging', 'ACTIVATED!') + + new Logger('prefix').debug('muffin') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 01:02:03] %c(prefix)', + 'color: yellow', + 'color: orange', + 'muffin' + ) + expect(defaultLogMock).not.toBeCalled() + }) + + it('wont log in production mode but without local storage access', () => { + dateShift = 0 + + isLocalStorageAccessDenied = true + + new Logger('prefix').debug('beans') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).not.toBeCalled() + expect(defaultLogMock).not.toBeCalled() + }) + + it('will enable debug logging enabled in test mode', () => { + dateShift = 3 + + testMode = true + + new Logger('prefix').debug('muffin') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 03:04:05] %c(prefix)', + 'color: yellow', + 'color: orange', + 'muffin' + ) + expect(defaultLogMock).not.toBeCalled() + }) + + it('will enable debug logging enabled in dev mode', () => { + dateShift = 4 + + devMode = true + + new Logger('prefix').debug('muffin') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 04:05:06] %c(prefix)', + 'color: yellow', + 'color: orange', + 'muffin' + ) + expect(defaultLogMock).not.toBeCalled() }) - new Logger('prefix').debug('beans') }) - it('logs a info message into the console', (done) => { - dateShift = 1 - mockConsole(LogLevel.INFO, (result) => { - expect(consoleMock).toBeCalled() - expect(result).toEqual('%c[2021-10-25 01:02:03] %c(prefix) color: yellow color: orange toast') - done() - }) + it('logs a info message into the console', () => { + dateShift = 5 + + new Logger('prefix').info('toast') + + expect(infoLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 05:06:07] %c(prefix)', + 'color: yellow', + 'color: orange', + 'toast' + ) + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).not.toBeCalled() + expect(defaultLogMock).not.toBeCalled() new Logger('prefix').info('toast') }) - it('logs a warn message into the console', (done) => { - dateShift = 2 - mockConsole(LogLevel.WARN, (result) => { - expect(consoleMock).toBeCalled() - expect(result).toEqual('%c[2021-10-25 02:03:04] %c(prefix) color: yellow color: orange eggs') - done() - }) + it('logs a warn message into the console', () => { + dateShift = 6 + new Logger('prefix').warn('eggs') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 06:07:08] %c(prefix)', + 'color: yellow', + 'color: orange', + 'eggs' + ) + expect(errorLogMock).not.toBeCalled() + expect(debugLogMock).not.toBeCalled() + expect(defaultLogMock).not.toBeCalled() }) - it('logs a error message into the console', (done) => { - dateShift = 3 - mockConsole(LogLevel.ERROR, (result) => { - expect(consoleMock).toBeCalled() - expect(result).toEqual('%c[2021-10-25 03:04:05] %c(prefix) color: yellow color: orange bacon') - done() - }) + it('logs a error message into the console', () => { + dateShift = 7 + new Logger('prefix').error('bacon') + + expect(infoLogMock).not.toBeCalled() + expect(warnLogMock).not.toBeCalled() + expect(errorLogMock).toHaveBeenCalledWith( + '%c[2021-10-25 07:08:09] %c(prefix)', + 'color: yellow', + 'color: orange', + 'bacon' + ) + expect(debugLogMock).not.toBeCalled() + expect(defaultLogMock).not.toBeCalled() }) }) diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts index 6033226c3..44af08642 100644 --- a/frontend/src/utils/logger.ts +++ b/frontend/src/utils/logger.ts @@ -3,25 +3,27 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { isDevMode, isTestMode } from './test-modes' import { DateTime } from 'luxon' -export enum LogLevel { - DEBUG = 'debug', - INFO = 'info', - WARN = 'warn', - ERROR = 'error' -} - -type OutputFunction = (...data: unknown[]) => void - /** * Simple logger that prefixes messages with a timestamp and a name. */ export class Logger { private readonly scope: string + private readonly debugLoggingEnabled: boolean constructor(scope: string) { this.scope = scope + this.debugLoggingEnabled = isDevMode || isTestMode || this.isDebugLoggingEnabled() + } + + private isDebugLoggingEnabled() { + try { + return !!window?.localStorage?.getItem('debugLogging') + } catch { + return false + } } /** @@ -30,7 +32,9 @@ export class Logger { * @param data data to log */ debug(...data: unknown[]): void { - this.log(LogLevel.DEBUG, ...data) + if (this.debugLoggingEnabled) { + this.log(console.debug, ...data) + } } /** @@ -39,7 +43,7 @@ export class Logger { * @param data data to log */ info(...data: unknown[]): void { - this.log(LogLevel.INFO, ...data) + this.log(console.info, ...data) } /** @@ -48,7 +52,7 @@ export class Logger { * @param data data to log */ warn(...data: unknown[]): void { - this.log(LogLevel.WARN, ...data) + this.log(console.warn, ...data) } /** @@ -57,26 +61,11 @@ export class Logger { * @param data data to log */ error(...data: unknown[]): void { - this.log(LogLevel.ERROR, ...data) + this.log(console.error, ...data) } - private log(loglevel: LogLevel, ...data: unknown[]) { - const preparedData = [...this.prefix(), ...data] - const logOutput = Logger.getLogOutput(loglevel) - logOutput(...preparedData) - } - - private static getLogOutput(logLevel: LogLevel): OutputFunction { - switch (logLevel) { - case LogLevel.INFO: - return console.info - case LogLevel.DEBUG: - return console.debug - case LogLevel.ERROR: - return console.error - case LogLevel.WARN: - return console.warn - } + private log(logSink: (...data: unknown[]) => void, ...data: unknown[]) { + logSink(...this.prefix(), ...data) } private prefix(): string[] {