"use strict"; import EventEmitter = NodeJS.EventEmitter; export {}; const winston = require("winston"); const DailyRotateFile = require('winston-daily-rotate-file'); const fs = require("fs"); const colors = require("colors/safe"); const util = require("util"); import {default as stripAnsi} from 'strip-ansi'; const Cryptr = require('cryptr'); //import stripAnsi from 'strip-ansi'; /* let stripAnsi; (async () => { //import {stripAnsi} from 'strip-ansi'; stripAnsi = await import('strip-ansi'); })(); // */ //let defaultConfig = require("../config/config"); import {config as defaultConfig} from "../config/config"; import {from} from "rxjs"; import {findPackageJson, isDefined, isInstanceOfClass, isJsonObject, isPlainObject, stackTrace} from "./Utils.js"; import {LEVELS, LEVELSCOLORS, LEVELSNAMES, LogLevelAreas} from './LevelLogs.js'; import path from "path"; import {fileURLToPath} from "node:url"; import { dirname, join } from 'path'; const LOG_ID = "LOGS - "; const tsFormat = (p_date = undefined) => { //let date = new Date(p_date); let date = new Date(); return date.toLocaleDateString() + " " + date.toLocaleTimeString() + ":" + date.getMilliseconds() + " [" + date.valueOf() + "]"; //return date.valueOf(); }; const myFormat = winston.format.printf( (info: any) => { // 12/03/2025 10:00:11:846 [1741770011846] //return `${info.timestamp} [${info._processInfo.pid}] ${info.level}: ${info.message}`; return `${tsFormat()} - ${info.level}: ${info.message}`; //return `${info.timestamp} [` + tsFormat(info.timestamp) + `] - ${info.level}: ${info.message}`; }); const myFormatNoColors = winston.format.printf(info => { //return `${info.timestamp} [` + tsFormat(info.timestamp) + `] - ${info.level}: ${info.message}`; return `${tsFormat()} - ${info.level}: ${info.message}`; // The following code is necessary when the colors lib disabled. //return `${tsFormat()}` + ' - ' + stripAnsi(info.level) + ':' + stripAnsi(info.message); }) ; class Logger { get areasLogs(): LogLevelAreas { return this._areasLogs; } set areasLogs(value: LogLevelAreas) { this._areasLogs = value; } private enableEncryptedLogs: boolean = false; public logLevel: LEVELSNAMES; public logColor: boolean; private _areasLogs : LogLevelAreas; // public levels: { debug: number; warning: number; error: number; "http": number, "xmpp": number; info: number , "warn": number, "trace": number, "internal": number, "internalerror": number}; get logEventEmitter(): NodeJS.EventEmitter { return this._logEventEmitter; } set logEventEmitter(value: NodeJS.EventEmitter) { this._logEventEmitter = value; } public colors: any; public _logger: any; public _winston: any; public hideId: any; public hideUuid: any; private _logEventEmitter: EventEmitter; private emit: (event, info) => void; private cryptr : any; constructor(config) { let self = this; const cryptr = new Cryptr('rainbow-node-sdk-1654341354345486797943542318461318730123013'); winston.addColors(LEVELSCOLORS); this.colors = colors; this.colors.setTheme({ silly: 'rainbow', input: 'grey', verbose: 'cyan', prompt: 'grey', info: 'green', data: 'grey', help: 'cyan', warn: 'yellow', debug: 'blue', error: 'red', xmpp: "cyan", events: [ 'magenta', 'underline', 'italic'], eventsEmitter: [ 'cyan', 'underline', 'italic'] }); let welcome = () => { let fullVersion = ""; try { //let content = fs.readFileSync(path.join(__dirname, "../package.json")); // If using CommonJS, use __dirname // If using ES modules: const currentFileUrl = __dirname; const currentDir = dirname(currentFileUrl); //const currentFileUrl = import.meta.url; //const currentDir = dirname(fileURLToPath(currentFileUrl)); const packageJsonPath = findPackageJson(currentDir); if (packageJsonPath) { console.log(`Found package.json at: ${packageJsonPath}`); let content = fs.readFileSync(packageJsonPath); let packageJSON = JSON.parse(content); fullVersion = packageJSON.version; } } catch(err) { console.log("CATCH Error !!! in welcome : ", err); } this._logger.log("info", LOG_ID + "------------------------------------------------"); this._logger.log("info", LOG_ID + "Welcome to the " + this.colors.magenta("ALE Rainbow SDK for Node.JS") + ""); this._logger.log("info", LOG_ID + "Where Everything connects"); this._logger.log("info", LOG_ID + "Support: Send message to Emily using #support #api"); this._logger.log("info", LOG_ID + "Node Version : " , process.versions.node, ", Rainbow Node SDK Version : ", fullVersion ); this._logger.log("info", LOG_ID + "------------------------------------------------"); this._logger.log("internal", LOG_ID + "(constructor) : ", this.colors.italic(this.colors.red(" \n\ * \"system-dev\" section in logs is activated!!! \n\ Note that it is for DEVELOPPEMENT ONLY, no production system should use it : \n\ * \"internals\" config property is for logs level of debug + unsensored data. \n\ Warning password and so on can be logs. \n " + that.colors.red.underline("*** IT SHOULD ONLY BE USED IN DEVELOPPEMENT ENVIRONEMENT !!! ***")))); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.error]("error level color : " + LEVELSCOLORS.error)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.warn]("warn level color : " + LEVELSCOLORS.warn)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.info]("info level color : " + LEVELSCOLORS.info)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.trace]("trace level color : " + LEVELSCOLORS.trace)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.http]("http level color : " + LEVELSCOLORS.http)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.xmpp]("xmpp level color : " + LEVELSCOLORS.xmpp)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.debug]("debug level color : " + LEVELSCOLORS.debug)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.internalerror]("internalerror level color : " + LEVELSCOLORS.internalerror)); this._logger.log("info", LOG_ID + this.colors[LEVELSCOLORS.internal]("internal level color : " + LEVELSCOLORS.internal)); }; let logs = defaultConfig.logs; let logDir = logs.path; self.logLevel = logs.level; self.logColor = logs.color; let logHttp = logs["system-dev"].http; let logInternals = logs["system-dev"].internals; let logFormat = myFormat; let zippedArchive = logs.zippedArchive; let maxSize = logs.maxSize; let maxFiles = logs.maxFiles; let enableConsoleLog = true; let enableFileLog = false; let enableEventsLogs = false; let enableEncryptedLogs = false; let customFileName = ""; // dev-code // this.argumentsToString = this.argumentsToStringReduced; // end-dev-code // // No configuration for file --> Don't store logs in file if (!("logs" in config)) { enableFileLog = false; } if (("logs" in config) && ("color" in config.logs)) { self.logColor = config.logs.color; } if (("logs" in config) && ("system-dev" in config.logs) &&("http" in config.logs["system-dev"])) { logHttp = config.logs["system-dev"].http; } if (("logs" in config) && ("system-dev" in config.logs) && ("internals" in config.logs["system-dev"])) { logInternals = config.logs["system-dev"].internals; } if (!self.logColor) { this.colors?.disable(); logFormat=myFormatNoColors; } else { this.colors?.enable(); logFormat=myFormat; } // Check for console log if (("logs" in config) && ("enableConsoleLogs" in config.logs) && config.logs.enableConsoleLogs) { enableConsoleLog = true; } else { enableConsoleLog = false; } // Check for file log if (("logs" in config) && ("enableFileLogs" in config.logs) && config.logs.enableFileLogs) { enableFileLog = true; } else { enableFileLog = false; } // Check for events log if (("logs" in config) && ("enableEventsLogs" in config.logs) && config.logs.enableEventsLogs) { enableEventsLogs = true; } else { enableEventsLogs = false; } // Check for Encryption of stanza in log if (("logs" in config) && ("enableEncryptedLogs" in config.logs)) { if (config.logs.enableEncryptedLogs==false) { enableEncryptedLogs = false; } else { enableEncryptedLogs = true; } this.enableEncryptedLogs = enableEncryptedLogs; } // Set Path for log file if (enableFileLog) { if (("logs" in config) && ("file" in config.logs) && ("path" in config.logs.file)) { logDir = config.logs.file.path; } } if (("logs" in config) && ("level" in config.logs)) { self.logLevel = config.logs.level; } if (("logs" in config) && ("internals" in config.logs)) { logInternals = config.logs.internals; } if (enableFileLog) { //Set the Level for log file if (("logs" in config) && ("file" in config.logs) && ("level" in config.logs.file)) { self.logLevel = config.logs.file.level; } //Set the zippedArchive for log file if (("logs" in config) && ("file" in config.logs) && ("zippedArchive" in config.logs.file)) { zippedArchive = config.logs.file.zippedArchive; } //Set the zippedArchive for log file if (("logs" in config) && ("file" in config.logs) && ("maxSize" in config.logs.file)) { maxSize = config.logs.file.maxSize; } //Set the zippedArchive for log file if (("logs" in config) && ("file" in config.logs) && ("maxFiles" in config.logs.file)) { maxFiles = config.logs.file.maxFiles; } // Check and create the directory for logging if (!fs.existsSync(logDir)) { try { fs.mkdirSync(config.logs.file.path); } catch (err) { console.log("Can't create the directoy for logging... File logs will be disabled"); console.log("Error", err); enableFileLog = false; } } } if (config && config.logs && config.logs.areas) { self._areasLogs = config.logs.areas; } else { self._areasLogs = new LogLevelAreas(self.logLevel,false,false,false); if (self.logLevel === LEVELSNAMES.DEBUG) { self._areasLogs.showServicesApiLogs(); } if (self.logLevel === LEVELSNAMES.INTERNAL) { self._areasLogs.showServicesApiLogs(true); self._areasLogs.showRESTLogs(self.logLevel); self._areasLogs.showEventsLogs(self.logLevel); } } let that : any = this; this._winston = {}; this._logger = {}; this._logger.customLabel = ""; this._logger.logHttp = logHttp; this._logger.argumentsToString = that.argumentsToString; this._logger.enableEncryptedLogs = that.enableEncryptedLogs; if (("logs" in config) && ("customLabel" in config.logs)) { this._logger.customLabel = config.logs.customLabel + " - " ; } if (enableFileLog) { if (("logs" in config) && ("file" in config.logs) && ("customFileName" in config.logs.file)) { customFileName = config.logs.file.customFileName? "-" + config.logs.file.customFileName: "" ; } } this._logger.info = function () { that._logger.log.apply(this._logger, [LEVELSNAMES.INFO, ...arguments]); }; this._logger.error = function () { that._logger.log.apply(this._logger, [LEVELSNAMES.ERROR, ...arguments]); }; this._logger.debug = function () { that._logger.log.apply(that._logger, [LEVELSNAMES.DEBUG, ...arguments]); }; this._logger.warn = function () { that._logger.log.apply(that._logger, [LEVELSNAMES.WARN, ...arguments]); }; this._logger.encrypt = function (str) { if (enableEncryptedLogs == true) { return cryptr.encrypt(str); } else { return (""); } }; this._logger.decrypt = function (str) { return cryptr.decrypt(str); }; this._logger.stripStringForLogs = function (value : string) { let result = ""; if (self.logLevel !== LEVELSNAMES.INFO ) { result = value; } else { if (!value ) { result = value; } else { value += ""; result = value.charAt(0) + value.replace(/[^\s](?=.{1,}$)/g, "*")?.substr(1); } } return result; } this.hideId = function (url) { return url.replace(/[\/="'][a-f\d]{24}[\/\?&"']?/ig, (x) => { let ret = x.toUpperCase(); if (ret.length > 8) { ret = ret.substring(0, 4) + "..." + ret.substring(ret.length - 4, ret.length); } return ret; }); }; this.hideUuid =function (url) { return url.replace(/[a-f0-9]{8}[a-f0-9]{4}4[a-f0-9]{3}[89aAbB][a-f0-9]{3}[a-f0-9]{12}[(@|c%40|\'|\")]/ig, (x) => { let ret = x.toUpperCase(); if (ret.length > 8) { ret = ret.substring(0, 4) + "..." + ret.substring(ret.length - 4, ret.length); } return ret; }); }; this.emit = function(level, info) { let event = LEVELSNAMES.DEBUG; if (this.logEventEmitter && enableEventsLogs) { let msg = new Date().toISOString() + " - " + level + ": " + (info ? info.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '') : ""); this._logEventEmitter.emit(event, msg); } } this._logger.stop = function () { return that._winston.end(); } /* this._logger.log = function (options : {"callerObj" : any, "level" : LEVELSNAMES, isApi : boolean} | LEVELSNAMES) { try { const args = [...arguments]; console.log(new Date().toISOString() + args?.toString()); return (-1); } catch (err) { } } // */ this._logger.log = function (options : {"callerObj" : any, "level" : LEVELSNAMES, isApi : boolean} | LEVELSNAMES) { try { let levelOfLog: LEVELSNAMES ; // Level of the SDK logs. let levelOfSdk = self.logLevel; let levelOfSdkValue = LEVELS[self.logLevel]; let levelLimit = LEVELS.error; let isApi : boolean = false; let showAreaApi : boolean = false; if (typeof options==="object") { let callerObj = options?.callerObj; let callerAreaName = "unknown"; if (callerObj && (typeof callerObj.getAccessorName) === "function") { callerAreaName = callerObj.getAccessorName(); } // Current log's config. let paramsOfAreaLogs = self._areasLogs ? self._areasLogs[callerAreaName] : {level:levelOfSdk}; levelOfLog=options?.level?options.level:LEVELSNAMES.INFO; isApi=options?.isApi; //showAreaApi = paramsOfAreaLogs.api; showAreaApi = isDefined(paramsOfAreaLogs.api) ? paramsOfAreaLogs.api : true; // Level of the current log. //let levelOfLog = options?.level; // let levelOfLogNumber = winston.config.syslog.levels[levelOfLog]; // let levelOfLogNumber = self.levels[levelOfLog]; // Level of the aera in the SDK config options. let levelOfArea = ""+paramsOfAreaLogs?.level; levelLimit = LEVELS[levelOfArea]; //levelLimit = levelOfArea?LEVELS[levelOfArea]:levelOfSdkValue; // console.log("paramOfClassLogs", paramsOfClassLogs); //let levelOfAreaNumber = self.levels[paramsOfClassLogs?.level]; //let levelOfSdkNumber = self.levels[levelOfLog]; //console.log("callerName : ", callerName, ", level : ", level, ", levelOfLogNumber : ", levelOfLogNumber, ", levelOfAreaNumber : ", levelOfAreaNumber, ", levelOfSdkNumber : ", levelOfSdkNumber); /* if (levelOfAreaNumber > levelOfSdkNumber ) { if (levelOfLogNumber > levelOfSdkNumber && levelOfLogNumber < levelOfSdkNumber) { level=levelOfSdkNumber; } } // */ // Force to not log the entry if the sdk and the entry are configured to log at "debug" and the aera override at "info". /* if ((levelOfLog === "debug" || levelOfLog === "internal") && levelOfArea === "info" && levelOfSdk === "debug") { return; } // Force to log the entry if the sdk is configured to "info" and the entry and area are configured to log at "debug". Override entry to "info". if ((levelOfLog === "debug" || levelOfLog === "internal") && levelOfArea === "debug" && levelOfSdk === "info") { level = "info"; } // */ //console.log("callerName : ", callerName, ", levelOfLog : ", levelOfLog, ", levelOfArea : ", levelOfArea, ", levelOfSdk : ", levelOfSdk); } else { levelOfLog = options; levelLimit = levelOfSdkValue; } if (!levelOfLog) { console.log("!!! undefined levelOfLog : ", levelOfLog, ", arguments : ", ...arguments); } let levelOfLogValue = LEVELS[levelOfLog]; //console.log("levelOfLog : ", levelOfLog, ", levelOfLogValue : ", levelOfLogValue, ", levelLimit : ", levelLimit); if (levelOfLogValue > levelLimit || (isApi && !showAreaApi)) { //console.log("levelOfLogValue : ", levelOfLogValue, "is supperior to levelLimit : ", levelLimit, " so ignore log."); return; } if (levelOfLog==="internal" || levelOfLog==="internalerror") { if (logInternals===true) { //level = (level === "internal") ? "debug" : "error"; let datatolog = that.colors.italic(that.colors.red("FORBIDDEN TO LOG THIS DATA IN PROD ENV !!! Sorry.")); // console.log(datatolog); // dev-code-internal // if (levelOfLog==="internal") { levelOfLog = LEVELSNAMES.DEBUG; datatolog = that.colors.italic(that.colors.red("PROD HIDDEN : ")) + that.argumentsToString(arguments); that._winston.log.apply(that._winston, [levelOfLog, that._logger.customLabel + datatolog]); that.emit(levelOfLog, that._logger.customLabel + datatolog); } else if (levelOfLog==="internalerror") { levelOfLog = LEVELSNAMES.ERROR; datatolog = that.colors.italic(that.colors.red("PROD HIDDEN : ")) + that.argumentsToStringFull(arguments); that._winston.log.apply(that._winston, [levelOfLog, that._logger.customLabel + datatolog]); that.emit(levelOfLog, that._logger.customLabel + datatolog); } /* */ // end-dev-code-internal // /* if ( level === "internal") { level = "debug"; that._winston.log.apply(that._winston, [level, that._logger.customLabel + that.hideId(that.hideUuid(that.argumentsToString(arguments)))]); that.emit(level, that._logger.customLabel + that.hideId(that.hideUuid(that.argumentsToString(arguments)))); } else if (level === "internalerror") { level = "error"; that._winston.log.apply(that._winston, [level, that._logger.customLabel + that.hideId(that.hideUuid(that.argumentsToString(arguments)))]); that.emit(level, that._logger.customLabel + that.hideId(that.hideUuid(that.argumentsToString(arguments)))); } // */ } } else { let datatolog = that.argumentsToString(arguments); if (logInternals) { that._winston.log.apply(that._winston, [levelOfLog, that._logger.customLabel + datatolog]); that.emit(levelOfLog, that._logger.customLabel + datatolog); } else { that._winston.log.apply(that._winston, [levelOfLog, that._logger.customLabel + that.hideId(that.hideUuid(datatolog))]); that.emit(levelOfLog, that._logger.customLabel + that.hideId(that.hideUuid(datatolog))); } } } catch (err) { console.error("CATCH Error !!! while logging : " + err); } }; if (enableConsoleLog && enableFileLog) { this._winston = winston.createLogger({ levels: LEVELS, format: winston.format.combine( /* winston.format.timestamp({ // 03/20/2025 10:00:11:846 format: 'MM/DD/YYYY HH:MM:SS:SSS' }), // */ /* winston.format.label({ label: "MonLabel" }), // */ winston.format.errors({ stack: true }), // <-- use errors format winston.format.colorize({ all: self.logColor }), //winston.format.label({ label: 'right meow!' }), //winston.format.colorize({ all: false }), winston.format.simple(), winston.format.timestamp(), logFormat //winston.format.prettyPrint() ), transports: [ new (winston.transports.Console)({ level: LEVELSNAMES.INTERNAL, }), new (DailyRotateFile)({ name: 'logs', filename: `${logDir}/%DATE%-rainbowsdk${customFileName}.log`, maxSize: maxSize, zippedArchive: zippedArchive, timestamp: tsFormat, datePattern: "YYYY-MM-DD", maxFiles: maxFiles, prepend: true, level: LEVELSNAMES.INTERNAL //level: self.logLevel }) ] }); welcome(); this._logger.log("info", LOG_ID + "(constructor) console and file logs enabled"); this._logger.log("info", LOG_ID + "(constructor) logs store in directory", logDir); } else if (enableConsoleLog) { this._winston = winston.createLogger({ levels: LEVELS, format: winston.format.combine( winston.format.errors({ stack: true }), // <-- use errors format winston.format.colorize({ all: self.logColor }), winston.format.simple(), //winston.format.label({ label: 'right meow!' }), winston.format.timestamp(), /*winston.format.timestamp({ // 03/20/2025 10:00:11:846 format: 'MM/DD/YYYY HH:MM:SS:SSS' }), // */ logFormat //winston.format.prettyPrint() ), transports: [ new (winston.transports.Console)({ level: LEVELSNAMES.INTERNAL }) ] }); // this._winston.addColors(this._winston.config.syslog.colors, { "trace": this._winston.config.syslog.colors.debug, "xmpp": this._winston.config.syslog.colors.debug, "internal": this._winston.config.syslog.colors.debug, "internalerror": this._winston.config.syslog.colors.error}) welcome(); this._logger.log("info", LOG_ID + "(constructor) No file logs enabled"); } else if (enableFileLog) { this._winston = winston.createLogger({ levels: LEVELS, format: winston.format.combine( winston.format.errors({ stack: true }), // <-- use errors format winston.format.colorize({ all: self.logColor }), //winston.format.label({ label: 'right meow!' }), winston.format.timestamp(), /* winston.format.timestamp({ // 03/20/2025 10:00:11:846 format: 'MM/DD/YYYY HH:MM:SS:SSS' }), // */ logFormat //winston.format.prettyPrint() ), transports: [ new (DailyRotateFile)({ name: 'logs', filename: `${logDir}/%DATE%-rainbowsdk${customFileName}.log`, maxSize: maxSize, zippedArchive: zippedArchive, timestamp: tsFormat, datePattern: "YYYY-MM-DD", maxFiles: maxFiles, prepend: true, level: LEVELSNAMES.INTERNAL }) ] }); welcome(); this._logger.log("info", LOG_ID + "(constructor) No console logs enabled"); this._logger.log("info", LOG_ID + "(constructor) logs store in directory", logDir); } else { this._winston = winston.createLogger({ transports: [], silent: true }); // No logs :-) } /* let loggers = winston.loggers; for (const logguersKey in loggers) { console.log("logguer : ", logguersKey, " : ", loggers[logguersKey]); } // */ if (that._winston.writable) { that._winston.on('error', () => {}) .on('close', () => {}) // flush isn't guaranteed to fire. Since files aren't flushed before 'finish' is fired, // wait 50ms before closing the logger. .on('finish', () => { setTimeout(() => { that._winston.close(); //console.log("finish winston."); }, 50); } ); //.end(); } else { } if (self._logger) { self._logger.colors = self.colors ; self._logger.logLevel = self.logLevel ; } } get log() { return this._logger; } stripStringForLogs = function (value : string) { return this._logger?.stripStringForLogs(value); } argumentsToStringReduced (v, delemiter : string = " "){ // convert arguments object to real array let args = Array.prototype.slice.call(v, 1); let options = { showHidden : false, depth : 3, colors : this.logColor, maxArrayLength : 3 }; for(let k in args) { /* if (isPlainObject(args[k])) { args[k] = util.inspect(args[k], options); }// */ // if (typeof args[k] === "object"){ if (isInstanceOfClass(args[k])) { // args[k] = JSON.stringify(args[k]); args[k] = util.inspect(args[k], options); } else if (isJsonObject(args[k])) { if (isPlainObject(args[k])) { args[k] = util.inspect(args[k], options); } else { args[k] = JSON.stringify(args[k]); } } // */ } //let str = Array.prototype.join.call(args, delemiter); let str = args.join(delemiter); return str; } argumentsToStringFull (v, delemiter : string = " ") { // convert arguments object to real array let args = Array.prototype.slice.call(v, 1); let options = { showHidden : false, depth : null, colors : this.logColor //maxArrayLength : 5 }; for(let k in args){ if (isPlainObject(args[k])) { args[k] = util.inspect(args[k], options); } /* //if (typeof args[k] === "object"){ if (isInstanceOfClass(args[k])){ // args[k] = JSON.stringify(args[k]); args[k] = util.inspect(args[k], false, null, this.logColor); } else if (isJsonObject(args[k])){ if (isPlainObject(args[k])) { args[k] = util.inspect(args[k], false, null, this.logColor); } else { args[k] = JSON.stringify(args[k]); } } // */ } // */ let str = args.join(delemiter); return str; } argumentsToString = this.argumentsToStringFull; } module.exports.Logger = Logger; export {Logger};