Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/context/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"dependencies": {
"@loopback/metadata": "^2.0.3",
"debug": "^4.1.1",
"@types/debug": "^4.1.5",
"p-event": "^4.1.0",
"tslib": "^1.11.1",
"uuid": "^7.0.3"
Expand All @@ -29,7 +30,6 @@
"@loopback/eslint-config": "^6.0.3",
"@loopback/testlab": "^3.0.0",
"@types/bluebird": "^3.5.30",
"@types/debug": "^4.1.5",
"@types/node": "^10.17.19",
"@types/uuid": "^7.0.2",
"bluebird": "^3.7.2"
Expand Down
64 changes: 62 additions & 2 deletions packages/context/src/__tests__/unit/context.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Debugger} from 'debug';
import {format} from 'util';
import {
Binding,
BindingCreationPolicy,
Expand Down Expand Up @@ -44,14 +46,14 @@ describe('Context constructor', () => {
it('generates uuid name if not provided', () => {
const ctx = new Context();
expect(ctx.name).to.match(
/^[0-9A-F]{8}-[0-9A-F]{4}-[1][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
);
});

it('adds subclass name as the prefix', () => {
const ctx = new TestContext();
expect(ctx.name).to.match(
/^TestContext-[0-9A-F]{8}-[0-9A-F]{4}-[1][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
/^TestContext-[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
);
});

Expand Down Expand Up @@ -828,6 +830,64 @@ describe('Context', () => {
});
});

describe('debug', () => {
it('allows override of debug from subclasses', () => {
let debugOutput = '';
const myDebug = (formatter: string, ...args: unknown[]) => {
debugOutput = format(formatter, ...args);
};
myDebug.enabled = true;
class MyContext extends Context {
constructor() {
super('my-context');
this._debug = myDebug as Debugger;
}

debug(formatter: string, ...args: unknown[]) {
super.debug(formatter, ...args);
}
}

const myCtx = new MyContext();
myCtx.debug('%s %d', 'number of bindings', 10);
expect(debugOutput).to.eql(`[${myCtx.name}] number of bindings 10`);
});

it('sets up debug for subclasses with the class name', () => {
class MyContext extends Context {
constructor() {
super('my-context');
}

get debugFn() {
return this._debug;
}
}

const myCtx = new MyContext();
expect(myCtx.debugFn.namespace).to.eql('loopback:context:mycontext');
});

it('allows debug namespace for subclasses', () => {
class MyContext extends Context {
constructor() {
super('my-context');
}

getDebugNamespace() {
return 'myapp:my-context';
}

get debugFn() {
return this._debug;
}
}

const myCtx = new MyContext();
expect(myCtx.debugFn.namespace).to.eql('myapp:my-context');
});
});

describe('toJSON() and inspect()', () => {
beforeEach(setupBindings);

Expand Down
72 changes: 53 additions & 19 deletions packages/context/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import debugFactory from 'debug';
import debugFactory, {Debugger} from 'debug';
import {EventEmitter} from 'events';
import {v1 as uuidv1} from 'uuid';
import {v4 as uuidv4} from 'uuid';
import {Binding, BindingInspectOptions, BindingTag} from './binding';
import {
ConfigurationResolver,
Expand Down Expand Up @@ -40,8 +40,6 @@ import {
ValueOrPromise,
} from './value-promise';

const debug = debugFactory('loopback:context');

/**
* Context provides an implementation of Inversion of Control (IoC) container
*/
Expand Down Expand Up @@ -76,6 +74,21 @@ export class Context extends EventEmitter {
*/
protected configResolver: ConfigurationResolver;

/**
* A debug function which can be overridden by subclasses.
*
* @example
* ```ts
* import debugFactory from 'debug';
* const debug = debugFactory('loopback:context:application');
* export class Application extends Context {
* super('application');
* this._debug = debug;
* }
* ```
*/
protected _debug: Debugger;

/**
* Create a new context.
*
Expand Down Expand Up @@ -113,13 +126,34 @@ export class Context extends EventEmitter {
this.name = name ?? this.generateName();
this.tagIndexer = new ContextTagIndexer(this);
this.subscriptionManager = new ContextSubscriptionManager(this);
this._debug = debugFactory(this.getDebugNamespace());
}

/**
* Get the debug namespace for the context class. Subclasses can override
* this method to supply its own namespace.
*
* @example
* ```ts
* export class Application extends Context {
* super('application');
* }
*
* protected getDebugNamespace() {
* return 'loopback:context:application';
* }
* ```
*/
protected getDebugNamespace() {
if (this.constructor === Context) return 'loopback:context';
const name = this.constructor.name.toLowerCase();
return `loopback:context:${name}`;
}

private generateName() {
const id = uuidv1();
let prefix = `${this.constructor.name}-`;
if (prefix === 'Context-') prefix = '';
return `${prefix}${id}`;
const id = uuidv4();
if (this.constructor === Context) return id;
return `${this.constructor.name}-${id}`;
}

/**
Expand All @@ -135,14 +169,14 @@ export class Context extends EventEmitter {
* as the prefix
* @param args - Arguments for the debug
*/
private _debug(...args: unknown[]) {
protected debug(...args: unknown[]) {
/* istanbul ignore if */
if (!debug.enabled) return;
if (!this._debug.enabled) return;
const formatter = args.shift();
if (typeof formatter === 'string') {
debug(`[%s] ${formatter}`, this.name, ...args);
this._debug(`[%s] ${formatter}`, this.name, ...args);
} else {
debug('[%s] ', this.name, formatter, ...args);
this._debug('[%s] ', this.name, formatter, ...args);
}
}

Expand Down Expand Up @@ -184,7 +218,7 @@ export class Context extends EventEmitter {
*/
add(binding: Binding<unknown>): this {
const key = binding.key;
this._debug('[%s] Adding binding: %s', key);
this.debug('[%s] Adding binding: %s', key);
let existingBinding: Binding | undefined;
const keyExists = this.registry.has(key);
if (keyExists) {
Expand Down Expand Up @@ -261,14 +295,14 @@ export class Context extends EventEmitter {
},
);
if (configResolver) {
debug(
this.debug(
'Custom ConfigurationResolver is loaded from %s.',
ContextBindings.CONFIGURATION_RESOLVER.toString(),
);
this.configResolver = configResolver;
} else {
// Fallback to DefaultConfigurationResolver
debug('DefaultConfigurationResolver is used.');
this.debug('DefaultConfigurationResolver is used.');
this.configResolver = new DefaultConfigurationResolver(this);
}
}
Expand Down Expand Up @@ -340,7 +374,7 @@ export class Context extends EventEmitter {
* @returns true if the binding key is found and removed from this context
*/
unbind(key: BindingAddress): boolean {
this._debug('Unbind %s', key);
this.debug('Unbind %s', key);
key = BindingKey.validate(key);
const binding = this.registry.get(key);
// If not found, return `false`
Expand Down Expand Up @@ -378,7 +412,7 @@ export class Context extends EventEmitter {
* which is created per request.
*/
close() {
this._debug('Closing context...');
this.debug('Closing context...');
this.subscriptionManager.close();
this.tagIndexer.close();
}
Expand Down Expand Up @@ -577,7 +611,7 @@ export class Context extends EventEmitter {
keyWithPath: BindingAddress<ValueType>,
optionsOrSession?: ResolutionOptionsOrSession,
): Promise<ValueType | undefined> {
this._debug('Resolving binding: %s', keyWithPath);
this.debug('Resolving binding: %s', keyWithPath);
return this.getValueOrPromise<ValueType | undefined>(
keyWithPath,
optionsOrSession,
Expand Down Expand Up @@ -645,7 +679,7 @@ export class Context extends EventEmitter {
keyWithPath: BindingAddress<ValueType>,
optionsOrSession?: ResolutionOptionsOrSession,
): ValueType | undefined {
this._debug('Resolving binding synchronously: %s', keyWithPath);
this.debug('Resolving binding synchronously: %s', keyWithPath);

const valueOrPromise = this.getValueOrPromise<ValueType>(
keyWithPath,
Expand Down
Loading