From aaa49219dcf39faf3616c82f8be42e08630a07b9 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Tue, 22 Sep 2020 11:31:22 +0200 Subject: [PATCH] feat: configure logger during setup --- config/config-default.json | 1 + config/presets/cli-params.json | 4 ++++ config/presets/logging.json | 12 ++++++++++++ config/presets/setup.json | 3 +++ index.ts | 10 ++++++++++ src/init/CliRunner.ts | 8 +++++++- src/init/Setup.ts | 11 +++++++++++ src/logging/LogUtil.ts | 30 ++++++++++++++++++++++++++++++ test/unit/init/Setup.test.ts | 3 ++- test/unit/logging/LogUtil.test.ts | 20 ++++++++++++++++++++ 10 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 config/presets/logging.json create mode 100644 src/logging/LogUtil.ts create mode 100644 test/unit/logging/LogUtil.test.ts diff --git a/config/config-default.json b/config/config-default.json index 88508589d..905098c31 100644 --- a/config/config-default.json +++ b/config/config-default.json @@ -8,6 +8,7 @@ "files-scs:config/presets/ldp/operation-handler.json", "files-scs:config/presets/ldp/permissions-extractor.json", "files-scs:config/presets/ldp/request-parser.json", + "files-scs:config/presets/logging.json", "files-scs:config/presets/setup.json", "files-scs:config/presets/storage.json", "files-scs:config/presets/storage_wrapper.json", diff --git a/config/presets/cli-params.json b/config/presets/cli-params.json index 4fa65fb78..e55d73320 100644 --- a/config/presets/cli-params.json +++ b/config/presets/cli-params.json @@ -12,6 +12,10 @@ { "@id": "urn:solid-server:default:variable:rootFilePath", "@type": "Variable" + }, + { + "@id": "urn:solid-server:default:variable:loggingLevel", + "@type": "Variable" } ] } diff --git a/config/presets/logging.json b/config/presets/logging.json new file mode 100644 index 000000000..d13310afd --- /dev/null +++ b/config/presets/logging.json @@ -0,0 +1,12 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "@graph": [ + { + "@id": "urn:solid-server:default:LoggerFactory", + "@type": "WinstonLoggerFactory", + "WinstonLoggerFactory:_level": { + "@id": "urn:solid-server:default:variable:loggingLevel" + } + } + ] +} diff --git a/config/presets/setup.json b/config/presets/setup.json index c3b6d06e3..3f19fe74e 100644 --- a/config/presets/setup.json +++ b/config/presets/setup.json @@ -13,6 +13,9 @@ "Setup:_aclManager": { "@id": "urn:solid-server:default:AclManager" }, + "Setup:_loggerFactory": { + "@id": "urn:solid-server:default:LoggerFactory" + }, "Setup:_base": { "@id": "urn:solid-server:default:variable:base" }, diff --git a/index.ts b/index.ts index 247053547..8066b7684 100644 --- a/index.ts +++ b/index.ts @@ -29,6 +29,16 @@ export * from './src/ldp/http/SparqlUpdateBodyParser'; export * from './src/ldp/http/SparqlUpdatePatch'; export * from './src/ldp/http/TargetExtractor'; +// Logging +export * from './src/logging/LazyLogger'; +export * from './src/logging/LazyLoggerFactory'; +export * from './src/logging/Logger'; +export * from './src/logging/LoggerFactory'; +export * from './src/logging/LogLevel'; +export * from './src/logging/LogUtil'; +export * from './src/logging/VoidLoggerFactory'; +export * from './src/logging/WinstonLoggerFactory'; + // LDP/Operations export * from './src/ldp/operations/DeleteOperationHandler'; export * from './src/ldp/operations/GetOperationHandler'; diff --git a/src/init/CliRunner.ts b/src/init/CliRunner.ts index 2d2910dfd..16c401f9a 100644 --- a/src/init/CliRunner.ts +++ b/src/init/CliRunner.ts @@ -3,8 +3,11 @@ import type { ReadStream, WriteStream } from 'tty'; import type { LoaderProperties } from 'componentsjs'; import { Loader } from 'componentsjs'; import yargs from 'yargs'; +import { getLoggerFor } from '../logging/LogUtil'; import type { Setup } from './Setup'; +const logger = getLoggerFor('CliRunner'); + /** * Generic run function for starting the server from a given config * @param args - Command line arguments. @@ -25,6 +28,7 @@ export const runCustom = function( .options({ port: { type: 'number', alias: 'p', default: 3000 }, config: { type: 'string', alias: 'c' }, + level: { type: 'string', alias: 'l', default: 'info' }, }) .help(); @@ -43,12 +47,14 @@ export const runCustom = function( 'urn:solid-server:default:variable:port': argv.port, 'urn:solid-server:default:variable:base': `http://localhost:${argv.port}/`, 'urn:solid-server:default:variable:rootFilePath': process.cwd(), + 'urn:solid-server:default:variable:loggingLevel': argv.level, }, }) as Setup; return await setup.setup(); })().then((base: string): void => { - stdout.write(`Running at ${base}\n`); + logger.log('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 f234a99a8..26472c10d 100644 --- a/src/init/Setup.ts +++ b/src/init/Setup.ts @@ -1,6 +1,9 @@ import streamifyArray from 'streamify-array'; import type { AclManager } from '../authorization/AclManager'; import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata'; +import { LazyLoggerFactory } from '../logging/LazyLoggerFactory'; +import type { LoggerFactory } from '../logging/LoggerFactory'; +import { getLoggerFor } from '../logging/LogUtil'; import type { ExpressHttpServer } from '../server/ExpressHttpServer'; import type { ResourceStore } from '../storage/ResourceStore'; import { TEXT_TURTLE } from '../util/ContentTypes'; @@ -10,9 +13,11 @@ import { CONTENT_TYPE } from '../util/UriConstants'; * Invokes all logic to setup a server. */ export class Setup { + protected readonly logger = getLoggerFor(this); private readonly httpServer: ExpressHttpServer; private readonly store: ResourceStore; private readonly aclManager: AclManager; + private readonly loggerFactory: LoggerFactory; private readonly base: string; private readonly port: number; @@ -20,12 +25,14 @@ export class Setup { httpServer: ExpressHttpServer, store: ResourceStore, aclManager: AclManager, + loggerFactory: LoggerFactory, base: string, port: number, ) { this.httpServer = httpServer; this.store = store; this.aclManager = aclManager; + this.loggerFactory = loggerFactory; this.base = base; this.port = port; } @@ -34,6 +41,9 @@ export class Setup { * Set up a server. */ public async setup(): Promise { + // Configure the logger factory so that others can statically call it. + LazyLoggerFactory.getInstance().setLoggerFactory(this.loggerFactory); + // Set up acl so everything can still be done by default // Note that this will need to be adapted to go through all the correct channels later on const aclSetup = async(): Promise => { @@ -61,6 +71,7 @@ export class Setup { }, ); }; + this.logger.log('debug', 'Setup default ACL settings'); await aclSetup(); this.httpServer.listen(this.port); diff --git a/src/logging/LogUtil.ts b/src/logging/LogUtil.ts new file mode 100644 index 000000000..75f374f51 --- /dev/null +++ b/src/logging/LogUtil.ts @@ -0,0 +1,30 @@ +import { LazyLoggerFactory } from './LazyLoggerFactory'; +import type { Logger } from './Logger'; + +/** + * Gets a logger instance for the given class instance. + * + * The returned type of logger depends on the configured {@link LoggerFactory} in {@link Setup}. + * + * The following shows a typical pattern on how to create loggers: + * ``` + * class MyClass { + * protected readonly logger = getLoggerFor(this); + * } + * ``` + * If no class is applicable, a logger can also be created as follows: + * ``` + * const logger = getLoggerFor('MyFunction'); + * ``` + * + * @param loggable - A class instance or a class string name. + */ +export const getLoggerFor = (loggable: string | Instance): Logger => LazyLoggerFactory.getInstance() + .createLogger(typeof loggable === 'string' ? loggable : loggable.constructor.name); + +/** + * Helper interface to identify class instances. + */ +interface Instance { + constructor: { name: string }; +} diff --git a/test/unit/init/Setup.test.ts b/test/unit/init/Setup.test.ts index 6b84f4650..4c5c09695 100644 --- a/test/unit/init/Setup.test.ts +++ b/test/unit/init/Setup.test.ts @@ -1,5 +1,6 @@ import { Setup } from '../../../src/init/Setup'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; +import { VoidLoggerFactory } from '../../../src/logging/VoidLoggerFactory'; describe('Setup', (): void => { let httpServer: any; @@ -16,7 +17,7 @@ describe('Setup', (): void => { httpServer = { listen: jest.fn(), }; - setup = new Setup(httpServer, store, aclManager, 'http://localhost:3000/', 3000); + setup = new Setup(httpServer, store, aclManager, new VoidLoggerFactory(), 'http://localhost:3000/', 3000); }); it('starts an HTTP server.', async(): Promise => { diff --git a/test/unit/logging/LogUtil.test.ts b/test/unit/logging/LogUtil.test.ts new file mode 100644 index 000000000..5cdfe0192 --- /dev/null +++ b/test/unit/logging/LogUtil.test.ts @@ -0,0 +1,20 @@ +import { LazyLogger } from '../../../src/logging/LazyLogger'; +import { LazyLoggerFactory } from '../../../src/logging/LazyLoggerFactory'; +import { getLoggerFor } from '../../../src/logging/LogUtil'; +import { VoidLogger } from '../../../src/logging/VoidLogger'; + +describe('LogUtil', (): void => { + beforeEach(async(): Promise => { + LazyLoggerFactory.getInstance().setLoggerFactory(undefined); + }); + + it('allows creating a lazy logger for a string label.', async(): Promise => { + expect(getLoggerFor('MyLabel')).toBeInstanceOf(LazyLogger); + expect((getLoggerFor('MyLabel') as any).label).toEqual('MyLabel'); + }); + + it('allows creating a lazy logger for a class instance.', async(): Promise => { + expect(getLoggerFor(new VoidLogger())).toBeInstanceOf(LazyLogger); + expect((getLoggerFor(new VoidLogger()) as any).label).toEqual('VoidLogger'); + }); +});