diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index 126e23ce3..72571282e 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -15,8 +15,8 @@ */ import sinon from 'sinon'; import { assert } from 'chai'; -import { getLogger } from '@optimizely/js-sdk-logging'; import { sprintf } from '../../utils/fns'; +import { getLogger } from '../../modules/logging'; import { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts b/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts index be6e8bfcd..87b332103 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016, 2018-2021, Optimizely + * Copyright 2016, 2018-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '@optimizely/js-sdk-logging'; +import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import { diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.ts b/packages/optimizely-sdk/lib/core/bucketer/index.ts index 82d7f5bca..3931d1b02 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.ts +++ b/packages/optimizely-sdk/lib/core/bucketer/index.ts @@ -19,7 +19,7 @@ */ import { sprintf } from '../../utils/fns'; import murmurhash from 'murmurhash'; -import { LogHandler } from '@optimizely/js-sdk-logging'; +import { LogHandler } from '../../modules/logging'; import { DecisionResponse, BucketerParams, diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js index 55a8e0dbe..8981741c7 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -21,7 +21,7 @@ import { LOG_LEVEL, LOG_MESSAGES, } from '../../utils/enums'; -import * as logging from '@optimizely/js-sdk-logging'; +import * as logging from '../../modules/logging'; import * as customAttributeEvaluator from './'; var browserConditionSafari = { diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts index f236533de..24f412652 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2018-2019, 2020 Optimizely, Inc. and contributors * + * Copyright 2018-2019, 2020, 2022, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { getLogger } from '@optimizely/js-sdk-logging'; +import { getLogger } from '../../modules/logging'; import { UserAttributes, Condition } from '../../shared_types'; import fns from '../../utils/fns'; diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.ts b/packages/optimizely-sdk/lib/core/decision_service/index.ts index 546aa5c23..495ea412d 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.ts +++ b/packages/optimizely-sdk/lib/core/decision_service/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { LogHandler } from '@optimizely/js-sdk-logging'; +import { LogHandler } from '../../modules/logging'; import { sprintf } from '../../utils/fns'; import fns from '../../utils/fns'; diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts index 9d2ae4609..e6f57fdbd 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2021, Optimizely + * Copyright 2019-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '@optimizely/js-sdk-logging'; +import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import * as eventTagUtils from '../../utils/event_tag_utils'; diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.ts b/packages/optimizely-sdk/lib/core/event_builder/index.ts index 4c85c5fda..7a115b357 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2021, Optimizely + * Copyright 2016-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade } from '@optimizely/js-sdk-logging'; +import { LoggerFacade } from '../../modules/logging'; import { EventV1 as CommonEventParams } from '@optimizely/js-sdk-event-processor'; import fns from '../../utils/fns'; diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.ts b/packages/optimizely-sdk/lib/core/notification_center/index.ts index d54e30f84..34a3320bc 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; +import { LogHandler, ErrorHandler } from '../../modules/logging'; import { objectValues } from '../../utils/fns'; import { NotificationListener, ListenerPayload } from '../../shared_types'; diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index 401eefb4d..6f22e3595 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -16,8 +16,8 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { forEach, cloneDeep } from 'lodash'; -import { getLogger } from '@optimizely/js-sdk-logging'; import { sprintf } from '../../utils/fns'; +import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import projectConfig from './'; diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/packages/optimizely-sdk/lib/core/project_config/index.ts index 9f04289be..c8b9ec4bd 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.ts @@ -30,7 +30,7 @@ import { } from '../../utils/enums'; import configValidator from '../../utils/config_validator'; -import { LogHandler } from '@optimizely/js-sdk-logging'; +import { LogHandler } from '../../modules/logging'; import { Audience, Experiment, diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js index 387c9aee8..25969facc 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js @@ -17,8 +17,8 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { cloneDeep } from 'lodash'; -import * as logging from '@optimizely/js-sdk-logging'; import { sprintf } from '../../utils/fns'; +import * as logging from '../../modules/logging'; import * as datafileManager from '@optimizely/js-sdk-datafile-manager'; import * as projectConfig from './index'; diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts index 961a79b89..d3bca799f 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '@optimizely/js-sdk-logging'; +import { getLogger } from '../../modules/logging'; import { sprintf } from '../../utils/fns'; import { ERROR_MESSAGES } from '../../utils/enums'; diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 6c6beddc5..69822f46c 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as logging from '@optimizely/js-sdk-logging'; +import logging from './modules/logging/logger'; import { assert } from 'chai'; import sinon from 'sinon'; diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts index 7411fc22a..0d3098a82 100644 --- a/packages/optimizely-sdk/lib/index.browser.ts +++ b/packages/optimizely-sdk/lib/index.browser.ts @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import logHelper from './modules/logging/logger'; import { getLogger, - setLogHandler, - setLogLevel, setErrorHandler, getErrorHandler, LogLevel -} from '@optimizely/js-sdk-logging'; +} from './modules/logging'; import { LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; @@ -35,8 +34,8 @@ import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager'; const logger = getLogger(); -setLogHandler(loggerPlugin.createLogger()); -setLogLevel(LogLevel.INFO); +logHelper.setLogHandler(loggerPlugin.createLogger()); +logHelper.setLogLevel(LogLevel.INFO); const MODULE_NAME = 'INDEX_BROWSER'; const DEFAULT_EVENT_BATCH_SIZE = 10; @@ -60,12 +59,12 @@ const createInstance = function(config: Config): Client | null { setErrorHandler(config.errorHandler); } if (config.logger) { - setLogHandler(config.logger); + logHelper.setLogHandler(config.logger); // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); + logHelper.setLogLevel(LogLevel.NOTSET); } if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); + logHelper.setLogLevel(config.logLevel); } try { @@ -160,6 +159,9 @@ const __internalResetRetryState = function(): void { /** * Entry point into the Optimizely Browser SDK */ + +const setLogHandler = logHelper.setLogHandler +const setLogLevel = logHelper.setLogLevel export { loggerPlugin as logging, defaultErrorHandler as errorHandler, diff --git a/packages/optimizely-sdk/lib/index.lite.ts b/packages/optimizely-sdk/lib/index.lite.ts index 3e46b8878..c688f9588 100644 --- a/packages/optimizely-sdk/lib/index.lite.ts +++ b/packages/optimizely-sdk/lib/index.lite.ts @@ -15,12 +15,12 @@ */ import { getLogger, - setLogHandler, - setLogLevel, setErrorHandler, getErrorHandler, - LogLevel - } from '@optimizely/js-sdk-logging'; + LogLevel, + setLogHandler, + setLogLevel + } from './modules/logging'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import noOpEventDispatcher from './plugins/event_dispatcher/no_op'; diff --git a/packages/optimizely-sdk/lib/index.node.ts b/packages/optimizely-sdk/lib/index.node.ts index 6c1a71f20..b628288b4 100644 --- a/packages/optimizely-sdk/lib/index.node.ts +++ b/packages/optimizely-sdk/lib/index.node.ts @@ -15,12 +15,12 @@ ***************************************************************************/ import { getLogger, - setLogHandler, - setLogLevel, setErrorHandler, getErrorHandler, - LogLevel -} from '@optimizely/js-sdk-logging'; + LogLevel, + setLogHandler, + setLogLevel +} from './modules/logging'; import Optimizely from './optimizely'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; diff --git a/packages/optimizely-sdk/lib/index.react_native.tests.js b/packages/optimizely-sdk/lib/index.react_native.tests.js index 098fa19fb..7fcd5728b 100644 --- a/packages/optimizely-sdk/lib/index.react_native.tests.js +++ b/packages/optimizely-sdk/lib/index.react_native.tests.js @@ -15,7 +15,7 @@ */ import { assert } from 'chai'; import sinon from 'sinon'; -import * as logging from '@optimizely/js-sdk-logging'; +import * as logging from './modules/logging/logger'; import * as eventProcessor from './plugins/event_processor'; import Optimizely from './optimizely'; diff --git a/packages/optimizely-sdk/lib/index.react_native.ts b/packages/optimizely-sdk/lib/index.react_native.ts index 67f7f46be..d6b377580 100644 --- a/packages/optimizely-sdk/lib/index.react_native.ts +++ b/packages/optimizely-sdk/lib/index.react_native.ts @@ -15,12 +15,12 @@ */ import { getLogger, - setLogHandler, - setLogLevel, setErrorHandler, getErrorHandler, - LogLevel -} from '@optimizely/js-sdk-logging'; + LogLevel, + setLogHandler, + setLogLevel +} from './modules/logging'; import * as enums from './utils/enums'; import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; diff --git a/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts b/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts new file mode 100644 index 000000000..bb659aeae --- /dev/null +++ b/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2019, Optimizely + * + * 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 + * + * http://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 + * @interface ErrorHandler + */ +export interface ErrorHandler { + /** + * @param {Error} exception + * @memberof ErrorHandler + */ + handleError(exception: Error): void +} + +/** + * @export + * @class NoopErrorHandler + * @implements {ErrorHandler} + */ +export class NoopErrorHandler implements ErrorHandler { + /** + * @param {Error} exception + * @memberof NoopErrorHandler + */ + handleError(exception: Error): void { + // no-op + return + } +} + +let globalErrorHandler: ErrorHandler = new NoopErrorHandler() + +/** + * @export + * @param {ErrorHandler} handler + */ +export function setErrorHandler(handler: ErrorHandler): void { + globalErrorHandler = handler +} + +/** + * @export + * @returns {ErrorHandler} + */ +export function getErrorHandler(): ErrorHandler { + return globalErrorHandler +} + +/** + * @export + */ +export function resetErrorHandler(): void { + globalErrorHandler = new NoopErrorHandler() +} diff --git a/packages/optimizely-sdk/lib/modules/logging/index.ts b/packages/optimizely-sdk/lib/modules/logging/index.ts new file mode 100644 index 000000000..47a1e99c8 --- /dev/null +++ b/packages/optimizely-sdk/lib/modules/logging/index.ts @@ -0,0 +1,18 @@ +/** + * Copyright 2019, Optimizely + * + * 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 + * + * http://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 './errorHandler' +export * from './models' +export * from './logger' diff --git a/packages/optimizely-sdk/lib/modules/logging/logger.ts b/packages/optimizely-sdk/lib/modules/logging/logger.ts new file mode 100644 index 000000000..e58664fb1 --- /dev/null +++ b/packages/optimizely-sdk/lib/modules/logging/logger.ts @@ -0,0 +1,333 @@ +/** + * Copyright 2019, Optimizely + * + * 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 + * + * http://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 { getErrorHandler } from './errorHandler' +import { isValidEnum, sprintf } from '../../utils/fns' + +import { LogLevel, LoggerFacade, LogManager, LogHandler } from './models' + +type StringToLogLevel = { + NOTSET: number, + DEBUG: number, + INFO: number, + WARNING: number, + ERROR: number, +} + +const stringToLogLevel: StringToLogLevel = { + NOTSET: 0, + DEBUG: 1, + INFO: 2, + WARNING: 3, + ERROR: 4, +} + +function coerceLogLevel(level: any): LogLevel { + if (typeof level !== 'string') { + return level + } + + level = level.toUpperCase() + if (level === 'WARN') { + level = 'WARNING' + } + + if (!stringToLogLevel[level as keyof StringToLogLevel]) { + return level + } + + return stringToLogLevel[level as keyof StringToLogLevel] +} + +type LogData = { + message: string + splat: any[] + error?: Error +} + +class DefaultLogManager implements LogManager { + private loggers: { + [name: string]: LoggerFacade + } + private defaultLoggerFacade = new OptimizelyLogger() + + constructor() { + this.loggers = {} + } + + getLogger(name?: string): LoggerFacade { + if (!name) { + return this.defaultLoggerFacade + } + + if (!this.loggers[name]) { + this.loggers[name] = new OptimizelyLogger({ messagePrefix: name }) + } + + return this.loggers[name] + } +} + +type ConsoleLogHandlerConfig = { + logLevel?: LogLevel | string + logToConsole?: boolean + prefix?: string +} + +export class ConsoleLogHandler implements LogHandler { + public logLevel: LogLevel + private logToConsole: boolean + private prefix: string + + /** + * Creates an instance of ConsoleLogger. + * @param {ConsoleLogHandlerConfig} config + * @memberof ConsoleLogger + */ + constructor(config: ConsoleLogHandlerConfig = {}) { + this.logLevel = LogLevel.NOTSET + if (config.logLevel !== undefined && isValidEnum(LogLevel, config.logLevel)) { + this.setLogLevel(config.logLevel) + } + + this.logToConsole = config.logToConsole !== undefined ? !!config.logToConsole : true + this.prefix = config.prefix !== undefined ? config.prefix : '[OPTIMIZELY]' + } + + /** + * @param {LogLevel} level + * @param {string} message + * @memberof ConsoleLogger + */ + log(level: LogLevel, message: string) : void { + if (!this.shouldLog(level) || !this.logToConsole) { + return + } + + const logMessage = `${this.prefix} - ${this.getLogLevelName( + level, + )} ${this.getTime()} ${message}` + + this.consoleLog(level, [logMessage]) + } + + /** + * @param {LogLevel} level + * @memberof ConsoleLogger + */ + setLogLevel(level: LogLevel | string) : void { + level = coerceLogLevel(level) + if (!isValidEnum(LogLevel, level) || level === undefined) { + this.logLevel = LogLevel.ERROR + } else { + this.logLevel = level + } + } + + /** + * @returns {string} + * @memberof ConsoleLogger + */ + getTime(): string { + return new Date().toISOString() + } + + /** + * @private + * @param {LogLevel} targetLogLevel + * @returns {boolean} + * @memberof ConsoleLogger + */ + private shouldLog(targetLogLevel: LogLevel): boolean { + return targetLogLevel >= this.logLevel + } + + /** + * @private + * @param {LogLevel} logLevel + * @returns {string} + * @memberof ConsoleLogger + */ + private getLogLevelName(logLevel: LogLevel): string { + switch (logLevel) { + case LogLevel.DEBUG: + return 'DEBUG' + case LogLevel.INFO: + return 'INFO ' + case LogLevel.WARNING: + return 'WARN ' + case LogLevel.ERROR: + return 'ERROR' + default: + return 'NOTSET' + } + } + + /** + * @private + * @param {LogLevel} logLevel + * @param {string[]} logArguments + * @memberof ConsoleLogger + */ + private consoleLog(logLevel: LogLevel, logArguments: [string, ...string[]]) { + switch (logLevel) { + case LogLevel.DEBUG: + console.log(...logArguments) + break + case LogLevel.INFO: + console.info(...logArguments) + break + case LogLevel.WARNING: + console.warn(...logArguments) + break + case LogLevel.ERROR: + console.error(...logArguments) + break + default: + console.log(...logArguments) + } + } +} + +let globalLogLevel: LogLevel = LogLevel.NOTSET +let globalLogHandler: LogHandler | null = null + +class OptimizelyLogger implements LoggerFacade { + private messagePrefix = '' + + constructor(opts: { messagePrefix?: string } = {}) { + if (opts.messagePrefix) { + this.messagePrefix = opts.messagePrefix + } + } + + /** + * @param {(LogLevel | LogInputObject)} levelOrObj + * @param {string} [message] + * @memberof OptimizelyLogger + */ + log(level: LogLevel | string, message: string, ...splat: any[]): void { + this.internalLog(coerceLogLevel(level), { + message, + splat, + }) + } + + info(message: string | Error, ...splat: any[]): void { + this.namedLog(LogLevel.INFO, message, splat) + } + + debug(message: string | Error, ...splat: any[]): void { + this.namedLog(LogLevel.DEBUG, message, splat) + } + + warn(message: string | Error, ...splat: any[]): void { + this.namedLog(LogLevel.WARNING, message, splat) + } + + error(message: string | Error, ...splat: any[]): void { + this.namedLog(LogLevel.ERROR, message, splat) + } + + private format(data: LogData): string { + return `${this.messagePrefix ? this.messagePrefix + ': ' : ''}${sprintf( + data.message, + ...data.splat, + )}` + } + + private internalLog(level: LogLevel, data: LogData): void { + if (!globalLogHandler) { + return + } + + if (level < globalLogLevel) { + return + } + + globalLogHandler.log(level, this.format(data)) + + if (data.error && data.error instanceof Error) { + getErrorHandler().handleError(data.error) + } + } + + private namedLog(level: LogLevel, message: string | Error, splat: any[]): void { + let error: Error | undefined + + if (message instanceof Error) { + error = message + message = error.message + this.internalLog(level, { + error, + message, + splat, + }) + return + } + + if (splat.length === 0) { + this.internalLog(level, { + message, + splat, + }) + return + } + + const last = splat[splat.length - 1] + if (last instanceof Error) { + error = last + splat.splice(-1) + } + + this.internalLog(level, { message, error, splat }) + } +} + +let globalLogManager: LogManager = new DefaultLogManager() + +export function getLogger(name?: string): LoggerFacade { + return globalLogManager.getLogger(name) +} + +export function setLogHandler(logger: LogHandler | null) : void { + globalLogHandler = logger +} + +export function setLogLevel(level: LogLevel | string) : void { + level = coerceLogLevel(level) + if (!isValidEnum(LogLevel, level) || level === undefined) { + globalLogLevel = LogLevel.ERROR + } else { + globalLogLevel = level + } +} + +export function getLogLevel(): LogLevel { + return globalLogLevel +} + +/** + * Resets all global logger state to it's original + */ +export function resetLogger() : void { + globalLogManager = new DefaultLogManager() + globalLogLevel = LogLevel.NOTSET +} + +export default { + setLogLevel: setLogLevel, + setLogHandler: setLogHandler +} diff --git a/packages/optimizely-sdk/lib/modules/logging/models.ts b/packages/optimizely-sdk/lib/modules/logging/models.ts new file mode 100644 index 000000000..cd3223932 --- /dev/null +++ b/packages/optimizely-sdk/lib/modules/logging/models.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2019, Optimizely + * + * 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 + * + * http://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 enum LogLevel { + NOTSET = 0, + DEBUG = 1, + INFO = 2, + WARNING = 3, + ERROR = 4, +} + +export interface LoggerFacade { + log(level: LogLevel | string, message: string, ...splat: any[]): void + + info(message: string | Error, ...splat: any[]): void + + debug(message: string | Error, ...splat: any[]): void + + warn(message: string | Error, ...splat: any[]): void + + error(message: string | Error, ...splat: any[]): void +} + +export interface LogManager { + getLogger(name?: string): LoggerFacade +} + +export interface LogHandler { + log(level: LogLevel, message: string, ...splat: any[]): void +} diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 2c0a7be28..2a4c45f1c 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -18,7 +18,7 @@ import sinon from 'sinon'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../utils/enums'; import eventProcessor from '../plugins/event_processor'; -import * as logging from '@optimizely/js-sdk-logging'; +import * as logging from '../modules/logging'; import Optimizely from './'; import OptimizelyUserContext from '../optimizely_user_context'; diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 23fbeec6c..d65fa3fa0 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { LoggerFacade, ErrorHandler } from '@optimizely/js-sdk-logging'; +import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; import { EventProcessor } from '@optimizely/js-sdk-event-processor'; diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index 6b9e1ccf0..3e7f9a746 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -16,7 +16,7 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import * as logging from '@optimizely/js-sdk-logging'; +import * as logging from '../modules/logging'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../utils/enums'; diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts index 42f993145..32b3e9905 100644 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts +++ b/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade } from '@optimizely/js-sdk-logging'; +import { LoggerFacade } from '../../modules/logging'; import { HttpPollingDatafileManager } from '@optimizely/js-sdk-datafile-manager'; import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts b/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts index ce8340e4c..5d5ee8ae7 100644 --- a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts +++ b/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LogLevel } from '@optimizely/js-sdk-logging'; +import { LogLevel } from '../../modules/logging'; import { sprintf } from '../../utils/fns'; import { NoOpLogger } from './index'; diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.ts b/packages/optimizely-sdk/lib/plugins/logger/index.ts index d0e09c0de..a9a24e7bb 100644 --- a/packages/optimizely-sdk/lib/plugins/logger/index.ts +++ b/packages/optimizely-sdk/lib/plugins/logger/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2020-2021, Optimizely + * Copyright 2016-2017, 2020-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ConsoleLogHandler, LogLevel } from '@optimizely/js-sdk-logging'; +import { ConsoleLogHandler, LogLevel } from '../../modules/logging'; type ConsoleLogHandlerConfig = { logLevel?: LogLevel | string; diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts b/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts index 86c37ad2a..d47ab1944 100644 --- a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts +++ b/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2017, 2019-2020 Optimizely + * Copyright 2017, 2019-2020, 2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ import { EventTags } from '@optimizely/js-sdk-event-processor'; -import { LoggerFacade } from '@optimizely/js-sdk-logging'; +import { LoggerFacade } from '../../modules/logging'; import { LOG_LEVEL, diff --git a/packages/optimizely-sdk/lib/utils/semantic_version/index.ts b/packages/optimizely-sdk/lib/utils/semantic_version/index.ts index bb92013a0..e8ef11c7f 100644 --- a/packages/optimizely-sdk/lib/utils/semantic_version/index.ts +++ b/packages/optimizely-sdk/lib/utils/semantic_version/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, Optimizely + * Copyright 2020, 2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '@optimizely/js-sdk-logging'; +import { getLogger } from '../../modules/logging'; import { VERSION_TYPE, LOG_MESSAGES } from '../enums'; const MODULE_NAME = 'SEMANTIC VERSION'; diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 6a6689b30..db780860d 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -42,7 +42,6 @@ "dependencies": { "@optimizely/js-sdk-datafile-manager": "^0.9.5", "@optimizely/js-sdk-event-processor": "^0.9.2", - "@optimizely/js-sdk-logging": "^0.3.1", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", "uuid": "^8.3.2" diff --git a/packages/optimizely-sdk/rollup.config.js b/packages/optimizely-sdk/rollup.config.js index 92c0aa20b..7afe33aea 100644 --- a/packages/optimizely-sdk/rollup.config.js +++ b/packages/optimizely-sdk/rollup.config.js @@ -75,15 +75,6 @@ const umdBundle = { resolve({ browser: true }), commonjs({ namedExports: { - '@optimizely/js-sdk-logging': [ - 'ConsoleLogHandler', - 'getLogger', - 'setLogLevel', - 'LogLevel', - 'setLogHandler', - 'setErrorHandler', - 'getErrorHandler', - ], '@optimizely/js-sdk-event-processor': ['LogTierV1EventProcessor', 'LocalStoragePendingEventsDispatcher'], }, }), diff --git a/packages/optimizely-sdk/tests/logger.spec.ts b/packages/optimizely-sdk/tests/logger.spec.ts new file mode 100644 index 000000000..f34afbb0e --- /dev/null +++ b/packages/optimizely-sdk/tests/logger.spec.ts @@ -0,0 +1,386 @@ +/// +import { + LogLevel, + LogHandler, + LoggerFacade, +} from '../lib/modules/logging/models' + +import { + setLogHandler, + setLogLevel, + getLogger, + ConsoleLogHandler, + resetLogger, + getLogLevel, +} from '../lib/modules/logging/logger' + +import { resetErrorHandler } from '../lib/modules/logging/errorHandler' +import { ErrorHandler, setErrorHandler } from '../lib/modules/logging/errorHandler' + +describe('logger', () => { + afterEach(() => { + resetLogger() + resetErrorHandler() + }) + + describe('OptimizelyLogger', () => { + let stubLogger: LogHandler + let logger: LoggerFacade + let stubErrorHandler: ErrorHandler + + beforeEach(() => { + stubLogger = { + log: jest.fn(), + } + stubErrorHandler = { + handleError: jest.fn(), + } + setLogLevel(LogLevel.DEBUG) + setLogHandler(stubLogger) + setErrorHandler(stubErrorHandler) + logger = getLogger() + }) + + describe('setLogLevel', () => { + it('should coerce "debug"', () => { + setLogLevel('debug') + expect(getLogLevel()).toBe(LogLevel.DEBUG) + }) + + it('should coerce "deBug"', () => { + setLogLevel('deBug') + expect(getLogLevel()).toBe(LogLevel.DEBUG) + }) + + it('should coerce "INFO"', () => { + setLogLevel('INFO') + expect(getLogLevel()).toBe(LogLevel.INFO) + }) + + it('should coerce "WARN"', () => { + setLogLevel('WARN') + expect(getLogLevel()).toBe(LogLevel.WARNING) + }) + + it('should coerce "warning"', () => { + setLogLevel('warning') + expect(getLogLevel()).toBe(LogLevel.WARNING) + }) + + it('should coerce "ERROR"', () => { + setLogLevel('WARN') + expect(getLogLevel()).toBe(LogLevel.WARNING) + }) + + it('should default to error if invalid', () => { + setLogLevel('invalid') + expect(getLogLevel()).toBe(LogLevel.ERROR) + }) + }) + + describe('getLogger(name)', () => { + it('should prepend the name in the log messages', () => { + const myLogger = getLogger('doit') + myLogger.info('test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') + }) + }) + + describe('logger.log(level, msg)', () => { + it('should work with a string logLevel', () => { + setLogLevel(LogLevel.INFO) + logger.log('info', 'test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') + }) + + it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { + setLogLevel(LogLevel.INFO) + logger.log(LogLevel.INFO, 'test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') + }) + + it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { + setLogLevel(LogLevel.INFO) + logger.log(LogLevel.WARNING, 'test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') + }) + + it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { + setLogLevel(LogLevel.INFO) + logger.log(LogLevel.DEBUG, 'test') + + expect(stubLogger.log).toHaveBeenCalledTimes(0) + }) + + it('should not throw if loggerBackend is not supplied', () => { + setLogLevel(LogLevel.INFO) + logger.log(LogLevel.ERROR, 'test') + }) + }) + + describe('logger.info', () => { + it('should handle info(message)', () => { + logger.info('test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') + }) + it('should handle info(message, ...splat)', () => { + logger.info('test: %s %s', 'hey', 'jude') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') + }) + + it('should handle info(message, ...splat, error)', () => { + const error = new Error('hey') + logger.info('test: %s', 'hey', error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + + it('should handle info(error)', () => { + const error = new Error('hey') + logger.info(error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + }) + + describe('logger.debug', () => { + it('should handle debug(message)', () => { + logger.debug('test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') + }) + + it('should handle debug(message, ...splat)', () => { + logger.debug('test: %s', 'hey') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') + }) + + it('should handle debug(message, ...splat, error)', () => { + const error = new Error('hey') + logger.debug('test: %s', 'hey', error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + + it('should handle debug(error)', () => { + const error = new Error('hey') + logger.debug(error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + }) + + describe('logger.warn', () => { + it('should handle warn(message)', () => { + logger.warn('test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') + }) + + it('should handle warn(message, ...splat)', () => { + logger.warn('test: %s', 'hey') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') + }) + + it('should handle warn(message, ...splat, error)', () => { + const error = new Error('hey') + logger.warn('test: %s', 'hey', error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + + it('should handle info(error)', () => { + const error = new Error('hey') + logger.warn(error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + }) + + describe('logger.error', () => { + it('should handle error(message)', () => { + logger.error('test') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') + }) + + it('should handle error(message, ...splat)', () => { + logger.error('test: %s', 'hey') + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') + }) + + it('should handle error(message, ...splat, error)', () => { + const error = new Error('hey') + logger.error('test: %s', 'hey', error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + + it('should handle error(error)', () => { + const error = new Error('hey') + logger.error(error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + + it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { + const error = new Error('hey') + logger.error('hey %s', error) + + expect(stubLogger.log).toHaveBeenCalledTimes(1) + expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') + expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) + }) + }) + + describe('using ConsoleLoggerHandler', () => { + beforeEach(() => { + jest.spyOn(console, 'info').mockImplementation(() => {}) + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('should work with BasicLogger', () => { + const logger = new ConsoleLogHandler() + const TIME = '12:00' + setLogHandler(logger) + setLogLevel(LogLevel.INFO) + jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + + logger.log(LogLevel.INFO, 'hey') + + expect(console.info).toBeCalledTimes(1) + expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') + }) + + it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { + const logger = new ConsoleLogHandler() + logger.setLogLevel('invalid' as any) + + expect(logger.logLevel).toEqual(LogLevel.ERROR) + }) + + it('should set logLevel to ERROR when setLogLevel is called with no value', () => { + const logger = new ConsoleLogHandler() + // @ts-ignore + logger.setLogLevel() + + expect(logger.logLevel).toEqual(LogLevel.ERROR) + }) + }) + }) + + describe('ConsoleLogger', function() { + beforeEach(() => { + jest.spyOn(console, 'info') + jest.spyOn(console, 'log') + jest.spyOn(console, 'warn') + jest.spyOn(console, 'error') + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('should log to console.info for LogLevel.INFO', () => { + const logger = new ConsoleLogHandler({ + logLevel: LogLevel.DEBUG, + }) + const TIME = '12:00' + jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + + logger.log(LogLevel.INFO, 'test') + + expect(console.info).toBeCalledTimes(1) + expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') + }) + + it('should log to console.log for LogLevel.DEBUG', () => { + const logger = new ConsoleLogHandler({ + logLevel: LogLevel.DEBUG, + }) + const TIME = '12:00' + jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + + logger.log(LogLevel.DEBUG, 'debug') + + expect(console.log).toBeCalledTimes(1) + expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') + }) + + it('should log to console.warn for LogLevel.WARNING', () => { + const logger = new ConsoleLogHandler({ + logLevel: LogLevel.DEBUG, + }) + const TIME = '12:00' + jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + + logger.log(LogLevel.WARNING, 'warning') + + expect(console.warn).toBeCalledTimes(1) + expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') + }) + + it('should log to console.error for LogLevel.ERROR', () => { + const logger = new ConsoleLogHandler({ + logLevel: LogLevel.DEBUG, + }) + const TIME = '12:00' + jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + + logger.log(LogLevel.ERROR, 'error') + + expect(console.error).toBeCalledTimes(1) + expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') + }) + + it('should not log if the configured logLevel is higher', () => { + const logger = new ConsoleLogHandler({ + logLevel: LogLevel.INFO, + }) + + logger.log(LogLevel.DEBUG, 'debug') + + expect(console.log).toBeCalledTimes(0) + }) + }) +})