UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

583 lines (581 loc) 24.6 kB
import { filter, take } from 'rxjs/operators'; import { Http } from '../data/http'; import { Net } from '../data/net'; import { RpcLogClient } from '../rpc/log/rpc-log-client'; import { RpcLogSubjectServer } from '../rpc/log/rpc-log-subject-server'; import { RpcTelemetryClient } from '../rpc/telemetry/rpc-telemetry-client'; import { RpcTelemetrySubjectServer } from '../rpc/telemetry/rpc-telemetry-subject-server'; import { LogLevel } from './log-level'; import { LoggingConstants } from './logging-constants'; import { TelemetryControlType } from './telemetry-control-type'; /** * Logging class. * @dynamic */ export class Logging { static logMaxRecordLength = 20; static logMaxWaitTimeMs = 30000; static telemetryMaxRecordLength = 100; static telemetryMaxWaitTimeMs = 60000; static testMode = 'test'; static instance; static errorLogHeaderStyle = 'font-weight: bold; color: #E81123;'; static warnLogHeaderStyle = 'font-weight: bold; color: #FF8C00;'; static successLogHeaderStyle = 'font-weight: bold; color: ##288928;'; static infoLogHeaderStyle = 'font-weight: bold; color: #0078D7;'; static verboseLogHeaderStyle = 'color: #999'; logSubjectServer; telemetrySubjectServer; gateway; http; rpc; logSet; telemetrySet; thresholdOfLogLevel = LogLevel.Informational; verboseTelemetry = false; /** * Log a logging event. * * @param record the log record to send the gateway. * @return Promise<any> settle to resolve if buffered. */ static log(record) { return Logging.current.logInternal(record); } /** * Log a critical logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logCritical(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Critical, source, message, params: params }); } /** * Log an error logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logError(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Error, source, message, params: params }); } /** * Log a warning logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logWarning(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Warning, source, message, params: params }); } /** * Log a success logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logSuccess(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Success, source, message, params: params }); } /** * Log a informational logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logInformational(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Informational, source, message, params: params }); } /** * Log a verbose logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logVerbose(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Verbose, source, message, params: params }); } /** * Log a debug logging event. * * @param source The record originator of event within a module. * @param message The message of event. * @param params The parameters object which will be serialized. * @return Promise<any> settle to resolve if buffered. */ static logDebug(source, message, params) { return Logging.current.logInternal({ level: LogLevel.Debug, source, message, params: params }); } /** * Trace a telemetry event. * * @param record the telemetry record to send the gateway. * @return Promise<any> settle to resolve if buffered. */ static trace(record) { return Logging.current.telemetryInternal(record); } /** * Trace a user action telemetry event. * * @param data additional data. * @return Promise<any> settle to resolve if buffered. */ static traceUserAction(data) { return Logging.trace({ view: LoggingConstants.views.smeUIControl, instance: LoggingConstants.empty, action: LoggingConstants.actions.click, data: data }); } /** * Trace a telemetry event for a button click. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceButton(controlName, controlId, nodeName, nodeType) { return Logging.traceButtonClick(TelemetryControlType.Button, controlName, controlId, nodeName, nodeType); } /** * Trace a telemetry event for a action pane button click. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceActionPaneButton(controlName, controlId, nodeName, nodeType) { return Logging.traceButtonClick(TelemetryControlType.ActionPaneButton, controlName, controlId, nodeName, nodeType); } /** * Trace a telemetry event for a action bar button click. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceActionBarButton(controlName, controlId, nodeName, nodeType) { return Logging.traceButtonClick(TelemetryControlType.ActionBarButton, controlName, controlId, nodeName, nodeType); } /** * Trace a telemetry event on a sme control. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param additionalData Additional key value pairs that can be sent to telemetry. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceControl(controlType, controlName, controlId, additionalData, nodeName, nodeType) { let data = {}; if (additionalData) { data = additionalData; } data[controlType] = controlType; data[controlName] = controlName; data[controlId] = controlId; const nodeId = Logging.getFormattedNodeId(nodeName, nodeType); if (nodeId) { data[LoggingConstants.dataFields.nodeId] = nodeId; } return Logging.traceUserAction(data); } /** * Trace a telemetry event on click of an external link. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param url External link that was clicked. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceExternalLink(controlName, url, controlId, nodeName, nodeType) { const data = { controlType: TelemetryControlType.ExternalLink, controlName: controlName, controlId: controlId, url: url }; const nodeId = Logging.getFormattedNodeId(nodeName, nodeType); if (nodeId) { data[LoggingConstants.dataFields.nodeId] = nodeId; } return Logging.traceUserAction(data); } /** * Trace a telemetry event on click of an internal link. * * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param route rpc route. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceInternalLink(controlName, route, controlId, nodeName, nodeType) { const data = { controlType: TelemetryControlType.InternalLink, controlName: controlName, controlId: controlId, route: route }; const nodeId = Logging.getFormattedNodeId(nodeName, nodeType); if (nodeId) { data[LoggingConstants.dataFields.nodeId] = nodeId; } return Logging.traceUserAction(data); } /** * Trace a telemetry event when an async task returns. * * @param eventLocation UI location of the event being recorded. * ex) createVmForm, RegisterAadDialog, stopServiceConfirmationDialog... * @param eventLabel Description of what event is being logged. * ex) 'Registered with Azure', 'Added a connection', 'Failed Azure backup' * @param result Result of event. * @param additionalData Additional key value pairs that can be sent to telemetry. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceAsyncResult(eventLocation, eventLabel, result, additionalData, nodeName, nodeType) { let data = {}; if (additionalData) { data = additionalData; } data[LoggingConstants.dataFields.eventLocation] = eventLocation; data[LoggingConstants.dataFields.eventLabel] = eventLabel; data[LoggingConstants.dataFields.result] = result; const nodeId = Logging.getFormattedNodeId(nodeName, nodeType); if (nodeId) { data[LoggingConstants.dataFields.nodeId] = nodeId; } return Logging.traceUserAction(data); } /** * Log a raw object into the console at debug level of mode. */ static debug(object) { if (Logging.current.consoleLogLevel >= LogLevel.Debug) { console.log(object); } } /** * Configure logging mode. * * @param thresholdOfLogLevel the log level for gateway. * @param verboseTelemetry if true, optional telemerty will be collected. */ static configureLog(thresholdOfLogLevel, verboseTelemetry) { Logging.current.thresholdOfLogLevel = thresholdOfLogLevel; Logging.current.verboseTelemetry = verboseTelemetry; } /** * Wrapper method for tracing a telemetry event for a button click. * * @param controlType Type of button that is being recorded * @param controlName Describes which control was used. * ex) For a button that says 'Set up' the control name can be 'Set up' to distinguish that from a cancel button. * @param controlId Unique identifier when a control appears more than once. Specific to how implementation handles uniqueness. * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return Promise<any> settle to resolve if buffered. */ static traceButtonClick(controlType, controlName, controlId, nodeName, nodeType) { const data = { controlType: controlType, controlName: controlName, controlId: controlId }; const nodeId = Logging.getFormattedNodeId(nodeName, nodeType); if (nodeId) { data[LoggingConstants.dataFields.nodeId] = nodeId; } return Logging.traceUserAction(data); } /** * Format node information for telemetry * * @param nodeName the name of the node currently connected to (this.appContextService.activeConnection.nodeName) * @param nodeType the type of node currently connected to (this.appContextService.connectionManager.activeConnection.type) * @return string */ static getFormattedNodeId(nodeName, nodeType) { if (nodeType && nodeName) { return nodeType + '!' + nodeName; } else { return null; } } /** * Gets the level of current logging. */ get consoleLogLevel() { const global = window; return (global.MsftSme && global.MsftSme.Init && global.MsftSme.Init.logLevel) || LogLevel.Warning; } /** * Gets the session Id of shell. */ get sessionId() { const global = window; return global.MsftSme && global.MsftSme.Init && global.MsftSme.Init.sessionId || Logging.testMode; } /** * Gets the name of current shell or module. */ get nameOfModule() { const global = window; return global.MsftSme && global.MsftSme.Init && global.MsftSme.Init.moduleName || Logging.testMode; } /** * Initializes a new instance of the Logging class. */ constructor() { this.http = new Http(); this.logSet = { maxWaitTimeMs: Logging.logMaxWaitTimeMs, maxRecordLength: Logging.logMaxRecordLength, api: '/log' }; this.telemetrySet = { maxWaitTimeMs: Logging.telemetryMaxWaitTimeMs, maxRecordLength: Logging.telemetryMaxRecordLength, api: '/telemetry' }; } /** * Gets the current logging instance. */ static get current() { if (Logging.instance) { return Logging.instance; } Logging.instance = new Logging(); return Logging.instance; } /** * Flush the regular log set, immediately submitting the logs to the gateway */ static flushLogs() { Logging.current.flush(Logging.current.logSet); } /** * Flush the telemetry log set, immediately submitting the logs to the gateway */ static flushTelemetry() { Logging.current.flush(Logging.current.telemetrySet); } /** * Register Rpc object to logging instance. * * @param rpc the rpc instance. */ registerRpc(rpc, gateway) { this.rpc = rpc; this.gateway = gateway; // start subscribing once after the rpc is ready on the shell. this.rpc.stateChanged .pipe(filter(active => active), take(1)) .subscribe(() => { if (this.rpc.isShell) { this.logSubjectServer = new RpcLogSubjectServer(this.rpc); this.logSet.rpcSubscription = this.logSubjectServer.subject .subscribe(data => { this.logGateway(this.logSet, data.data); data.deferred.resolve(); }); this.telemetrySubjectServer = new RpcTelemetrySubjectServer(this.rpc); this.telemetrySet.rpcSubscription = this.telemetrySubjectServer.subject .subscribe(data => { this.logGateway(this.telemetrySet, data.data); data.deferred.resolve(); }); } }); } /** * Dispose the set of rpc forwarding pipes. */ dispose() { this.disposeSet(this.logSet); this.disposeSet(this.telemetrySet); } /** * Log a record. * * @param record the log record. * @return Promise<any> the promise object. */ logInternal(record) { const now = new Date(); if (record.level <= this.consoleLogLevel) { let headerStyle = Logging.verboseLogHeaderStyle; if (record.level <= LogLevel.Error) { headerStyle = Logging.errorLogHeaderStyle; } else if (record.level <= LogLevel.Warning) { headerStyle = Logging.warnLogHeaderStyle; } else if (record.level <= LogLevel.Success) { headerStyle = Logging.successLogHeaderStyle; } else if (record.level <= LogLevel.Informational) { headerStyle = Logging.infoLogHeaderStyle; } const logSeparator = '--'; const customHeader = record.consoleGroupHeader ? ` ${logSeparator} ${record.consoleGroupHeader}` : ''; if (MsftSme.isEdge() || MsftSme.isInternetExplorer()) { // Microsoft Edge and IE don't support styles in group headers. so remove if running on Microsoft Edge // Since there is no color support, also log the severity console.groupCollapsed(`${LogLevel[record.level]} ${logSeparator} ${this.nameOfModule} ${logSeparator} ${record.source}${customHeader}`); } else { console.groupCollapsed(`%c${this.nameOfModule} ${logSeparator} ${record.source}${customHeader}`, headerStyle); } console.log(`logLevel: ${LogLevel[record.level]}`); console.log(`timestamp: ${now.toISOString()}`); if (record.message) { if (MsftSme.isObject(record.message)) { console.log('message:'); console.log({ ...record.message }); } else { console.log(`message: ${record.message}`); } } if (record.params) { console.group('params'); console.log({ ...record.params }); console.groupEnd(); } if (record.localParams) { console.group('params'); console.log({ ...record.localParams }); console.groupEnd(); delete record['localParams']; } if (record.stack) { console.group('stack'); console.log(record.stack); console.groupEnd(); } console.groupEnd(); } if (record.level <= this.thresholdOfLogLevel && this.sessionId !== Logging.testMode) { if (MsftSme.isObject(record.message)) { // ensure message is a string. record.message = JSON.stringify(record.message); } const rpcRecord = { ...record, ...{ sessionId: this.sessionId, timestamp: now.getTime(), sourceName: this.nameOfModule } }; if (this.rpc && this.rpc.stateActive && !this.rpc.isShell) { return RpcLogClient.log(this.rpc, rpcRecord); } this.logGateway(this.logSet, rpcRecord); } return Promise.resolve(); } /** * Log a telemerty record. * * @param record the telemetry record. * @return Promise<any> the promise object. */ telemetryInternal(record) { if (((!this.verboseTelemetry && !record.optional) || this.verboseTelemetry) && this.sessionId !== Logging.testMode) { const rpcRecord = { ...record, ...{ sessionId: this.sessionId, timestamp: Date.now(), sourceName: this.nameOfModule } }; if (this.rpc && this.rpc.stateActive && !this.rpc.isShell) { return RpcTelemetryClient.telemetry(this.rpc, rpcRecord); } this.logGateway(this.telemetrySet, rpcRecord); } return Promise.resolve(); } /** * Dispose the set. * * @param set the logger set. */ disposeSet(set) { if (set.rpcSubscription) { set.rpcSubscription.unsubscribe(); set.rpcSubscription = null; } } /** * Log to the gateway. * * @param set the logger set. * @param data the record data. */ logGateway(set, data) { if (set.timer == null) { set.buffer = []; set.timer = setTimeout(() => this.submitRecords(set), set.maxWaitTimeMs); } set.buffer.push(data); if (set.buffer.length >= set.maxRecordLength) { clearTimeout(set.timer); this.submitRecords(set); set.timer = setTimeout(() => this.submitRecords(set), set.maxWaitTimeMs); } } /** * Flush a log set, immediately submitting the logs to the gateway */ flush(set) { if (set && set.buffer && set.buffer.length > 0) { this.submitRecords(set); } } /** * Submit records to the gateway. */ submitRecords(set) { // Disabled gateway can't log anything in gateway. if (set.buffer.length > 0 && this.gateway && !this.gateway.disabled) { this.gateway.post(set.api, set.buffer, { maxRetryCount: 0 }).subscribe({ error: error => { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.LoggingUnableSubmit.message; console.error(message.format(Net.getErrorMessage(error))); } }); } set.timer = null; set.buffer = []; } } //# sourceMappingURL=logging.js.map