diff --git a/src/api/diag.ts b/src/api/diag.ts index b3136c96..768ad74c 100644 --- a/src/api/diag.ts +++ b/src/api/diag.ts @@ -14,8 +14,14 @@ * limitations under the License. */ +import { DiagComponentLogger } from '../diag/ComponentLogger'; import { createLogLevelDiagLogger } from '../diag/internal/logLevelLogger'; -import { DiagLogFunction, DiagLogger, DiagLogLevel } from '../diag/types'; +import { + ComponentLoggerOptions, + DiagLogFunction, + DiagLogger, + DiagLogLevel, +} from '../diag/types'; import { getGlobal, registerGlobal, @@ -90,6 +96,10 @@ export class DiagAPI implements DiagLogger { unregisterGlobal(API_NAME); }; + self.createComponentLogger = (options: ComponentLoggerOptions) => { + return new DiagComponentLogger(options); + }; + self.verbose = _logProxy('verbose'); self.debug = _logProxy('debug'); self.info = _logProxy('info'); @@ -106,6 +116,12 @@ export class DiagAPI implements DiagLogger { * @returns true if the logger was successfully registered, else false */ public setLogger!: (logger: DiagLogger, logLevel?: DiagLogLevel) => boolean; + /** + * + */ + public createComponentLogger!: ( + options: ComponentLoggerOptions + ) => DiagLogger; // DiagLogger implementation public verbose!: DiagLogFunction; diff --git a/src/diag/ComponentLogger.ts b/src/diag/ComponentLogger.ts new file mode 100644 index 00000000..aaddb9de --- /dev/null +++ b/src/diag/ComponentLogger.ts @@ -0,0 +1,70 @@ +/* + * 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 { getGlobal } from '../internal/global-utils'; +import { ComponentLoggerOptions, DiagLogger } from './types'; + +/** + * Component Logger which is meant to be used as part of any component which + * will add automatically additional namespace in front of the log message. + * It will then forward all message to global diag logger + * @example + * const cLogger = diag.createComponentLogger({ namespace: '@opentelemetry/instrumentation-http' }); + * cLogger.debug('test'); + * // @opentelemetry/instrumentation-http test + */ +export class DiagComponentLogger implements DiagLogger { + private _namespace: string; + + constructor(props: ComponentLoggerOptions) { + this._namespace = props.namespace || 'DiagComponentLogger'; + } + + public debug(...args: any[]): void { + return logProxy('debug', this._namespace, args); + } + + public error(...args: any[]): void { + return logProxy('error', this._namespace, args); + } + + public info(...args: any[]): void { + return logProxy('info', this._namespace, args); + } + + public warn(...args: any[]): void { + return logProxy('warn', this._namespace, args); + } + + public verbose(...args: any[]): void { + return logProxy('verbose', this._namespace, args); + } +} + +function logProxy( + funcName: keyof DiagLogger, + namespace: string, + args: any +): void { + const logger = getGlobal('diag'); + // shortcut if logger not set + if (!logger) { + return; + } + + args.unshift(namespace); + return logger[funcName].apply(logger, args); +} diff --git a/src/diag/types.ts b/src/diag/types.ts index 3a633a5b..54bf398d 100644 --- a/src/diag/types.ts +++ b/src/diag/types.ts @@ -89,3 +89,10 @@ export enum DiagLogLevel { /** Used to set the logging level to include all logging */ ALL = 9999, } + +/** + * Defines options for ComponentLogger + */ +export interface ComponentLoggerOptions { + namespace: string; +} diff --git a/test/diag/ComponentLogger.test.ts b/test/diag/ComponentLogger.test.ts new file mode 100644 index 00000000..bbc25b1e --- /dev/null +++ b/test/diag/ComponentLogger.test.ts @@ -0,0 +1,63 @@ +/* + * 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 * as assert from 'assert'; +import * as sinon from 'sinon'; +import { diag, DiagLogger, DiagLogLevel } from '../../src'; + +class SpyLogger implements DiagLogger { + debug() {} + + error() {} + + info() {} + + warn() {} + + verbose() {} +} + +const loggerFunctions = ['verbose', 'debug', 'info', 'warn', 'error']; + +describe('ComponentLogger', () => { + let logger: DiagLogger; + + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + logger = new SpyLogger(); + sandbox.spy(logger); + diag.setLogger(logger, DiagLogLevel.ALL); + }); + + afterEach(() => { + sandbox.restore(); + }); + + loggerFunctions.forEach(name => { + const fName = name as keyof SpyLogger; + it(`should call global logger function "${name}" with namespace as first param`, () => { + const componentLogger = diag.createComponentLogger({ namespace: 'foo' }); + componentLogger[fName]('test'); + + assert.strictEqual((logger[fName] as sinon.SinonSpy).callCount, 1); + assert.deepStrictEqual((logger[fName] as sinon.SinonSpy).args[0], [ + 'foo', + 'test', + ]); + }); + }); +});