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 all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Expand Up @@ -32,6 +32,7 @@ module.exports = {
"@typescript-eslint/no-inferrable-types": ["error", { ignoreProperties: true }],
"arrow-parens": ["error", "as-needed"],
"prettier/prettier": ["error", { "singleQuote": true, "arrowParens": "avoid" }],
"prefer-spread": "off",
"node/no-deprecated-api": ["warn"],
"header/header": [2, "block", [{
pattern: / \* Copyright The OpenTelemetry Authors[\r\n]+ \*[\r\n]+ \* Licensed under the Apache License, Version 2\.0 \(the \"License\"\);[\r\n]+ \* you may not use this file except in compliance with the License\.[\r\n]+ \* You may obtain a copy of the License at[\r\n]+ \*[\r\n]+ \* https:\/\/www\.apache\.org\/licenses\/LICENSE-2\.0[\r\n]+ \*[\r\n]+ \* Unless required by applicable law or agreed to in writing, software[\r\n]+ \* distributed under the License is distributed on an \"AS IS\" BASIS,[\r\n]+ \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.[\r\n]+ \* See the License for the specific language governing permissions and[\r\n]+ \* limitations under the License\./gm,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -16,7 +16,7 @@
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p .",
"compile": "tsc --build",
"docs-test": "linkinator docs/out --silent --skip david-dm.org",
"docs": "typedoc --tsconfig tsconfig.docs.json",
"docs": "typedoc",
"lint:fix": "eslint src test --ext .ts --fix",
"lint": "eslint src test --ext .ts",
"test:browser": "nyc karma start --single-run",
Expand Down
114 changes: 53 additions & 61 deletions src/api/diag.ts
Expand Up @@ -14,29 +14,24 @@
* limitations under the License.
*/

import {
DiagLogger,
DiagLogFunction,
createNoopDiagLogger,
diagLoggerFunctions,
} from '../diag/logger';
import { DiagLogLevel, createLogLevelDiagLogger } from '../diag/logLevel';
import { createLogLevelDiagLogger } from '../diag/internal/logLevelLogger';
import { createNoopDiagLogger } from '../diag/internal/noopLogger';
import { DiagLogFunction, DiagLogger, DiagLogLevel } from '../diag/types';
import {
API_BACKWARDS_COMPATIBILITY_VERSION,
GLOBAL_DIAG_LOGGER_API_KEY,
makeGetter,
_global,
} from './global-utils';

function nop() {}

/** 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;
return Object.assign(
{ disable: nop, setLogger: nop },
createNoopDiagLogger()
);
}

/**
Expand Down Expand Up @@ -71,21 +66,18 @@ export class DiagAPI implements DiagLogger {
* @private
*/
private constructor() {
let _logLevel: DiagLogLevel = DiagLogLevel.INFO;
let _filteredLogger: DiagLogger | null;
let _logger: DiagLogger = createNoopDiagLogger();
let _filteredLogger: DiagLogger | undefined;

function _logProxy(funcName: keyof DiagLogger): DiagLogFunction {
return function () {
const orgArguments = arguments as unknown;
const theLogger = _filteredLogger || _logger;
const theFunc = theLogger[funcName];
if (typeof theFunc === 'function') {
return theFunc.apply(
theLogger,
orgArguments as Parameters<DiagLogFunction>
);
}
// shortcut if logger not set
if (!_filteredLogger) return;
return _filteredLogger[funcName].apply(
_filteredLogger,
// work around Function.prototype.apply types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
arguments as any
);
};
}

Expand All @@ -94,57 +86,57 @@ 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.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);
self.setLogger = (
logger: DiagLogger,
logLevel: DiagLogLevel = DiagLogLevel.INFO
) => {
if (logger === self) {
if (_filteredLogger) {
const err = new Error(
'Cannot use diag as the logger for itself. Please use a DiagLogger implementation like ConsoleDiagLogger or a custom implementation'
);
_filteredLogger.error(err.stack ?? err.message);
logger = _filteredLogger;
} else {
// There isn't much we can do here.
// Logging to the console might break the user application.
return;
}
}

return prevLogger;
_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++) {
const name = diagLoggerFunctions[i];
self[name] = _logProxy(name);
}
self.verbose = _logProxy('verbose');
self.debug = _logProxy('debug');
self.info = _logProxy('info');
self.warn = _logProxy('warn');
self.error = _logProxy('error');
}

/**
* 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;

/**
* 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;
}
2 changes: 1 addition & 1 deletion src/diag/consoleLogger.ts
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { DiagLogger, DiagLogFunction } from './logger';
import { DiagLogger, DiagLogFunction } from './types';

const consoleMap: { n: keyof DiagLogger; c: keyof Console }[] = [
{ n: 'error', c: 'error' },
Expand Down
18 changes: 18 additions & 0 deletions src/diag/index.ts
@@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './consoleLogger';
export * from './types';
51 changes: 51 additions & 0 deletions src/diag/internal/logLevelLogger.ts
@@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DiagLogFunction, DiagLogger, DiagLogLevel } from '../types';

export function createLogLevelDiagLogger(
maxLevel: DiagLogLevel,
logger: DiagLogger
): DiagLogger {
if (maxLevel < DiagLogLevel.NONE) {
maxLevel = DiagLogLevel.NONE;
} else if (maxLevel > DiagLogLevel.ALL) {
maxLevel = DiagLogLevel.ALL;
}

// In case the logger is null or undefined
logger = logger || {};

function _filterFunc(
funcName: keyof DiagLogger,
theLevel: DiagLogLevel
): DiagLogFunction {
const theFunc = logger[funcName];

if (typeof theFunc === 'function' && maxLevel >= theLevel) {
return theFunc.bind(logger);
}
return function () {};
}

return {
error: _filterFunc('error', DiagLogLevel.ERROR),
warn: _filterFunc('warn', DiagLogLevel.WARN),
info: _filterFunc('info', DiagLogLevel.INFO),
debug: _filterFunc('debug', DiagLogLevel.DEBUG),
verbose: _filterFunc('verbose', DiagLogLevel.VERBOSE),
};
}
34 changes: 34 additions & 0 deletions src/diag/internal/noopLogger.ts
@@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DiagLogger } from '../types';

function noopLogFunction() {}

/**
* Returns a No-Op Diagnostic logger where all messages do nothing.
* @implements {@link DiagLogger}
* @returns {DiagLogger}
*/
export function createNoopDiagLogger(): DiagLogger {
return {
verbose: noopLogFunction,
debug: noopLogFunction,
info: noopLogFunction,
warn: noopLogFunction,
error: noopLogFunction,
};
}