diff --git a/src/init/CliRunner.ts b/src/init/CliRunner.ts index 16c401f9a..a13437084 100644 --- a/src/init/CliRunner.ts +++ b/src/init/CliRunner.ts @@ -52,7 +52,7 @@ export const runCustom = function( }) as Setup; return await setup.setup(); })().then((base: string): void => { - logger.log('info', `Running at ${base}`); + logger.info(`Running at ${base}`); }).catch((error): void => { // This is the only time we can *not* use the logger to print error messages, as dependency injection has failed. stderr.write(`${error}\n`); diff --git a/src/init/Setup.ts b/src/init/Setup.ts index 26472c10d..0c531a145 100644 --- a/src/init/Setup.ts +++ b/src/init/Setup.ts @@ -71,7 +71,7 @@ export class Setup { }, ); }; - this.logger.log('debug', 'Setup default ACL settings'); + this.logger.debug('Setup default ACL settings'); await aclSetup(); this.httpServer.listen(this.port); diff --git a/src/logging/LazyLogger.ts b/src/logging/LazyLogger.ts index aebc7e185..87a31c705 100644 --- a/src/logging/LazyLogger.ts +++ b/src/logging/LazyLogger.ts @@ -1,5 +1,5 @@ import type { LazyLoggerFactory } from './LazyLoggerFactory'; -import type { Logger } from './Logger'; +import { Logger } from './Logger'; import type { LogLevel } from './LogLevel'; /** @@ -8,13 +8,14 @@ import type { LogLevel } from './LogLevel'; * An error will be thrown if {@link LazyLogger.log} is invoked * before a {@link LoggerFactory} is set in {@link LazyLoggerFactory}. */ -export class LazyLogger implements Logger { +export class LazyLogger extends Logger { private readonly lazyLoggerFactory: LazyLoggerFactory; private readonly label: string; private logger: Logger | undefined; public constructor(lazyLoggerFactory: LazyLoggerFactory, label: string) { + super(); this.lazyLoggerFactory = lazyLoggerFactory; this.label = label; } diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index b5c57ab47..01b0c90d3 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -5,7 +5,7 @@ import type { LogLevel } from './LogLevel'; * * @see getLoggerFor on how to instantiate loggers. */ -export interface Logger { +export abstract class Logger { /** * Log the given message at the given level. * If the internal level is higher than the given level, the message may be voided. @@ -13,5 +13,59 @@ export interface Logger { * @param message - The message to log. * @param meta - Optional metadata to include in the log message. */ - log: (level: LogLevel, message: string, meta?: any) => Logger; + public abstract log(level: LogLevel, message: string, meta?: any): Logger; + + /** + * Log a message at the 'error' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public error(message: string, meta?: any): Logger { + return this.log('error', message, meta); + } + + /** + * Log a message at the 'warn' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public warn(message: string, meta?: any): Logger { + return this.log('warn', message, meta); + } + + /** + * Log a message at the 'info' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public info(message: string, meta?: any): Logger { + return this.log('info', message, meta); + } + + /** + * Log a message at the 'verbose' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public verbose(message: string, meta?: any): Logger { + return this.log('verbose', message, meta); + } + + /** + * Log a message at the 'debug' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public debug(message: string, meta?: any): Logger { + return this.log('debug', message, meta); + } + + /** + * Log a message at the 'silly' level. + * @param message - The message to log. + * @param meta - Optional metadata to include in the log message. + */ + public silly(message: string, meta?: any): Logger { + return this.log('silly', message, meta); + } } diff --git a/src/logging/VoidLogger.ts b/src/logging/VoidLogger.ts index e154ab801..b73fb15d0 100644 --- a/src/logging/VoidLogger.ts +++ b/src/logging/VoidLogger.ts @@ -1,10 +1,10 @@ -import type { Logger } from './Logger'; +import { Logger } from './Logger'; import type { LogLevel } from './LogLevel'; /** * A logger that does nothing on a log message. */ -export class VoidLogger implements Logger { +export class VoidLogger extends Logger { // eslint-disable-next-line @typescript-eslint/no-unused-vars public log(level: LogLevel, message: string, meta?: any): Logger { // Do nothing diff --git a/src/logging/WinstonLogger.ts b/src/logging/WinstonLogger.ts index 65591f7c3..e5431ba0c 100644 --- a/src/logging/WinstonLogger.ts +++ b/src/logging/WinstonLogger.ts @@ -1,14 +1,15 @@ import type { Logger as WinstonInnerLogger } from 'winston'; -import type { Logger } from './Logger'; +import { Logger } from './Logger'; import type { LogLevel } from './LogLevel'; /** * A WinstonLogger implements the {@link Logger} interface using a given winston logger. */ -export class WinstonLogger implements Logger { +export class WinstonLogger extends Logger { private readonly logger: WinstonInnerLogger; public constructor(logger: WinstonInnerLogger) { + super(); this.logger = logger; } diff --git a/src/server/ExpressHttpServer.ts b/src/server/ExpressHttpServer.ts index 4f3cac23d..9217d8aab 100644 --- a/src/server/ExpressHttpServer.ts +++ b/src/server/ExpressHttpServer.ts @@ -38,11 +38,11 @@ export class ExpressHttpServer { // Delegate to the main handler app.use(async(request, response, done): Promise => { try { - this.logger.log('info', `Received request for ${request.url}`); + this.logger.info(`Received request for ${request.url}`); await this.handler.handleSafe({ request, response }); } catch (error: unknown) { const errMsg = error instanceof Error ? `${error.name}: ${error.message}\n${error.stack}` : 'Unknown error.'; - this.logger.log('error', errMsg); + this.logger.error(errMsg); response.status(500).contentType('text/plain').send(errMsg); } finally { done(); diff --git a/test/unit/logging/Logger.test.ts b/test/unit/logging/Logger.test.ts new file mode 100644 index 000000000..39f18dc54 --- /dev/null +++ b/test/unit/logging/Logger.test.ts @@ -0,0 +1,46 @@ +import { Logger } from '../../../src/logging/Logger'; + +describe('Logger', (): void => { + let logger: Logger; + let meta: any; + beforeEach(async(): Promise => { + logger = new (Logger as any)(); + logger.log = jest.fn(); + meta = { abc: 123 }; + }); + + it('Error delegates to log.', async(): Promise => { + logger.error('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('error', 'my message', meta); + }); + + it('Warn delegates to log.', async(): Promise => { + logger.warn('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('warn', 'my message', meta); + }); + + it('Info delegates to log.', async(): Promise => { + logger.info('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('info', 'my message', meta); + }); + it('Verbose delegates to log.', async(): Promise => { + logger.verbose('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('verbose', 'my message', meta); + }); + + it('Debug delegates to log.', async(): Promise => { + logger.debug('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('debug', 'my message', meta); + }); + + it('Silly delegates to log.', async(): Promise => { + logger.silly('my message', meta); + expect(logger.log).toHaveBeenCalledTimes(1); + expect(logger.log).toHaveBeenCalledWith('silly', 'my message', meta); + }); +});