mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
fix(frontend): dont log debug messages in production build
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
85ba675507
commit
1251f2f650
2 changed files with 193 additions and 65 deletions
|
@ -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<string, string>()
|
||||
const localStorageMock = Mock.of<Storage>({
|
||||
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) => {
|
||||
describe('debug logging', () => {
|
||||
it('wont log without test mode, dev mode and local storage', () => {
|
||||
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()
|
||||
})
|
||||
|
||||
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('logs a info message into the console', (done) => {
|
||||
it('will enable debug logging in production mode but with local storage setting', () => {
|
||||
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()
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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[] {
|
||||
|
|
Loading…
Reference in a new issue