@itwin/core-bentley
Version:
Bentley JavaScript core components
304 lines • 14.9 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerfLogger = exports.Logger = exports.LogLevel = void 0;
const BeEvent_1 = require("./BeEvent");
const BentleyError_1 = require("./BentleyError");
const BentleyLoggerCategory_1 = require("./BentleyLoggerCategory");
/** Use to categorize logging messages by severity.
* @public
*/
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 || (exports.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
*/
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_1.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_1.BentleyError.getMetaData(meta[1]);
if (val)
Object.assign(metaObj, val);
}
Object.assign(metaObj, BentleyError_1.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_1.BentleyError(BentleyError_1.IModelStatus.BadArg, `LoggerLevelsConfig - unrecognized property: ${prop}`);
if (prop === "defaultLevel") {
if (!Logger.isLogLevel(config.defaultLevel))
throw new BentleyError_1.BentleyError(BentleyError_1.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_1.BentleyError(BentleyError_1.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_1.BentleyError(BentleyError_1.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_1.BentleyError(BentleyError_1.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_1.BentleyError.getErrorStack(err)}` : "";
return BentleyError_1.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_1.BentleyError.isError(err))
return { ...BentleyError_1.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);
}
}
exports.Logger = Logger;
/** 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
*/
class PerfLogger {
static _severity = LogLevel.Info;
_operation;
_metaData;
_startTimeStamp;
constructor(operation, metaData) {
this._operation = operation;
this._metaData = metaData;
if (!Logger.isEnabled(BentleyLoggerCategory_1.BentleyLoggerCategory.Performance, PerfLogger._severity)) {
this._startTimeStamp = 0;
return;
}
Logger.logInfo(BentleyLoggerCategory_1.BentleyLoggerCategory.Performance, `${this._operation},START`, this._metaData);
this._startTimeStamp = new Date().getTime(); // take timestamp
}
logMessage() {
const endTimeStamp = new Date().getTime();
if (!Logger.isEnabled(BentleyLoggerCategory_1.BentleyLoggerCategory.Performance, PerfLogger._severity))
return;
Logger.logInfo(BentleyLoggerCategory_1.BentleyLoggerCategory.Performance, `${this._operation},END`, () => {
const mdata = this._metaData ? BentleyError_1.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]();
}
}
exports.PerfLogger = PerfLogger;
//# sourceMappingURL=Logger.js.map