import { JL } from 'jsnlog' import { OpenVidu } from "../../OpenVidu/OpenVidu"; import { ConsoleLogger } from './ConsoleLogger'; import { OpenViduLoggerConfiguration } from "./OpenViduLoggerConfiguration"; export class OpenViduLogger { private static instance: OpenViduLogger; private JSNLOG_URL: string = "/openvidu/elk/openvidu-browser-logs"; private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100; private MAX_MSECONDS_BATCH_MESSAGES: number = 5000; private MAX_LENGTH_STRING_JSON: number = 1000; private defaultConsoleLogger: ConsoleLogger = new ConsoleLogger(window.console); private currentAppender: any; private isProdMode = false; private isJSNLogSetup = false; // This two variables are used to restart JSNLog // on different sessions and different userIds private loggingSessionId: string | undefined; /** * @hidden */ static configureJSNLog(openVidu: OpenVidu, token: string) { try { // If dev mode if ((window['LOG_JSNLOG_RESULTS']) || // If instance is created and it is OpenVidu Pro (this.instance && openVidu.webrtcStatsInterval > -1 // If logs are enabled && this.instance.isOpenViduBrowserLogsDebugActive(openVidu) // Only reconfigure it if session or finalUserId has changed && this.instance.canConfigureJSNLog(openVidu, this.instance))) { // Check if app logs can be sent // and replace console.log function to send // logs of the application if (openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debugApp) { this.instance.replaceWindowConsole(); } // isJSNLogSetup will not be true until completed setup this.instance.isJSNLogSetup = false; this.instance.info("Configuring JSNLogs."); const finalUserId = openVidu.finalUserId; const sessionId = openVidu.session.sessionId; const beforeSendCallback = (xhr) => { // If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs // https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818 const parentReadyStateFunction = xhr.onreadystatechange; xhr.onreadystatechange = () => { if (this.isInvalidResponse(xhr)) { Object.defineProperty(xhr, "readyState", { value: 4 }); Object.defineProperty(xhr, "status", { value: 200 }); // Disable JSNLog too to not send periodically errors this.instance.disableLogger(); } parentReadyStateFunction(); } // Headers to identify and authenticate logs xhr.setRequestHeader('Authorization', "Basic " + btoa(`${finalUserId}%/%${sessionId}` + ":" + token)); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') // Additional headers for OpenVidu xhr.setRequestHeader('OV-Final-User-Id', finalUserId); xhr.setRequestHeader('OV-Session-Id', sessionId); xhr.setRequestHeader('OV-Token', token); } // Creation of the appender. this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`); this.instance.currentAppender.setOptions({ beforeSend: beforeSendCallback, maxBatchSize: 1000, batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES, batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES }); // Avoid circular dependencies const logSerializer = (obj): string => { const getCircularReplacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value != null) { if (seen.has(value) || value instanceof HTMLElement) { return; } seen.add(value); } return value; }; }; // Cut long messages let stringifyJson = JSON.stringify(obj, getCircularReplacer()); if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) { stringifyJson = `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`; } if (window['LOG_JSNLOG_RESULTS']) { console.log(stringifyJson); } return stringifyJson; }; // Initialize JL to send logs JL.setOptions({ defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL, serialize: logSerializer, enabled: true }); JL().setOptions({ appenders: [this.instance.currentAppender] }); this.instance.isJSNLogSetup = true; this.instance.loggingSessionId = sessionId; this.instance.info("JSNLog configured."); } } catch (e) { // Print error console.error("Error configuring JSNLog: "); console.error(e); // Restore defaults values just in case any exception happen- this.instance.disableLogger(); } } /** * @hidden */ static getInstance(): OpenViduLogger { if (!OpenViduLogger.instance) { OpenViduLogger.instance = new OpenViduLogger(); } return OpenViduLogger.instance; } private static isInvalidResponse(xhr: XMLHttpRequest) { return xhr.status == 401 || xhr.status == 403 || xhr.status == 404 || xhr.status == 0; } private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean { return openVidu.session.sessionId != logger.loggingSessionId } private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) { return openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug || openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debugApp; } // Return console functions with jsnlog integration private getConsoleWithJSNLog() { return function(openViduLogger: OpenViduLogger){ return { log: function(...args){ openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments); if (openViduLogger.isJSNLogSetup) { JL().info(arguments); } }, info: function (...args) { openViduLogger.defaultConsoleLogger.info.apply(openViduLogger.defaultConsoleLogger.logger, arguments); if (openViduLogger.isJSNLogSetup) { JL().info(arguments); } }, debug: function(...args) { openViduLogger.defaultConsoleLogger.debug.apply(openViduLogger.defaultConsoleLogger.logger, arguments); }, warn: function (...args) { openViduLogger.defaultConsoleLogger.warn.apply(openViduLogger.defaultConsoleLogger.logger, arguments); if (openViduLogger.isJSNLogSetup) { JL().warn(arguments); } }, error: function (...args) { openViduLogger.defaultConsoleLogger.error.apply(openViduLogger.defaultConsoleLogger.logger, arguments); if (openViduLogger.isJSNLogSetup) { JL().error(arguments); } } }; }(this); } private replaceWindowConsole() { window.console = this.defaultConsoleLogger.logger; window.console.log = this.getConsoleWithJSNLog().log; window.console.info = this.getConsoleWithJSNLog().info; window.console.debug = this.getConsoleWithJSNLog().debug; window.console.warn = this.getConsoleWithJSNLog().warn; window.console.error = this.getConsoleWithJSNLog().error; } private disableLogger() { JL.setOptions({enabled: false}); this.isJSNLogSetup = false; this.loggingSessionId = undefined; this.currentAppender = undefined; window.console = this.defaultConsoleLogger.logger; window.console.log = this.defaultConsoleLogger.log; window.console.info = this.defaultConsoleLogger.info; window.console.debug = this.defaultConsoleLogger.debug; window.console.warn = this.defaultConsoleLogger.warn; window.console.error = this.defaultConsoleLogger.error; } /** * @hidden */ log(...args: any[]) { if (!this.isProdMode) { this.defaultConsoleLogger.log.apply(this.defaultConsoleLogger.logger, arguments); } if (this.isJSNLogSetup) { JL().info(arguments); } } /** * @hidden */ debug(...args: any[]) { if (!this.isProdMode) { this.defaultConsoleLogger.debug.apply(this.defaultConsoleLogger.logger, arguments); } } /** * @hidden */ info(...args: any[]) { if (!this.isProdMode) { this.defaultConsoleLogger.info.apply(this.defaultConsoleLogger.logger, arguments); } if (this.isJSNLogSetup) { JL().info(arguments); } } /** * @hidden */ warn(...args: any[]) { if (!this.isProdMode) { this.defaultConsoleLogger.warn.apply(this.defaultConsoleLogger.logger, arguments); } if (this.isJSNLogSetup) { JL().warn(arguments); } } /** * @hidden */ error(...args: any[]) { this.defaultConsoleLogger.error.apply(this.defaultConsoleLogger.logger, arguments); if (this.isJSNLogSetup) { JL().error(arguments); } } /** * @hidden */ flush() { if (this.isJSNLogSetup && this.currentAppender != null) { this.currentAppender.sendBatch(); } } enableProdMode() { this.isProdMode = true; } }