Skip to content
This repository has been archived by the owner on Nov 10, 2022. It is now read-only.

chore: refactor diag logger #9

Merged
merged 7 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
73 changes: 36 additions & 37 deletions src/api/diag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DiagLogFunction,
createNoopDiagLogger,
diagLoggerFunctions,
FilteredDiagLogger,
} from '../diag/logger';
import { DiagLogLevel, createLogLevelDiagLogger } from '../diag/logLevel';
import {
Expand All @@ -30,13 +31,13 @@ import {

/** Internal simple Noop Diag API that returns a noop logger and does not allow any changes */
function noopDiagApi(): DiagAPI {
const noopApi = createNoopDiagLogger() as DiagAPI;

noopApi.getLogger = () => noopApi;
noopApi.setLogger = noopApi.getLogger;
noopApi.setLogLevel = () => {};

return noopApi;
const noopLogger = createNoopDiagLogger();
return {
disable: () => {},
getLogger: () => noopLogger,
setLogger: () => {},
...noopLogger,
};
}

/**
Expand Down Expand Up @@ -71,14 +72,13 @@ export class DiagAPI implements DiagLogger {
* @private
*/
private constructor() {
let _logLevel: DiagLogLevel = DiagLogLevel.INFO;
let _filteredLogger: DiagLogger | null;
let _logger: DiagLogger = createNoopDiagLogger();
const _noopLogger = createNoopDiagLogger();
let _filteredLogger: FilteredDiagLogger | undefined;

function _logProxy(funcName: keyof DiagLogger): DiagLogFunction {
return function () {
const orgArguments = arguments as unknown;
const theLogger = _filteredLogger || _logger;
const theLogger = _filteredLogger || _noopLogger;
dyladan marked this conversation as resolved.
Show resolved Hide resolved
const theFunc = theLogger[funcName];
if (typeof theFunc === 'function') {
return theFunc.apply(
Expand All @@ -94,29 +94,23 @@ export class DiagAPI implements DiagLogger {

// DiagAPI specific functions

self.getLogger = (): DiagLogger => {
// Return itself if no existing logger is defined (defaults effectively to a Noop)
return _logger;
self.getLogger = (): FilteredDiagLogger => {
return _filteredLogger || _noopLogger;
};

self.setLogger = (logger?: DiagLogger): DiagLogger => {
const prevLogger = _logger;
if (!logger || logger !== self) {
// Simple special case to avoid any possible infinite recursion on the logging functions
_logger = logger || createNoopDiagLogger();
_filteredLogger = createLogLevelDiagLogger(_logLevel, _logger);
}

return prevLogger;
self.setLogger = (
logger: DiagLogger,
logLevel: DiagLogLevel = DiagLogLevel.INFO
) => {
// This is required to prevent an endless loop in the case where the diag
// is used as a child of itself accidentally.
logger = logger === self ? self.getLogger().getChild() : logger;
logger = logger ?? _noopLogger;
_filteredLogger = createLogLevelDiagLogger(logLevel, logger);
};

self.setLogLevel = (maxLogLevel: DiagLogLevel) => {
if (maxLogLevel !== _logLevel) {
_logLevel = maxLogLevel;
if (_logger) {
_filteredLogger = createLogLevelDiagLogger(maxLogLevel, _logger);
}
}
self.disable = () => {
_filteredLogger = undefined;
dyladan marked this conversation as resolved.
Show resolved Hide resolved
};

for (let i = 0; i < diagLoggerFunctions.length; i++) {
Expand All @@ -129,22 +123,27 @@ export class DiagAPI implements DiagLogger {
* Return the currently configured logger instance, if no logger has been configured
* it will return itself so any log level filtering will still be applied in this case.
*/
public getLogger!: () => DiagLogger;
public getLogger!: () => FilteredDiagLogger;

/**
* Set the DiagLogger instance
* @param logger - [Optional] The DiagLogger instance to set as the default logger, if not provided it will set it back as a noop
* Set the global DiagLogger and DiagLogLevel.
* If a global diag logger is already set, this will override it.
*
* @param logger - [Optional] The DiagLogger instance to set as the default logger.
* @param logLevel - [Optional] The DiagLogLevel used to filter logs sent to the logger. If not provided it will default to INFO.
* @returns The previously registered DiagLogger
*/
public setLogger!: (logger?: DiagLogger) => DiagLogger;

/** Set the default maximum diagnostic logging level */
public setLogLevel!: (maxLogLevel: DiagLogLevel) => void;
public setLogger!: (logger: DiagLogger, logLevel?: DiagLogLevel) => void;

// DiagLogger implementation
public verbose!: DiagLogFunction;
public debug!: DiagLogFunction;
public info!: DiagLogFunction;
public warn!: DiagLogFunction;
public error!: DiagLogFunction;

/**
* Unregister the global logger and return to Noop
*/
public disable!: () => void;
}
16 changes: 7 additions & 9 deletions src/diag/logLevel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

import { DiagAPI } from '../api/diag';
import { DiagLogger, DiagLogFunction, createNoopDiagLogger } from './logger';
import { diag } from '..';
import { DiagLogger, DiagLogFunction, FilteredDiagLogger } from './logger';

/**
* Defines the available internal logging levels for the diagnostic logger, the numeric values
Expand Down Expand Up @@ -81,19 +81,15 @@ const levelMap: { n: keyof DiagLogger; l: DiagLogLevel }[] = [
export function createLogLevelDiagLogger(
maxLevel: DiagLogLevel,
logger?: DiagLogger | null
): DiagLogger {
): FilteredDiagLogger {
if (maxLevel < DiagLogLevel.NONE) {
maxLevel = DiagLogLevel.NONE;
} else if (maxLevel > DiagLogLevel.ALL) {
maxLevel = DiagLogLevel.ALL;
}

if (maxLevel === DiagLogLevel.NONE) {
return createNoopDiagLogger();
}

if (!logger) {
logger = DiagAPI.instance();
logger = diag.getLogger().getChild();
}

function _filterFunc(
Expand All @@ -116,11 +112,13 @@ export function createLogLevelDiagLogger(
return function () {};
}

const newLogger = {} as DiagLogger;
const newLogger = {} as FilteredDiagLogger;
for (let i = 0; i < levelMap.length; i++) {
const name = levelMap[i].n;
newLogger[name] = _filterFunc(logger, name, levelMap[i].l);
}

newLogger.getChild = () => logger!;

return newLogger;
}
18 changes: 16 additions & 2 deletions src/diag/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ export interface DiagLogger {
verbose: DiagLogFunction;
}

/**
* A filtered diag logger has the same functions as a diag logger, but calls the
* child logging methods or not depending on some filtering scheme; for example
* log level, namespace, or throttling.
*/
export interface FilteredDiagLogger extends DiagLogger {
dyladan marked this conversation as resolved.
Show resolved Hide resolved
/**
* Get the child logger of the filtered diag logger.
*/
getChild(): DiagLogger;
}

// DiagLogger implementation
export const diagLoggerFunctions: Array<keyof DiagLogger> = [
'verbose',
Expand All @@ -75,12 +87,14 @@ function noopLogFunction() {}
* @implements {@link DiagLogger}
* @returns {DiagLogger}
*/
export function createNoopDiagLogger(): DiagLogger {
const diagLogger = {} as DiagLogger;
export function createNoopDiagLogger(): FilteredDiagLogger {
const diagLogger = {} as FilteredDiagLogger;

for (let i = 0; i < diagLoggerFunctions.length; i++) {
diagLogger[diagLoggerFunctions[i]] = noopLogFunction;
}

diagLogger.getChild = () => diagLogger;

return diagLogger;
}
1 change: 1 addition & 0 deletions test/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ describe('API', () => {

diagLoggerFunctions.forEach(fName => {
it(`no argument logger ${fName} message doesn't throw`, () => {
//@ts-expect-error an undefined logger is not allowed
diag.setLogger();
assert.doesNotThrow(() => {
diag[fName](`${fName} message`);
Expand Down
49 changes: 31 additions & 18 deletions test/diag/logLevel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ describe('LogLevelFilter DiagLogger', () => {

beforeEach(() => {
// Set no logger so that sinon doesn't complain about TypeError: Attempted to wrap xxxx which is already wrapped
diag.setLogger();
diag.setLogLevel(DiagLogLevel.INFO);
diag.disable();

// mock
dummyLogger = {} as DiagLogger;
Expand Down Expand Up @@ -164,8 +163,7 @@ describe('LogLevelFilter DiagLogger', () => {
});

it('should use default logger for undefined and log', () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(DiagLogLevel.ALL);
diag.setLogger(dummyLogger, DiagLogLevel.ALL);
const testLogger = createLogLevelDiagLogger(map.level, undefined);
testLogger[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
Expand All @@ -181,8 +179,7 @@ describe('LogLevelFilter DiagLogger', () => {
});

it('should use default logger for null and log', () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(DiagLogLevel.ALL);
diag.setLogger(dummyLogger, DiagLogLevel.ALL);
const testLogger = createLogLevelDiagLogger(map.level, null);
testLogger[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
Expand All @@ -199,17 +196,35 @@ describe('LogLevelFilter DiagLogger', () => {

levelMap.forEach(masterLevelMap => {
describe(`when diag logger is set to ${masterLevelMap.message}`, () => {
it('diag setLogLevel is not ignored and using default logger', () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(masterLevelMap.level);
it('diag.setLogger level is not ignored and using default logger', () => {
diag.setLogger(dummyLogger, masterLevelMap.level);

const testLogger = createLogLevelDiagLogger(map.level);
testLogger[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
if (
fName === lName &&
map.ignoreFuncs.indexOf(lName) === -1 &&
masterLevelMap.ignoreFuncs.indexOf(lName) === -1
map.ignoreFuncs.indexOf(lName) === -1
) {
assert.deepStrictEqual(calledArgs[lName], [
`${fName} called %s`,
'param1',
]);
} else {
assert.strictEqual(calledArgs[lName], null);
}
});
});

it('diag.setLogger level is ignored and using diag as the logger', () => {
diag.setLogger(dummyLogger, masterLevelMap.level);
diag.setLogger(diag, map.level);

diag[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
if (
fName === lName &&
map.ignoreFuncs.indexOf(lName) === -1
) {
assert.deepStrictEqual(calledArgs[lName], [
`${fName} called %s`,
Expand All @@ -221,13 +236,12 @@ describe('LogLevelFilter DiagLogger', () => {
});
});

it('diag setLogLevel is ignored when using a specific logger', () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(masterLevelMap.level);
it('diag.setLogger level is ignored when using a specific logger', () => {
diag.setLogger(dummyLogger, masterLevelMap.level);

const testLogger = createLogLevelDiagLogger(
map.level,
diag.getLogger()
diag.getLogger().getChild()
);
testLogger[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
Expand All @@ -247,9 +261,8 @@ describe('LogLevelFilter DiagLogger', () => {
});
});

it('diag setLogLevel and logger should log', () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(map.level);
it('diag.setLogger level and logger should log', () => {
diag.setLogger(dummyLogger, map.level);
diag[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
if (fName === lName && map.ignoreFuncs.indexOf(lName) === -1) {
Expand Down
4 changes: 2 additions & 2 deletions test/diag/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('DiagLogger functions', () => {
diagLoggerFunctions.forEach(fName => {
calledArgs[fName] = null;
});
diag.disable();
});

describe('constructor', () => {
Expand All @@ -68,8 +69,7 @@ describe('DiagLogger functions', () => {
});

it(`diag should log with ${fName} message`, () => {
diag.setLogger(dummyLogger);
diag.setLogLevel(DiagLogLevel.ALL);
diag.setLogger(dummyLogger, DiagLogLevel.ALL);
diag[fName](`${fName} called %s`, 'param1');
diagLoggerFunctions.forEach(lName => {
if (fName === lName) {
Expand Down