mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 03:06:31 -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
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { Logger, LogLevel } from './logger'
|
import { Logger } from './logger'
|
||||||
import { Settings } from 'luxon'
|
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', () => {
|
describe('Logger', () => {
|
||||||
let consoleMock: jest.SpyInstance
|
|
||||||
let originalNow: () => number
|
let originalNow: () => number
|
||||||
let dateShift = 0
|
let dateShift = 0
|
||||||
|
|
||||||
function mockConsole(methodToMock: LogLevel, onResult: (result: string) => void) {
|
let infoLogMock: jest.SpyInstance
|
||||||
consoleMock = jest.spyOn(console, methodToMock).mockImplementation((...data: string[]) => {
|
let warnLogMock: jest.SpyInstance
|
||||||
const result = data.reduce((state, current) => state + ' ' + current)
|
let errorLogMock: jest.SpyInstance
|
||||||
onResult(result)
|
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(() => {
|
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
|
originalNow = Settings.now
|
||||||
Settings.now = () => new Date(2021, 9, 25, dateShift, 1 + dateShift, 2 + dateShift, 3 + dateShift).valueOf()
|
Settings.now = () => new Date(2021, 9, 25, dateShift, 1 + dateShift, 2 + dateShift, 3 + dateShift).valueOf()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
Settings.now = originalNow
|
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
|
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')
|
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
|
dateShift = 1
|
||||||
mockConsole(LogLevel.INFO, (result) => {
|
|
||||||
expect(consoleMock).toBeCalled()
|
window.localStorage.setItem('debugLogging', 'ACTIVATED!')
|
||||||
expect(result).toEqual('%c[2021-10-25 01:02:03] %c(prefix) color: yellow color: orange toast')
|
|
||||||
done()
|
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')
|
new Logger('prefix').info('toast')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs a warn message into the console', (done) => {
|
it('logs a warn message into the console', () => {
|
||||||
dateShift = 2
|
dateShift = 6
|
||||||
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()
|
|
||||||
})
|
|
||||||
new Logger('prefix').warn('eggs')
|
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) => {
|
it('logs a error message into the console', () => {
|
||||||
dateShift = 3
|
dateShift = 7
|
||||||
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()
|
|
||||||
})
|
|
||||||
new Logger('prefix').error('bacon')
|
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
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import { isDevMode, isTestMode } from './test-modes'
|
||||||
import { DateTime } from 'luxon'
|
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.
|
* Simple logger that prefixes messages with a timestamp and a name.
|
||||||
*/
|
*/
|
||||||
export class Logger {
|
export class Logger {
|
||||||
private readonly scope: string
|
private readonly scope: string
|
||||||
|
private readonly debugLoggingEnabled: boolean
|
||||||
|
|
||||||
constructor(scope: string) {
|
constructor(scope: string) {
|
||||||
this.scope = scope
|
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
|
* @param data data to log
|
||||||
*/
|
*/
|
||||||
debug(...data: unknown[]): void {
|
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
|
* @param data data to log
|
||||||
*/
|
*/
|
||||||
info(...data: unknown[]): void {
|
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
|
* @param data data to log
|
||||||
*/
|
*/
|
||||||
warn(...data: unknown[]): void {
|
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
|
* @param data data to log
|
||||||
*/
|
*/
|
||||||
error(...data: unknown[]): void {
|
error(...data: unknown[]): void {
|
||||||
this.log(LogLevel.ERROR, ...data)
|
this.log(console.error, ...data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private log(loglevel: LogLevel, ...data: unknown[]) {
|
private log(logSink: (...data: unknown[]) => void, ...data: unknown[]) {
|
||||||
const preparedData = [...this.prefix(), ...data]
|
logSink(...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 prefix(): string[] {
|
private prefix(): string[] {
|
||||||
|
|
Loading…
Reference in a new issue