Skip to content

Commit

Permalink
feat+fix: add guards to LumberjackModule and prevent failure if no lo…
Browse files Browse the repository at this point in the history
…g drivers are registered (ngworker#19)

* chore: lock dependencies

* test: cover LumberjackModule

* feat: add guards to LumberjackModule to prevent accidental misuse

* fix: don't fail if no log drivers are registered

* test: fix flaky tests
  • Loading branch information
LayZeeDK authored Oct 13, 2020
1 parent 1cfff0d commit 4ed6a0d
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe(ConsoleDriverModule.name, () => {
expect(actualConfig).toEqual(expectedConfig);
});

it('does not register the specified log driver configuration when the lumberjack module is imported after the console driver module', () => {
it('does register the specified log driver configuration when the lumberjack module is imported after the console driver module', () => {
const expectedConfig: LogDriverConfig = {
levels: [LumberjackLogLevel.Debug],
};
Expand All @@ -77,7 +77,7 @@ describe(ConsoleDriverModule.name, () => {
});

const actualConfig = consoleDriver.config;
expect(actualConfig).not.toEqual(expectedConfig);
expect(actualConfig).toEqual(expectedConfig);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LumberjackRootModule } from './lumberjack-root.module';

describe(LumberjackRootModule.name, () => {
it('guards against being registered in multiple injectors', () => {
const rootInjectorInstance = new LumberjackRootModule();

expect(() => new LumberjackRootModule(rootInjectorInstance)).toThrowError(/multiple injectors/);
});
});
21 changes: 21 additions & 0 deletions projects/ngworker/lumberjack/src/lib/lumberjack-root.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NgModule, Optional, SkipSelf } from '@angular/core';

import { defaultLogDriverConfig, LogDriverConfigToken } from './configs/log-driver.config';

@NgModule({
providers: [
{
provide: LogDriverConfigToken,
useValue: defaultLogDriverConfig,
},
],
})
export class LumberjackRootModule {
constructor(@Optional() @SkipSelf() maybeNgModuleFromParentInjector?: LumberjackRootModule) {
if (maybeNgModuleFromParentInjector) {
throw new Error(
'LumberjackModule.forRoot registered in multiple injectors. Only call it from your root injector such as in AppModule.'
);
}
}
}
51 changes: 51 additions & 0 deletions projects/ngworker/lumberjack/src/lib/lumberjack.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { TestBed } from '@angular/core/testing';

import { expectNgModuleToBeGuarded } from '../../tests/expect-ng-module-to-be-guarded';

import {
defaultLogConfig,
defaultLogDriverConfig,
LogDriverConfigToken,
LumberjackLogConfig,
LumberjackLogConfigToken,
} from './configs';
import { LumberjackModule } from './lumberjack.module';

describe(LumberjackModule.name, () => {
it(`cannot be imported without using the ${LumberjackModule.forRoot.name} method`, () => {
expectNgModuleToBeGuarded(LumberjackModule);
});

describe(LumberjackModule.forRoot.name, () => {
it('accepts a log configuration', () => {
const expectedConfig: LumberjackLogConfig = {
format: ({ message }) => message,
};

TestBed.configureTestingModule({
imports: [LumberjackModule.forRoot(expectedConfig)],
});

const actualConfig = TestBed.inject(LumberjackLogConfigToken);
expect(actualConfig).toEqual(expectedConfig);
});

it('provides a default log configuration', () => {
TestBed.configureTestingModule({
imports: [LumberjackModule.forRoot()],
});

const actualConfig = TestBed.inject(LumberjackLogConfigToken);
expect(actualConfig).toEqual(defaultLogConfig);
});

it('provides a default log driver configuration', () => {
TestBed.configureTestingModule({
imports: [LumberjackModule.forRoot()],
});

const actualConfig = TestBed.inject(LogDriverConfigToken);
expect(actualConfig).toEqual(defaultLogDriverConfig);
});
});
});
13 changes: 7 additions & 6 deletions projects/ngworker/lumberjack/src/lib/lumberjack.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import { ModuleWithProviders, NgModule } from '@angular/core';
import { defaultLogConfig } from './configs/default-log.config';
import { defaultLogDriverConfig, LogDriverConfigToken } from './configs/log-driver.config';
import { LumberjackLogConfig, LumberjackLogConfigToken } from './configs/lumberjack-log.config';
import { LumberjackRootModule } from './lumberjack-root.module';

@NgModule()
export class LumberjackModule {
static forRoot(config: LumberjackLogConfig = defaultLogConfig): ModuleWithProviders<LumberjackModule> {
static forRoot(config: LumberjackLogConfig = defaultLogConfig): ModuleWithProviders<LumberjackRootModule> {
return {
ngModule: LumberjackModule,
ngModule: LumberjackRootModule,
providers: [
{
provide: LumberjackLogConfigToken,
useValue: config,
},
{
provide: LogDriverConfigToken,
useValue: defaultLogDriverConfig,
},
],
};
}

constructor() {
throw new Error('Do not import LumberjackModule directly. Use LumberjackModule.forRoot.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TestBed } from '@angular/core/testing';

import { LogDriverConfig, LogDriverConfigToken } from './configs';
import { createDebugLog } from './log-types';
import { LumberjackModule } from './lumberjack.module';
import { LumberjackService } from './lumberjack.service';

const allLogLevelsDisabled: LogDriverConfig = {
levels: [],
};
const createEmptyDebugLog = createDebugLog('');
const logEmptyDebugMessage = () => TestBed.inject(LumberjackService).log(createEmptyDebugLog());

describe(LumberjackService.name, () => {
it('accepts logs when no log drivers are registered', () => {
TestBed.configureTestingModule({
imports: [LumberjackModule.forRoot()],
});

expect(logEmptyDebugMessage).not.toThrow();
});

it('accepts logs when all log levels are disabled', () => {
TestBed.configureTestingModule({
imports: [LumberjackModule.forRoot()],
providers: [{ provide: LogDriverConfigToken, useValue: allLogLevelsDisabled }],
});

expect(logEmptyDebugMessage).not.toThrow();
});
});
12 changes: 6 additions & 6 deletions projects/ngworker/lumberjack/src/lib/lumberjack.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ describe('LumberjackService', () => {
const drivers = spectator.inject<LogDriver[]>(LogDriverToken);
const consoleDriver = drivers[0] as ConsoleDriver;
const httpDriver = drivers[1] as HttpDriver;
const consoleSpyLogInfo = spyOn(consoleDriver, 'logInfo').and.callThrough();
const httpSpyLogInfo = spyOn(httpDriver, 'logInfo').and.callThrough();
const consoleSpyDebugInfo = spyOn(consoleDriver, 'logDebug').and.callThrough();
const httpSpyDebugInfo = spyOn(httpDriver, 'logDebug').and.callThrough();
const consoleSpyLogInfo = spyOn(consoleDriver, 'logInfo');
const httpSpyLogInfo = spyOn(httpDriver, 'logInfo');
const consoleSpyDebugInfo = spyOn(consoleDriver, 'logDebug');
const httpSpyDebugInfo = spyOn(httpDriver, 'logDebug');

const infoLog: LumberjackLog = {
context: 'Lumberjack Service',
Expand All @@ -73,8 +73,8 @@ describe('LumberjackService', () => {
const drivers = spectator.inject<LogDriver[]>(LogDriverToken);
const consoleDriver = drivers[0] as ConsoleDriver;
const httpDriver = drivers[1] as HttpDriver;
const consoleSpyError = spyOn(consoleDriver, 'logError').and.callThrough();
const httpSpyError = spyOn(httpDriver, 'logError').and.callThrough();
const consoleSpyError = spyOn(consoleDriver, 'logError');
const httpSpyError = spyOn(httpDriver, 'logError');

const errorLog: LumberjackLog = {
context: 'Lumberjack Service',
Expand Down
11 changes: 8 additions & 3 deletions projects/ngworker/lumberjack/src/lib/lumberjack.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable } from '@angular/core';
import { Inject, Injectable, Optional } from '@angular/core';

import { LumberjackLogConfig, LumberjackLogConfigToken } from './configs/lumberjack-log.config';
import { LogDriver, LogDriverToken } from './log-drivers';
Expand All @@ -15,12 +15,17 @@ import { LumberjackLogLevel } from './lumberjack-log-levels';
*/
@Injectable({ providedIn: 'root' })
export class LumberjackService {
private logDrivers: LogDriver[];

constructor(
@Inject(LumberjackLogConfigToken) private config: LumberjackLogConfig,
// Each driver must be provided with multi. That way we can capture every provided driver
// and use it to log to its output.
@Inject(LogDriverToken) private logDrivers: LogDriver[]
) {}
@Optional() @Inject(LogDriverToken) logDrivers?: LogDriver[]
) {
logDrivers = logDrivers ?? [];
this.logDrivers = Array.isArray(logDrivers) ? logDrivers : [logDrivers];
}

log(logItem: LumberjackLog): void {
const { format } = this.config;
Expand Down
Loading

0 comments on commit 4ed6a0d

Please sign in to comment.