UNPKG

@itwin/core-bentley

Version:

Bentley JavaScript core components

299 lines • 14.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Logging */ import { BeEvent } from "./BeEvent"; import { BentleyError, IModelStatus } from "./BentleyError"; import { BentleyLoggerCategory } from "./BentleyLoggerCategory"; /** Use to categorize logging messages by severity. * @public */ export var LogLevel; (function (LogLevel) { /** Tracing and debugging - low level */ LogLevel[LogLevel["Trace"] = 0] = "Trace"; /** Information - mid level */ LogLevel[LogLevel["Info"] = 1] = "Info"; /** Warnings - high level */ LogLevel[LogLevel["Warning"] = 2] = "Warning"; /** Errors - highest level */ LogLevel[LogLevel["Error"] = 3] = "Error"; /** Higher than any real logging level. This is used to turn a category off. */ LogLevel[LogLevel["None"] = 4] = "None"; })(LogLevel || (LogLevel = {})); /** Logger allows libraries and apps to report potentially useful information about operations, and it allows apps and users to control * how or if the logged information is displayed or collected. See [Learning about Logging]($docs/learning/common/Logging.md). * @public */ export class Logger { static _logError; static _logWarning; static _logInfo; static _logTrace; static _onLogLevelChanged; static _staticMetaData = new Map(); /** An event raised whenever [[setLevel]] or [[setLevelDefault]] is called. */ static get onLogLevelChanged() { // We have to lazily initialize because it's static and BeEvent imports UnexpectedErrors which imports Logger which wants to instantiate BeEvent. if (undefined === Logger._onLogLevelChanged) { Logger._onLogLevelChanged = new BeEvent(); } return Logger._onLogLevelChanged; } static _categoryFilter = {}; /** Maps category names to the least severe level at which messages in that category should be displayed, * or `undefined` if a minimum has not been defined. * @see [[setLevel]] to change the minimum logging level for a category. */ static get categoryFilter() { // NOTE: this property is accessed by native code. return this._categoryFilter; } static _minLevel; /** The least severe level at which messages should be displayed by default. * @see [[setLevelDefault]] to change this default. * @see [[setLevel]] to override this default for specific categories. */ static get minLevel() { // NOTE: this property is accessed by native code. */ return this._minLevel; } /** Should the call stack be included when an exception is logged? */ static logExceptionCallstacks = false; /** Contains metadata that should be included with every logged message. * @beta */ static get staticMetaData() { return this._staticMetaData; } /** Initialize the logger streams. Should be called at application initialization time. */ static initialize(logError, logWarning, logInfo, logTrace) { Logger._logError = logError; Logger._logWarning = logWarning; Logger._logInfo = logInfo; Logger._logTrace = logTrace; Logger.turnOffLevelDefault(); Logger.turnOffCategories(); } /** Initialize the logger to output to the console. */ static initializeToConsole() { const logConsole = (level) => (category, message, metaData) => console.log(`${level} | ${category} | ${message} ${Logger.stringifyMetaData(metaData)}`); // eslint-disable-line no-console Logger.initialize(logConsole("Error"), logConsole("Warning"), logConsole("Info"), logConsole("Trace")); } /** merge the supplied metadata with all static metadata into one object */ static getMetaData(metaData) { const metaObj = {}; for (const meta of this._staticMetaData) { const val = BentleyError.getMetaData(meta[1]); if (val) Object.assign(metaObj, val); } Object.assign(metaObj, BentleyError.getMetaData(metaData)); // do this last so user supplied values take precedence return metaObj; } /** stringify the metadata for a log message by merging the supplied metadata with all static metadata into one object that is then `JSON.stringify`ed. */ static stringifyMetaData(metaData) { const metaObj = this.getMetaData(metaData); return Object.keys(metaObj).length > 0 ? JSON.stringify(metaObj) : ""; } /** Set the least severe level at which messages should be displayed by default. Call setLevel to override this default setting for specific categories. */ static setLevelDefault(minLevel) { this._minLevel = minLevel; this.onLogLevelChanged.raiseEvent(); } /** Set the minimum logging level for the specified category. The minimum level is least severe level at which messages in the * specified category should be displayed. */ static setLevel(category, minLevel) { Logger._categoryFilter[category] = minLevel; this.onLogLevelChanged.raiseEvent(); } /** Interpret a string as the name of a LogLevel */ static parseLogLevel(str) { switch (str.toUpperCase()) { case "EXCEPTION": return LogLevel.Error; case "FATAL": return LogLevel.Error; case "ERROR": return LogLevel.Error; case "WARNING": return LogLevel.Warning; case "INFO": return LogLevel.Info; case "TRACE": return LogLevel.Trace; case "DEBUG": return LogLevel.Trace; } return LogLevel.None; } /** Set the log level for multiple categories at once. Also see [[validateProps]] */ static configureLevels(cfg) { Logger.validateProps(cfg); if (cfg.defaultLevel !== undefined) { this.setLevelDefault(Logger.parseLogLevel(cfg.defaultLevel)); } if (cfg.categoryLevels !== undefined) { for (const cl of cfg.categoryLevels) { this.setLevel(cl.category, Logger.parseLogLevel(cl.logLevel)); } } } static isLogLevel(v) { return LogLevel.hasOwnProperty(v); } /** Check that the specified object is a valid LoggerLevelsConfig. This is useful when reading a config from a .json file. */ static validateProps(config) { const validProps = ["defaultLevel", "categoryLevels"]; for (const prop of Object.keys(config)) { if (!validProps.includes(prop)) throw new BentleyError(IModelStatus.BadArg, `LoggerLevelsConfig - unrecognized property: ${prop}`); if (prop === "defaultLevel") { if (!Logger.isLogLevel(config.defaultLevel)) throw new BentleyError(IModelStatus.BadArg, `LoggerLevelsConfig.defaultLevel must be a LogLevel. Invalid value: ${JSON.stringify(config.defaultLevel)}`); } else if (prop === "categoryLevels") { const value = config[prop]; if (!Array.isArray(value)) throw new BentleyError(IModelStatus.BadArg, `LoggerLevelsConfig.categoryLevels must be an array. Invalid value: ${JSON.stringify(value)}`); for (const item of config[prop]) { if (!item.hasOwnProperty("category") || !item.hasOwnProperty("logLevel")) throw new BentleyError(IModelStatus.BadArg, `LoggerLevelsConfig.categoryLevels - each item must be a LoggerCategoryAndLevel {category: logLevel:}. Invalid value: ${JSON.stringify(item)}`); if (!Logger.isLogLevel(item.logLevel)) throw new BentleyError(IModelStatus.BadArg, `LoggerLevelsConfig.categoryLevels - each item's logLevel property must be a LogLevel. Invalid value: ${JSON.stringify(item.logLevel)}`); } } } } /** Get the minimum logging level for the specified category. */ static getLevel(category) { // Prefer the level set for this category specifically const minLevelForThisCategory = Logger.categoryFilter[category]; if (minLevelForThisCategory !== undefined) return minLevelForThisCategory; // Fall back on the level set for the parent of this category. const parent = category.lastIndexOf("."); if (parent !== -1) return Logger.getLevel(category.slice(0, parent)); // Fall back on the default level. return Logger.minLevel; } /** Turns off the least severe level at which messages should be displayed by default. * This turns off logging for all messages for which no category minimum level is defined. */ static turnOffLevelDefault() { Logger._minLevel = undefined; } /** Turns off all category level filters previously defined with [[Logger.setLevel]]. */ static turnOffCategories() { Logger._categoryFilter = {}; } /** Check if messages in the specified category should be displayed at this level of severity. */ static isEnabled(category, level) { const minLevel = Logger.getLevel(category); return (minLevel !== undefined) && (level >= minLevel); } /** Log the specified message to the **error** stream. * @param category The category of the message. * @param message The message. * @param metaData Optional data for the message */ static logError(category, message, metaData) { if (Logger._logError && Logger.isEnabled(category, LogLevel.Error)) Logger._logError(category, message, metaData); } static getExceptionMessage(err) { if (err === undefined) { return "Error: err is undefined."; } if (err === null) { return "Error: err is null."; } const stack = Logger.logExceptionCallstacks ? `\n${BentleyError.getErrorStack(err)}` : ""; return BentleyError.getErrorMessage(err) + stack; } /** Log the specified exception. * For legacy [[BentleyError]] exceptions, the special "exceptionType" property will be added as metadata. Otherwise, all enumerable members of the exception are logged as metadata. * @param category The category of the message. * @param err The exception object. * @param log The logger output function to use - defaults to Logger.logError */ static logException(category, err, log = (_category, message, metaData) => Logger.logError(_category, message, metaData)) { log(category, Logger.getExceptionMessage(err), () => { // For backwards compatibility, log BentleyError old way if (BentleyError.isError(err)) return { ...BentleyError.getErrorMetadata(err), exceptionType: err?.constructor?.name ?? "<Unknown>" }; // return a copy of the error, with non-enumerable members `message` and `stack` removed, as "metadata" for log. return { ...err }; }); } /** Log the specified message to the **warning** stream. * @param category The category of the message. * @param message The message. * @param metaData Optional data for the message */ static logWarning(category, message, metaData) { if (Logger._logWarning && Logger.isEnabled(category, LogLevel.Warning)) Logger._logWarning(category, message, metaData); } /** Log the specified message to the **info** stream. * @param category The category of the message. * @param message The message. * @param metaData Optional data for the message */ static logInfo(category, message, metaData) { if (Logger._logInfo && Logger.isEnabled(category, LogLevel.Info)) Logger._logInfo(category, message, metaData); } /** Log the specified message to the **trace** stream. * @param category The category of the message. * @param message The message. * @param metaData Optional data for the message */ static logTrace(category, message, metaData) { if (Logger._logTrace && Logger.isEnabled(category, LogLevel.Trace)) Logger._logTrace(category, message, metaData); } } /** Simple performance diagnostics utility. * It measures the time from construction to disposal. On disposal it logs the routine name along with * the duration in milliseconds. * It also logs the routine name at construction time so that nested calls can be disambiguated. * * The timings are logged using the log category **Performance** and log severity [[LogLevel.INFO]]. * Enable those, if you want to capture timings. * @public */ export class PerfLogger { static _severity = LogLevel.Info; _operation; _metaData; _startTimeStamp; constructor(operation, metaData) { this._operation = operation; this._metaData = metaData; if (!Logger.isEnabled(BentleyLoggerCategory.Performance, PerfLogger._severity)) { this._startTimeStamp = 0; return; } Logger.logInfo(BentleyLoggerCategory.Performance, `${this._operation},START`, this._metaData); this._startTimeStamp = new Date().getTime(); // take timestamp } logMessage() { const endTimeStamp = new Date().getTime(); if (!Logger.isEnabled(BentleyLoggerCategory.Performance, PerfLogger._severity)) return; Logger.logInfo(BentleyLoggerCategory.Performance, `${this._operation},END`, () => { const mdata = this._metaData ? BentleyError.getMetaData(this._metaData) : {}; return { ...mdata, TimeElapsed: endTimeStamp - this._startTimeStamp, // eslint-disable-line @typescript-eslint/naming-convention }; }); } [Symbol.dispose]() { this.logMessage(); } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ dispose() { this[Symbol.dispose](); } } //# sourceMappingURL=Logger.js.map