UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

606 lines (568 loc) 25.5 kB
/*! * OpenUI5 * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define(["sap/base/util/now"], function(now) { "use strict"; /** * A Logging API for JavaScript. * * Provides methods to manage a client-side log and to create entries in it. Each of the logging methods * {@link module:sap/base/Log.debug}, {@link module:sap/base/Log.info}, {@link module:sap/base/Log.warning}, * {@link module:sap/base/Log.error} and {@link module:sap/base/Log.fatal} creates and records a log entry, * containing a timestamp, a log level, a message with details and a component info. * The log level will be one of {@link module:sap/base/Log.Level} and equals the name of the concrete logging method. * * By using the {@link module:sap/base/Log.setLevel} method, consumers can determine the least important * log level which should be recorded. Less important entries will be filtered out. (Note that higher numeric * values represent less important levels). The initially set level depends on the mode that UI5 is running in. * When the optimized sources are executed, the default level will be {@link module:sap/base/Log.Level.ERROR}. * For normal (debug sources), the default level is {@link module:sap/base/Log.Level.DEBUG}. * * All logging methods allow to specify a <b>component</b>. These components are simple strings and * don't have a special meaning to the UI5 framework. However they can be used to semantically group * log entries that belong to the same software component (or feature). There are two APIs that help * to manage logging for such a component. With {@link module:sap/base/Log.getLogger}, * one can retrieve a logger that automatically adds the given <code>sComponent</code> as component * parameter to each log entry, if no other component is specified. Typically, JavaScript code will * retrieve such a logger once during startup and reuse it for the rest of its lifecycle. * Second, the {@link module:sap/base/Log.Logger#setLevel}(iLevel, sComponent) method allows to set the log level * for a specific component only. This allows a more fine granular control about the created logging entries. * {@link module:sap/base/Log.Logger#getLevel} allows to retrieve the currently effective log level for a given * component. * * {@link module:sap/base/Log.getLogEntries} returns an array of the currently collected log entries. * * Furthermore, a listener can be registered to the log. It will be notified whenever a new entry * is added to the log. The listener can be used for displaying log entries in a separate page area, * or for sending it to some external target (server). * * @public * @since 1.58 * @namespace * @alias module:sap/base/Log */ var Log = {}; /** * Enumeration of the configurable log levels that a Logger should persist to the log. * * Only if the current LogLevel is higher than the level {@link module:sap/base/Log.Level} of the currently added log entry, * then this very entry is permanently added to the log. Otherwise it is ignored. * @enum {int} * @public */ Log.Level = { /** * Do not log anything * @public */ NONE : -1, /** * Fatal level. Use this for logging unrecoverable situations * @public */ FATAL : 0, /** * Error level. Use this for logging of erroneous but still recoverable situations * @public */ ERROR : 1, /** * Warning level. Use this for logging unwanted but foreseen situations * @public */ WARNING : 2, /** * Info level. Use this for logging information of purely informative nature * @public */ INFO : 3, /** * Debug level. Use this for logging information necessary for debugging * @public */ DEBUG : 4, /** * Trace level. Use this for tracing the program flow. * @public */ TRACE : 5, /** * Trace level to log everything. * @public */ ALL : (5 + 1) }; var sDefaultComponent, /** * The array that holds the log entries that have been recorded so far */ aLog = [], /** * Maximum log level to be recorded (per component). */ mMaxLevel = { '' : Log.Level.ERROR }, /** * Maximum amount of stored log entries */ iLogEntriesLimit = 3000, /** * Registered listener to be informed about new log entries. */ oListener = null, /** * Additional support information delivered by callback should be logged */ bLogSupportInfo = false; function pad0(i,w) { return ("000" + String(i)).slice(-w); } function level(sComponent) { return (!sComponent || isNaN(mMaxLevel[sComponent])) ? mMaxLevel[''] : mMaxLevel[sComponent]; } /** * Discard 30 percent of log entries when the limit is reached */ function discardLogEntries() { var iLogLength = aLog.length; if (iLogLength) { var iEntriesToKeep = Math.min(iLogLength, Math.floor(iLogEntriesLimit * 0.7)); if (oListener) { // Notify listener that entries are being discarded oListener.onDiscardLogEntries(aLog.slice(0, iLogLength - iEntriesToKeep)); } if (iEntriesToKeep) { aLog = aLog.slice(-iEntriesToKeep, iLogLength); } else { aLog = []; } } } /** * Gets the log entry listener instance, if not present creates a new one * @returns {Object} the singleton log entry listener */ function getLogEntryListenerInstance(){ if (!oListener) { oListener = { listeners: [], onLogEntry: function(oLogEntry){ for (var i = 0; i < oListener.listeners.length; i++) { if (oListener.listeners[i].onLogEntry) { oListener.listeners[i].onLogEntry(oLogEntry); } } }, onDiscardLogEntries: function(aDiscardedLogEntries) { for (var i = 0; i < oListener.listeners.length; i++) { if (oListener.listeners[i].onDiscardLogEntries) { oListener.listeners[i].onDiscardLogEntries(aDiscardedLogEntries); } } }, attach: function(oLog, oLstnr){ if (oLstnr) { oListener.listeners.push(oLstnr); if (oLstnr.onAttachToLog) { oLstnr.onAttachToLog(oLog); } } }, detach: function(oLog, oLstnr){ for (var i = 0; i < oListener.listeners.length; i++) { if (oListener.listeners[i] === oLstnr) { if (oLstnr.onDetachFromLog) { oLstnr.onDetachFromLog(oLog); } oListener.listeners.splice(i,1); return; } } } }; } return oListener; } /** * Creates a new fatal-level entry in the log with the given message, details and calling component. * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.fatal = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.FATAL, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Creates a new error-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.error = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.ERROR, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Creates a new warning-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.warning = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.WARNING, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Creates a new info-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.info = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.INFO, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Creates a new debug-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.debug = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.DEBUG, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Creates a new trace-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ Log.trace = function(sMessage, sDetails, sComponent, fnSupportInfo) { log(Log.Level.TRACE, sMessage, sDetails, sComponent, fnSupportInfo); }; /** * Defines the maximum <code>sap.base.log.Level</code> of log entries that will be recorded. * Log entries with a higher (less important) log level will be omitted from the log. * When a component name is given, the log level will be configured for that component * only, otherwise the log level for the default component of this logger is set. * For the global logger, the global default level is set. * * <b>Note</b>: Setting a global default log level has no impact on already defined * component log levels. They always override the global default log level. * * @param {module:sap/base/Log.Level} iLogLevel The new log level * @param {string} [sComponent] The log component to set the log level for * @public */ Log.setLevel = function(iLogLevel, sComponent, _bDefault) { sComponent = sComponent || sDefaultComponent || ''; if (!_bDefault || mMaxLevel[sComponent] == null) { mMaxLevel[sComponent] = iLogLevel; var sLogLevel; Object.keys(Log.Level).forEach(function(sLevel) { if (Log.Level[sLevel] === iLogLevel) { sLogLevel = sLevel; } }); log(Log.Level.INFO, "Changing log level " + (sComponent ? "for '" + sComponent + "' " : "") + "to " + sLogLevel, "", "sap.base.log"); } }; /** * Returns the log level currently effective for the given component. * If no component is given or when no level has been configured for a * given component, the log level for the default component of this logger is returned. * * @param {string} [sComponent] Name of the component to retrieve the log level for * @returns {module:sap/base/Log.Level} The log level for the given component or the default log level * @public */ Log.getLevel = function(sComponent) { return level(sComponent || sDefaultComponent); }; /** * Checks whether logging is enabled for the given log level, * depending on the currently effective log level for the given component. * * If no component is given, the default component of this logger will be taken into account. * * @param {module:sap/base/Log.Level} [iLevel=Level.DEBUG] The log level in question * @param {string} [sComponent] Name of the component to check the log level for * @returns {boolean} Whether logging is enabled or not * @public */ Log.isLoggable = function(iLevel, sComponent) { return (iLevel == null ? Log.Level.DEBUG : iLevel) <= level(sComponent || sDefaultComponent); }; /** * Enables or disables whether additional support information is logged in a trace. * If enabled, logging methods like error, warning, info and debug are calling the additional * optional callback parameter fnSupportInfo and store the returned object in the log entry property supportInfo. * * @param {boolean} bEnabled true if the support information should be logged * @private * @ui5-restricted sap.ui.support */ Log.logSupportInfo = function(bEnabled) { bLogSupportInfo = bEnabled; }; /** * Creates a new log entry depending on its level and component. * * If the given level is higher than the max level for the given component * (or higher than the global level, if no component is given), * then no entry is created and <code>undefined</code> is returned. * * If an <code>Error</code> is passed via <code>sDetails</code> the stack * of the <code>Error</code> will be logged as a separate parameter in * the proper <code>console</code> function for the matching log level. * * @param {module:sap/base/Log.Level} iLevel One of the log levels FATAL, ERROR, WARNING, INFO, DEBUG, TRACE * @param {string} sMessage The message to be logged * @param {string|Error} [sDetails] The optional details for the message; could be an Error which will be logged with the stack to easily find the root cause of the Error * @param {string} [sComponent] The log component under which the message should be logged * @param {function} [fnSupportInfo] Callback that returns an additional support object to be logged in support mode. * This function is only called if support info mode is turned on with <code>logSupportInfo(true)</code>. * To avoid negative effects regarding execution times and memory consumption, the returned object should be a simple * immutable JSON object with mostly static and stable content. * @returns {object} The log entry as an object or <code>undefined</code> if no entry was created * @private */ function log(iLevel, sMessage, sDetails, sComponent, fnSupportInfo) { if (!fnSupportInfo && !sComponent && typeof sDetails === "function") { fnSupportInfo = sDetails; sDetails = ""; } if (!fnSupportInfo && typeof sComponent === "function") { fnSupportInfo = sComponent; sComponent = ""; } sComponent = sComponent || sDefaultComponent; if (iLevel <= level(sComponent) ) { var fNow = now(), oNow = new Date(fNow), iMicroSeconds = Math.floor((fNow - Math.floor(fNow)) * 1000), oLogEntry = { time : pad0(oNow.getHours(),2) + ":" + pad0(oNow.getMinutes(),2) + ":" + pad0(oNow.getSeconds(),2) + "." + pad0(oNow.getMilliseconds(),3) + pad0(iMicroSeconds,3), date : pad0(oNow.getFullYear(),4) + "-" + pad0(oNow.getMonth() + 1,2) + "-" + pad0(oNow.getDate(),2), timestamp: fNow, level : iLevel, message : String(sMessage || ""), details : String(sDetails || ""), component: String(sComponent || "") }; if (bLogSupportInfo && typeof fnSupportInfo === "function") { oLogEntry.supportInfo = fnSupportInfo(); } if (iLogEntriesLimit) { if (aLog.length >= iLogEntriesLimit) { // Cap the amount of stored log messages by 30 percent discardLogEntries(); } aLog.push(oLogEntry); } if (oListener) { oListener.onLogEntry(oLogEntry); } /* * Console Log, also tries to log to the console, if available. * * Unfortunately, the support for console is quite different between the UI5 browsers. The most important differences are: * - in IE (checked until IE9), the console object does not exist in a window, until the developer tools are opened for that window. * After opening the dev tools, the console remains available even when the tools are closed again. Only using a new window (or tab) * restores the old state without console. * When the console is available, it provides most standard methods, but not debug and trace * - in FF3.6 the console is not available, until FireBug is opened. It disappears again, when fire bug is closed. * But when the settings for a web site are stored (convenience), the console remains open * When the console is available, it supports all relevant methods * - in FF9.0, the console is always available, but method assert is only available when firebug is open * - in Webkit browsers, the console object is always available and has all required methods * - Exception: in the iOS Simulator, console.info() does not exist */ /*eslint-disable no-console */ if (console) { // in IE and FF, console might not exist; in FF it might even disappear var isDetailsError = sDetails instanceof Error, logText = oLogEntry.date + " " + oLogEntry.time + " " + oLogEntry.message + " - " + oLogEntry.details + " " + oLogEntry.component; switch (iLevel) { case Log.Level.FATAL: case Log.Level.ERROR: isDetailsError ? console.error(logText, "\n", sDetails) : console.error(logText); break; case Log.Level.WARNING: isDetailsError ? console.warn(logText, "\n", sDetails) : console.warn(logText); break; case Log.Level.INFO: if (console.info) { // info not available in iOS simulator isDetailsError ? console.info(logText, "\n", sDetails) : console.info(logText); } else { isDetailsError ? console.log(logText, "\n", sDetails) : console.log(logText); } break; case Log.Level.DEBUG: if (console.debug) { // debug not available in IE, fallback to log isDetailsError ? console.debug(logText, "\n", sDetails) : console.debug(logText); } else { isDetailsError ? console.log(logText, "\n", sDetails) : console.log(logText); } break; case Log.Level.TRACE: if (console.trace) { // trace not available in IE, fallback to log (no trace) isDetailsError ? console.trace(logText, "\n", sDetails) : console.trace(logText); } else { isDetailsError ? console.log(logText, "\n", sDetails) : console.log(logText); } break; } if (console.info && oLogEntry.supportInfo) { console.info(oLogEntry.supportInfo); } } /*eslint-enable no-console */ return oLogEntry; } } /** * Returns the logged entries recorded so far as an array. * * Log entries are plain JavaScript objects with the following properties * <ul> * <li>timestamp {number} point in time when the entry was created * <li>level {module:sap/base/Log.Level} LogLevel level of the entry * <li>message {string} message text of the entry * </ul> * The default amount of stored log entries is limited to 3000 entries. * @returns {object[]} an array containing the recorded log entries * @public * @static */ Log.getLogEntries = function() { return aLog.slice(); }; /** * Returns the maximum amount of stored log entries. * * @returns {int|Infinity} The maximum amount of stored log entries or Infinity if no limit is set * @private * @ui5-restricted */ Log.getLogEntriesLimit = function() { return iLogEntriesLimit; }; /** * Sets the limit of stored log entries * * If the new limit is lower than the current limit, the overlap of old log entries will be discarded. * If the limit is reached the amount of stored messages will be reduced by 30 percent. * * @param {int|Infinity} iLimit The maximum amount of stored log entries or Infinity for unlimited entries * @private * @ui5-restricted */ Log.setLogEntriesLimit = function(iLimit) { if (iLimit < 0) { throw new Error("The log entries limit needs to be greater than or equal to 0!"); } iLogEntriesLimit = iLimit; if (aLog.length >= iLogEntriesLimit) { discardLogEntries(); } }; /** * Allows to add a new LogListener that will be notified for new log entries. * * The given object must provide method <code>onLogEntry</code> and can also be informed * about <code>onDetachFromLog</code>, <code>onAttachToLog</code> and <code>onDiscardLogEntries</code>. * @param {object} oListener The new listener object that should be informed * @public * @static */ Log.addLogListener = function(oListener) { getLogEntryListenerInstance().attach(this, oListener); }; /** * Allows to remove a registered LogListener. * @param {object} oListener The new listener object that should be removed * @public * @static */ Log.removeLogListener = function(oListener) { getLogEntryListenerInstance().detach(this, oListener); }; /** * @private */ function Logger(sComponent) { this.fatal = function(msg,detail,comp,support) { Log.fatal(msg, detail, comp || sComponent, support); return this; }; this.error = function(msg,detail,comp,support) { Log.error(msg, detail, comp || sComponent, support); return this; }; this.warning = function(msg,detail,comp,support) { Log.warning(msg, detail, comp || sComponent, support); return this; }; this.info = function(msg,detail,comp,support) { Log.info(msg, detail, comp || sComponent, support); return this; }; this.debug = function(msg,detail,comp,support) { Log.debug(msg, detail, comp || sComponent, support); return this; }; this.trace = function(msg,detail,comp,support) { Log.trace(msg, detail, comp || sComponent, support); return this; }; this.setLevel = function(level, comp) { Log.setLevel(level, comp || sComponent); return this; }; this.getLevel = function(comp) { return Log.getLevel(comp || sComponent); }; this.isLoggable = function(level,comp) { return Log.isLoggable(level, comp || sComponent); }; } /** * Returns a dedicated logger for a component * * The logger comes with the same API as the Logger module: * <ul> * <li><code>#fatal</code> - see: {@link sap/base/Log.fatal} * <li><code>#error</code> - see: {@link sap/base/Log.error} * <li><code>#warning</code> - see: {@link sap/base/Log.warning} * <li><code>#info</code> - see: {@link sap/base/Log.info} * <li><code>#debug</code> - see: {@link sap/base/Log.debug} * <li><code>#trace</code> - see: {@link sap/base/Log.trace} * <li><code>#setLevel</code> - see: {@link sap/base/Log.setLevel} * <li><code>#getLevel</code> - see: {@link sap/base/Log.getLevel} * <li><code>#isLoggable</code> - see: {@link sap/base/Log.isLoggable} * </ul> * * @param {string} sComponent Name of the component which should be logged * @param {module:sap/base/Log.Level} [iLogLevel] The default log level * @return {object} A logger with a specified component * @public * @static */ Log.getLogger = function(sComponent, iDefaultLogLevel) { if ( !isNaN(iDefaultLogLevel) && mMaxLevel[sComponent] == null ) { mMaxLevel[sComponent] = iDefaultLogLevel; } return new Logger(sComponent); }; return Log; });