/* -- do not edit following lines - START -- { "debug": false, "verbose": false, "mtime": 1547059146 } -- do not edit previous lines - END --*/ /******************************************************************************* * --------------------------- * Log Script für ioBroker zum Aufbereiten des Logs für Visualisierungen (vis) * --------------------------- * * Das Script liest regelmäßig (einstellbar, z.B. alle 2 Minuten) die tägliche * Log-Datei des ioBrokers aus und setzt das Ergebnis in Datenpunkte, aufgeteilt * je nach Einstellung unten. * Neue Log-Einträge werden in den Datenpunkten regelmäßig ergänzt. * Es stehen auch JSON-Datenpunkte zur Verfügung, mit diesen kann im vis eine * Tabelle ausgegeben werden (z.B. über das Widget 'basic - Tabelle')- * * Aktuelle Version: https://github.com/Mic-M/iobroker.logfile-script * Support: https://forum.iobroker.net/viewtopic.php?f=21&t=15514 * * Change Log: * 0.8.1 Mic - Fix: L_SORTrolla_ORDER_DESC was not defined (renamed constant name was not changed in config) * 0.8 Mic - Fix: Script caused a "file not found" error if executed right at or shortly after midnight. * 0.7 Mic - Fix: States "...clearDateTime" will not get an initial date value on first script start, * also fix for "on({id: ". * 0.6 Mic + Put 0.5.1 BETA into stable * + New option L_APPLY_CSS. If true, it will add xxx * to each log string. 'log-info' for level info, 'log-error' for error, etc. * This makes it easy to format a JSON table with CSS. * 0.5.1 BETA Mic + New States "Clear JSON log ..." and "Clear JSON log - Date/Time ...". * When the button "Clear JSON log" is pushed, the current date/time * will be set into the date/time state. Once refreshed * (per schedule in the script, e.g. after 2 minutes), the JSON * will be cleaned and display just newer logs. * Use Case: In vis, you can now add a button "clear log" or * "Mark as read". If you hit the button, the log will be * cleared and just new log items will be displayed. * *** THIS IS STILL BEING TESTED *** therefore a beta release... * 0.5 Mic + New parameter 'clean' to remove certain strings * from the log line. * + New parameter 'columns' for JSON output to specify which columns * to be shown, and in which order. * + New state "JSONcount" to have the number of log lines in state * - Fixed a few issues * 0.4 Mic - Bug fix: improved validation of log line consistency * 0.3 Mic + Added filtering and blacklist * - Several fixes * 0.2 Mic - Bug fix: corrected wrong function name * 0.1 Mic * Initial release * *******************************************************************************/ /******************************************************************************* * Konfiguration: Pfade ******************************************************************************/ // Pfad, unter dem die States in den Objekten angelegt werden. //const L_STATE_PATH = 'javascript.0.Logs'+ instance + '.' + 'mylog'; const L_STATE_PATH = 'javascript.0.Logs_fuer_VIS'; // Pfad zu dem Log-Verzeichnis auf dem Linux-Rechner. // Der Standard-Pfad auf Raspberry: '/opt/iobroker/log/'. const L_LOG_PATH = '/opt/iobroker/log/'; /******************************************************************************* * Konfiguration: Alle Logeinträge - Global ******************************************************************************/ // Zahl: Maximale Anzahl der letzten Logeinträge in den Datenpunkten. Alle älteren werden entfernt. // Bitte nicht allzu viele behalten, denn das kostet Performance. const L_NO_OF_ENTRIES = 200; // Sortierung der Logeinträge: true für descending (absteigend, also neuester oben), oder false für ascending (aufsteigend, also ältester oben) // Empfohlen ist true, damit neueste Einträge immer oben stehen. const L_SORT_ORDER_DESC = true; // Wie oft sollen die Log-Datenpunkte aktualisiert werden? Benutze den "Cron"-Button oben rechts für komfortable Einstellung // Bitte nicht jede Sekunde laufen lassen, alle paar Minuten sollte locker reichen. //const L_SCHEDULE = "*/1 * * * *"; // alle 2 Minuten const L_SCHEDULE = "*/10 * * * * *"; // alle 2 Minuten // Blacklist - falls einer dieser Begriffe enthalten ist, dann wird der Log-Eintrag // nicht aufgenommen. Praktisch, um penetrante Logeinträge zu eliminieren. // Mindestens 3 Zeichen erforderlich, sonst wird es nicht berücksichtigt. // Datenpunkt-Inhalte bei Änderung ggf. vorher löschen, diese werden nicht nachträglich gefiltert. const L_BLACKLIST_GLOBAL = ['<==Disconnect system.user.admin from ::ffff:', '', '', '']; // Entferne zusätzliche Leerzeichen, Tab-Stops, Zeilenumbrüche // Wird empfohlen. Falls nicht gewünscht, auf false setzen. const L_CLEAN_LOG = true; /******************************************************************************* * Konfiguration: JSON-Log (für Ausgabe z.B. im vis) ******************************************************************************/ // Datumsformat für JSON Log. Z.B. volles z.B. Datum mit 'yyyy-mm-dd HH:MM:SS' oder nur Uhrzeit mit "HH:MM:SS". Die Platzhalter yyyy, mm, dd usw. // werden jeweils ersetzt. yyyy = Jahr, mm = Monat, dd = Tag, HH = Stunde, MM = Minute, SS = Sekunde. Auf Groß- und Kleinschreibung achten! // Die Verbinder (-, :, Leerzeichen, etc.) können im Prinzip frei gewählt werden. // Beispiele: 'HH:MM:SS' für 19:37:25, 'HH:MM' für 19:37, 'mm.dd HH:MM' für '25.07. 19:37' //const L_DATE_FORMAT = 'HH:MM:SS'; const L_DATE_FORMAT = 'HH:MM:SS'; // Max. Anzahl Zeichen der Log-Meldung im JSON Log. const L_LEN = 200; // Zahl: Maximale Anzahl der letzten Logeinträge in den Datenpunkten. Alle älteren werden entfernt. // Speziell für das JSON-Log zur Visualisierung, hier brauchen wir ggf. weniger als für L_NO_OF_ENTRIES gesamt. const L_NO_OF_ENTRIES_JSON = 200; // Füge CSS-Klasse hinzu je nach Log-Level (error, warn, info, etc.), um Tabellen-Text zu formatieren. // Beispiel für Info: ersetzt "xxx" durch "xxx"" // Analog für error: log-error, warn: log-warn, etc. // Beim Widget "basic - Table" im vis können im Reiter "CSS" z.B. folgende Zeilen hinzugefügt werden, // um Warnungen in oranger und Fehler in roter Farbe anzuzeigen. // .log-warn { color: orange; } // .log-error { color: red; } const L_APPLY_CSS = true; // L_APPLY_CSS wird nur für die Spalte "level" (also error, info) angewendet, aber nicht für die // restlichen Spalten wie Datum, Log-Eintrag, etc. // Falls alle Zeilen formatiert werden sollen: auf false setzen. const L_APPLY_CSS_LIMITED_TO_LEVEL = true; /******************************************************************************* * Konfiguration: Datenpunkte und Filter ******************************************************************************/ // Dies ist das Herzstück dieses Scripts: hier werden die Datenpunkte // konfiguriert, die erstellt werden sollen. Hierbei können wir entsprechend // Filter setzen, also Wörter/Begriffe, die in Logeinträgen enthalten sein // sollen und in den Datenpunkten aufgenommen werden. // // id: Hier Begriff ohne Leerzeichen, z.B. "error", "sonoff", etc. // Die ID wird dann Teil der ID der Datenpunkte. // filter_all: ALLE Begriffe müssen in der Logzeile enthalten sein. Ist einer // der Begriffe nicht enthalten, dann wird der komplette Logeintrag // auch nicht übernommen. // Leeres Array eingeben [] falls hier filtern nicht gewünscht. // filter_any: Mindestens einer der gelisteten Begriffe muss enthalten sein. // Leeres Array eingeben [] falls hier filtern nicht gewünscht. // blacklist: Wenn einer dieser Begriffe im Logeintrag enthalten ist, // so wird der komplette Logeintrag nicht übernommen, egal was // vorher in filter_all oder filter_any definiert ist. // Mindestens 3 Zeichen erforderlich, sonst wird es nicht // berücksichtigt. // clean: Der Log-Eintrag wird um diese Zeichenfolgen bereinigt, d.h. diese // werden entfernt, aber die restliche Zeile bleibt bestehen. Z.B. // um unerwünschte Zeichenfolgen zu entfernen oder Log-Ausgaben // zu kürzen. // columns: Nur für JSON (für vis). // Folgende Spalten gibt es: 'date','level','source','msg' // Hier können einzelne Spalten entfernt oder die Reihenfolge // verändert werden. // Bitte keine anderen Werte eintragen. // // filter_all, filter_any und blacklist werden gleichzeitig ausgeführt. // Bei den Filtern bitte beachten: Datenpunkt-Inhalte bei Änderung ggf. vorher // löschen, diese werden nicht nachträglich gefiltert. // // Die Filter-Einträge können natürlich beliebig geändert und erweitert werden, // bitte aber den Aufbau beibehalten. // const L_FILTER = [ // { // id: 'all', // wir wollen hier alle Logeinträge, keine Filterung // filter_all: ['', ''], // wird ignoriert, wenn leer // filter_any: ['+++'], // wird ignoriert, wenn leer // blacklist: ['', ''], // wird ignoriert, wenn leer // clean: ['', '', ''], // wird ignoriert, wenn leer // columns: ['date','level','source','msg'], // Spaltenreihenfolge für JSON (Tabelle in vis) // }, // { // id: 'debug', // filter_all: [' - debug: '], // nur Logeinträge mit Level 'debug' // filter_any: ['', ''], // blacklist: ['', ''], // clean: ['', '', ''], // columns: ['date','level','source','msg'], // }, // { // id: 'info', // filter_all: [' - info: '], // nur Logeinträge mit Level 'info' // filter_any: ['', ''], // blacklist: ['', ''], // clean: ['', '', ''], // columns: ['date','level','source','msg'], // }, // { // id: 'warn', // filter_all: [' - warn: '], // nur Logeinträge mit Level 'warn' // filter_any: ['', ''], // blacklist: ['', ''], // clean: ['', '', ''], // columns: ['date','level','source','msg'], // }, // { // id: 'error', // filter_all: [' - error: '], // nur Logeinträge mit Level 'error' // filter_any: ['', ''], // blacklist: ['', ''], // clean: ['', '', ''], // columns: ['date','level','source','msg'], // }, { id: 'warnanderror', filter_all: ['', ''], filter_any: [' - error: ', ' - warn: '], blacklist: [''], clean: ['', '', ''], columns: ['date','source','level','msg'], }, { id: 'javascript', filter_all: ['+++'], filter_any: [''], blacklist: ['Error'], //clean: [''], clean: ['system.','steckdosen.','sontiges.','licht.','script.js.java.wol.','script.js.java.shutdown.','script.js.smarthome.','rolladen.','javascript.0','info','+++ ',' +++','NAS_und_VMware.shutdown_WinNAS_v01:','adapter_starten_stoppen:','rolladen_tag_nacht:','script.js.java.wol.wol_winnas:','backup_erstellen:','briefkasten_v0_1: ','stufenbeleuchtung_v01: ','weihnachten: ','echo_stati: ','NAS_und_VMware.shutdown_synology_v0_1:','shutdown_synology_v0_1: ','weckradio: ','kueche_v0_2: ','schlafzimmer_v0_3: ','schlafzimmer_tv_v0_1: ','licht.weckzeit_v0_2: ','NAS_und_VMware.start_stop_esxp: ','wol_synology: ','steckdosen.ps4_lueftung_v0_2:','wol_daniel: ','NAS_und_VMware.wake_on_lan_v0_2: ','alexa_benachrichtigungen: ','anwesenheit: ','system_ctr_vis: ','reboot__skript: ','weckzeit_v0_2:','wol_winnas: ','wol_qnap_archiv: ','lichterkette_flur_v0_3: ','reboot_skript: ','briefkasten_tuerklingel_v0_1: ','shutdown_qnap-archiv_v0_1: ','rolladen_verdunkelung_v0_1: ','tablet: ','usv_shutdown: ','script.js.java.sonstige.speedtest: ','kodi_steckdose_v0_3: ','bewaesserung: ','weckzeit_daniel: ','weckzeit_jenny: ','harmony_schaltung_v0_4: ','benachrichtigungen_v0_1:'], columns: ['date','msg'], }, // Beispiel für individuellen Eintrag. Hier wird Euer Hubschrauber-Landeplatz // überwacht :-) Wir wollen nur Einträge vom Adapter 'hubschr.0'. // Dabei sollen entweder Wetterwarnungen, Alarme, oder UFOs gemeldet werden. // Alles unter Windstärke "5 Bft" interessiert uns dabei nicht, daher haben // wir '0 Bft' bis '4 Bft' auf die Blackliste gesetzt. // Außerdem entfernen wir von der Log-Zeile die Zeichenfolgen '****', '!!!!' // und 'ufo gesichtet', der Rest bleibt aber bestehen. // Zudem haben wir unter columns die Spaltenreihenfolge geändert. 'level' // herausgenommen, und Quelle ganz vorne. //{ // id: 'hubschrauberlandeplatz', // filter_all: ['hubschr.0'], // filter_any: ['wetterwarnung', 'alarm', 'ufo'], //blacklist: ['0 Bft', '1 Bft', '2 Bft', '3 Bft', '4 Bft'], //clean: ['****', '!!!!', 'ufo gesichtet'], //columns: ['level','date','msg'], //}, ]; /******************************************************************************* * Konfiguration: Konsolen-Ausgaben ******************************************************************************/ // Auf true setzen, wenn zur Fehlersuche einige Meldungen ausgegeben werden sollen. // Ansonsten bitte auf false stellen. const LOG_DEBUG = false; // Auf true setzen, wenn ein paar Infos im Log ausgegeben werden dürfen, bei false bleiben die Infos weg. const LOG_INFO = false; /******************************************************************************* * Experten-Konfiguration ******************************************************************************/ // Regex für die Aufteilung des Logs in 1-Datum/Zeit, 3-Level, 5-Quelle und 7-Logtext. // Ggf. anzupassen bei anderem Datumsformat im Log. Wir erwarten ein Format // wie '2018-07-22 12:45:02.769 - info: javascript.0 Stop script script.js.ScriptAbc' const REGEX_LOG = /([0-9_.\-:\s]*)(\s+\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\-]*)(\s)(.*)/g; // Debug: Falls auf true, dann werden die Datenpunkte nicht ausgelesen, sondern von // der Log-Datei immer neu gesetzt. const DEBUG_IGNORE_STATES = false; // Leer lassen! Nur setzen, falls ein eigener Filename für das Logfile verwendet wird für Debug. const L_LOG_FILENAME = ''; /******************************************************************************* * Ab hier nichts mehr ändern / Stop editing here! ******************************************************************************/ /** * Executed on every script start. Also sets the schedule. */ init(); function init() { // Create states L_createStates(); // Now we call the main update function. This is redundant since we set a schedule further below, // however it is useful if we change something in the script and save it, so that we see the changes // after 5 seconds and not after the time the schedule is set. // We use setTimeout() to execute 5s later and avoid error message on initial start if states not yet created. setTimeout(function() { L_UpdateLog(); }, 5000); // Schedule script accordingly. // We execute 30 seconds later since we called the function right before. setTimeout(function() { schedule(L_SCHEDULE, function () { // apply schedule L_UpdateLog(); }); }, 30000); // Set current date to state if button is pressed for (var i = 0; i < L_FILTER.length; i++) { var strIDCleanFinal = L_STATE_PATH + '.' + 'log' + prepStateNameInclCapitalizeFirst(L_FILTER[i].id) + 'JSONclear'; on({id: strIDCleanFinal, change: "any", val: true}, function(obj) { var currentDate = new Date(); setState(obj.id + 'DateTime', currentDate.toString()); }); // warning on the left can be ignored, we need a function here... } } /** * Main function. Process content of today's logfile (e.g. /opt/iobroker/log/iobroker.2018-07-19.log) */ function L_UpdateLog() { // A couple warnings if (DEBUG_IGNORE_STATES) L_Log2('DEBUG_IGNORE_STATES is set to true!', "warn"); if (L_LOG_FILENAME !== '') L_Log2('L_LOG_FILENAME is set: "' + L_LOG_FILENAME + '"', "warn"); // Path and filename to log file var strLogPathFinal = L_LOG_PATH; if (strLogPathFinal.slice(-1) !== '/') strLogPathFinal = strLogPathFinal + '/'; var strFullLogPath = strLogPathFinal + L_LOG_FILENAME; if (L_LOG_FILENAME === '') strFullLogPath = strLogPathFinal + 'iobroker.' + L_GetCurrentISODate() + '.log'; if (LOG_DEBUG) L_Log('Path and Filename: ' + '>' + strFullLogPath + '<'); // Reads the log file entry, result will be string in variable "data" var fs = require('fs'); fs.readFile(strFullLogPath, 'utf8', function (err,data) { if (err) { // At midnight, the script will use the new log file of the day. // However, the server may not yet have it created, so we ignore the 'file not found error' for 3 hours. // This should be enough time for ioBroker until we get a log entry and therefore a log file. if(L_IsTimeInRange('00:00:00', '03:00:00')) { if (LOG_DEBUG) L_Log('Midnight or right after, so a log file of the new day does not exist yet'); return; // exit function } else { return L_Log2('Error when trying to read the log file, msg: ' + err, 'error'); // Exit function with error msg } } // get log entries into array, these are separated by new line in the file... var logArray = data.split(/\r?\n/); // We process each log entry line // We add one element per each filter to the Array ('all', 'error', etc.) var logArrayProcessed = []; for (var j = 0; j < L_FILTER.length; j++) { logArrayProcessed[L_FILTER[j].id] = ''; } for (var i = 0; i < logArray.length; i++) { var loopElement = logArray[i]; // Clean up loopElement = loopElement.replace(/\u001b\[.*?m/g, ''); // Remove color escapes - https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings if (loopElement.substr(0,9) === 'undefined') loopElement = loopElement.substr(9,99999); // sometimes, a log line starts with the term "undefined", so we remove it. if (L_CLEAN_LOG) loopElement = loopElement.replace(/\s\s+/g, ' '); // Remove white space, tab stops, new line // Check against global blacklist if(L_StringContainsTerms(loopElement, L_BLACKLIST_GLOBAL, 'blacklist')) loopElement = ''; ///////////////// // Split log levels. //////////////// // We apply regex here. This will also eliminate all log lines without proper info // like date/time, log level, and entry. var arrSplitLogLine = L_SplitLogLine(loopElement, REGEX_LOG); if (arrSplitLogLine !== false) { ///////////////// // We apply our filters. ///////////////// if (L_IsValueEmptyNullUndefined(L_FILTER) === false) { // Now let's iterate again over the filter array elements // We check if both the "all" and "any" filters apply. If yes, - and blacklist false - we add the log line. for (var k = 0; k < L_FILTER.length; k++) { if ( (L_StringContainsTerms(loopElement, L_FILTER[k].filter_all, 'every') === true) && (L_StringContainsTerms(loopElement, L_FILTER[k].filter_any, 'some') === true) && (L_StringContainsTerms(loopElement, L_FILTER[k].blacklist, 'blacklist') === false) ){ logArrayProcessed[L_FILTER[k].id] = logArrayProcessed[L_FILTER[k].id] + loopElement + "\n"; } // Now we remove terms if desired if (L_IsValueEmptyNullUndefined(L_FILTER[k].clean) === false) { for (var lpTerm of L_FILTER[k].clean) { if (lpTerm !== '') { logArrayProcessed[L_FILTER[k].id] = logArrayProcessed[L_FILTER[k].id].replace(lpTerm, ''); } } } } } // if } // if } // for loop // Process further L_processLogAndSetToState(logArrayProcessed); if (LOG_INFO) L_Log('Log-Datenpunkte aktualisiert'); }); // fs.readFile } /** * Further processes the log array */ function L_processLogAndSetToState(arrayLogInput) { // Build log levels array and add filters (like 'all', 'alerts', etc.) var arrayFilterIds = []; for (var i = 0; i < L_FILTER.length; i++) { arrayFilterIds.push(L_FILTER[i].id); // each filter id into array } // Loop through the log filter ids for (var k = 0; k < arrayFilterIds.length; k++) { // Log filter id(all, error, etc.) = arrayFilterIds[k] // Content of Log Level = arrayLogInput[arrayFilterIds[k]] var strLoopLogContent = arrayLogInput[arrayFilterIds[k]]; // Get full path to state var strStateFullPath = L_STATE_PATH + '.' + 'log' + prepStateNameInclCapitalizeFirst(arrayFilterIds[k]); // Get state contents of loop filter id and append it var strStateLogContent = getState(strStateFullPath).val; if (!DEBUG_IGNORE_STATES) { if (L_IsValueEmptyNullUndefined(strStateLogContent) === false) { strLoopLogContent = strLoopLogContent + strStateLogContent; // "\n" not needed, always added above } } if (L_IsValueEmptyNullUndefined(strLoopLogContent) === false) { // Convert to array for easier handling var myArray = strLoopLogContent.split(/\r?\n/); // Remove duplicates myArray = L_arrayRemoveDuplicates(myArray); // Remove empty values myArray = L_cleanArray(myArray); // Sort array descending myArray = L_SortLogByDate(myArray, 'desc'); // Separate ID for JSON var myArrayJSON = myArray; // This is to clear the log // Let's remove elements if current date in state "logXXXJSONclearDateTime" is greater than log date. var strDateFromState = getState(strStateFullPath + 'JSONclearDateTime').val; if (L_IsValueEmptyNullUndefined(strDateFromState) === false) { if (strDateFromState !== 0) { // we set it to 0 via vis widget if we want to clear the state myArrayJSON = L_clearArrayByDate(myArrayJSON, strDateFromState); } } // Just keep the first x elements of the array myArray = myArray.slice(0, L_NO_OF_ENTRIES); myArrayJSON = myArrayJSON.slice(0, L_NO_OF_ENTRIES_JSON); // Sort ascending if desired if (!L_SORT_ORDER_DESC) { myArray = myArray.reverse(); myArrayJSON = myArrayJSON.reverse(); } // ** Finally set the states /////////////////////////////// // -1- Full Log, String, separated by "\n" /////////////////////////////// var strResult = myArray.join("\n"); setState(strStateFullPath, strResult); /////////////////////////////// // -2- JSON, with elements date and msg /////////////////////////////// // Let's put together the JSON var jsonArr = []; for (var j = 0; j < myArrayJSON.length; j++) { // +++ // We apply regex here to get 4 elements in array: datetime, level, source, message // +++ var arrSplitLogLine = L_SplitLogLine(myArrayJSON[j], REGEX_LOG); if (arrSplitLogLine !== false) { var strLogMsg = arrSplitLogLine.message; // Reduce the length for each log message per configuration strLogMsg = strLogMsg.substr(0, L_LEN); // ++++++ // Build the final Array // ++++++ // We need this section to generate the JSON with the columns (which ones, and order) as specified in L_FILTER var objectJSONentry = {}; // object (https://stackoverflow.com/a/13488998) if (L_IsValueEmptyNullUndefined(L_FILTER[k].columns)) L_Log2('Columns not specified in L_FILTER', 'error'); // Prepare CSS var strCSS1, strCSS2; var strCSS1_level, strCSS2_level; if (L_APPLY_CSS) { strCSS1 = ""; strCSS2 = ''; strCSS1_level = strCSS1; strCSS2_level = strCSS1; if (L_APPLY_CSS_LIMITED_TO_LEVEL) { strCSS1 = ''; strCSS2 = ''; } } for (var lpCol of L_FILTER[k].columns) { switch (lpCol) { case 'date' : objectJSONentry.date = strCSS1 + L_ReformatLogDate(arrSplitLogLine.datetime, L_DATE_FORMAT) + strCSS2; break; case 'level' : objectJSONentry.level = strCSS1_level + arrSplitLogLine.level + strCSS2_level; break; case 'source' : objectJSONentry.source = strCSS1 + arrSplitLogLine.source + strCSS2; break; case 'msg' : objectJSONentry.msg = strCSS1 + strLogMsg + strCSS2; break; default: //nothing; } } // Ok, so now we have the JSON entry. jsonArr.push(objectJSONentry); } } if (L_IsValueEmptyNullUndefined(myArrayJSON) === false) { setState(strStateFullPath + 'JSON', JSON.stringify(jsonArr)); setState(strStateFullPath + 'JSONcount', myArrayJSON.length); } else { // Is empty here if for example L_clearArrayByDate had no hits setState(strStateFullPath + 'JSON', ''); setState(strStateFullPath + 'JSONcount', 0); } } else { // No log available, so we clean it. setState(strStateFullPath, ''); setState(strStateFullPath + 'JSON', ''); setState(strStateFullPath + 'JSONcount', 0); } } } /** * Clear array: if strDate is greater or equal than log date, we remove the entire log entry */ function L_clearArrayByDate(inputArray, strDate) { var dtState = new Date(strDate); // the date provided from the state var newArray = []; for (var lpLog of inputArray) { var dtLog = new Date(lpLog.substr(0,23)); if (dtLog >= dtState) { newArray.push(lpLog); } } return newArray; } /** * Checks if the string provided contains either every or some terms. * @param {string} strInput - The string on which we run this search * @param {array} arrayTerms - The terms we are searching, e.g. ["hue", "error", "raspberry"] * @param {string} type - 'every': all terms must match to be true, * 'some': at least one term (or more) must match * 'blacklist': different here: function will always * return FALSE, but if one of the arrayTerms contains * minimum 3 chars and is found in provided string, * we return TRUE (= blacklisted item found). * @return true, if it contains ALL words, false if not all words (or none) * Also, will return true if arrayTerms is not array or an empty array * @source https://stackoverflow.com/questions/36283767/javascript-select-the-string-if-it-matches-multiple-words-in-array */ function L_StringContainsTerms(strInput, arrayTerms, type) { if(type === 'blacklist') { if (Array.isArray(arrayTerms)) { var arrayTermsNew = []; for (var lpTerm of arrayTerms) { if (lpTerm.length >= 3) { arrayTermsNew.push(lpTerm); } } if(L_IsValueEmptyNullUndefined(arrayTermsNew) === false) { var bResultBL = arrayTermsNew.some(function(word) { return strInput.indexOf(word) > -1; }); return bResultBL; } else { return false; // return false if no items to be blacklisted } } else { return false; // we return false if the arrayTerms given is not an array. Want to make sure if we really should blacklist... } } else { if (Array.isArray(arrayTerms)) { if(type === 'every') { var bResultEvery = arrayTerms.every(function(word) { return strInput.indexOf(word) > -1; }); return bResultEvery; } else if(type === 'some') { var bResultSome = arrayTerms.some(function(word) { return strInput.indexOf(word) > -1; }); return bResultSome; } } else { return true; // we return true if the arrayTerms given is not an array } } } /** * Splits a given log line into an array with 4 elements. * @param {string} strLog Log line like '2018-07-22 11:47:53.019 - info: javascript.0 script.js ...' * @param {string} strRegex RegEx * @param {boolean} validity check: true for yes, false for no * @return: Array with 4 elements: datetime (e.g. 2018-07-22 11:47:53.019), * level (e.g. info), source (e.g. javascript.0) * and message (e.g. script.js....) * Returns FALSE if no match */ function L_SplitLogLine(strLog, strRegex) { // At first we split into array var returnArray = []; var m; do { m = strRegex.exec(strLog); if (m) { returnArray.datetime = m[1]; returnArray.level = m[3]; returnArray.source = m[5]; returnArray.message = m[7]; } } while (m); // Now we check if we have valid entries we want if ((returnArray.datetime === undefined) || (returnArray.level === undefined) || (returnArray.source === undefined) || (returnArray.message === undefined) ){ return false; // no valid hits } // We can return the array now, since it meets all requirements return returnArray; } /** * Create all States we need at this time. */ function L_createStates() { var statesArray = []; if (L_IsValueEmptyNullUndefined(L_FILTER) === false) { for(var i = 0; i < L_FILTER.length; i++) { if (L_FILTER[i].id !== '') { var strIDClean = prepStateNameInclCapitalizeFirst(L_FILTER[i].id); if (LOG_DEBUG) L_Log('clean ID: ' + '>' + strIDClean + '<'); statesArray.push({ id:'log' + strIDClean, name:'Filtered Log - ' + strIDClean, type:"string", role: "log", def: ""}); statesArray.push({ id:'log' + strIDClean + 'JSON', name:'Filtered Log - ' + strIDClean + ' - JSON', type:"string", role: "log", def: ""}); statesArray.push({ id:'log' + strIDClean + 'JSONcount', name:'Filtered Log - Count of JSON ' + strIDClean, role: "log", type:"number", def: 0}); statesArray.push({ id:'log' + strIDClean + 'JSONclear', name:'Clear JSON log ' + strIDClean, role: "button", type:"boolean", def: false}); statesArray.push({ id:'log' + strIDClean + 'JSONclearDateTime', name:'Clear JSON log - Date/Time ' + strIDClean, role: "log", type:"string", def: ''}); } } } for (var s=0; s < statesArray.length; s++) { createState(L_STATE_PATH + '.' + statesArray[s].id, { 'name': statesArray[s].name, 'desc': statesArray[s].name, 'type': statesArray[s].type, 'read': true, 'write': true, 'role': statesArray[s].role, 'def': statesArray[s].def, }); } } /** * Will just keep lower case letters, numbers, '-' and '_' and removes the rest * Also, capitalize first Letter. */ function prepStateNameInclCapitalizeFirst(stringInput) { var strProcess = stringInput; strProcess = strProcess.replace(/([^a-z0-9_\-]+)/gi, ''); strProcess = strProcess.toLowerCase(); strProcess = strProcess.charAt(0).toUpperCase() + strProcess.slice(1); return strProcess; } /** * Clean Array: Will remove all falsy values: undefined, null, 0, false, NaN and "" (empty string) * @source - https://stackoverflow.com/questions/281264/remove-empty-elements-from-an-array-in-javascript * */ function L_cleanArray(inputArray) { var newArray = []; for (var i = 0; i < inputArray.length; i++) { if (inputArray[i]) { newArray.push(inputArray[i]); } } return newArray; } /** * Remove Duplicates from Array * @source - https://stackoverflow.com/questions/23237704/nodejs-how-to-remove-duplicates-from-array */ function L_arrayRemoveDuplicates(inputArray) { var uniqueArray; uniqueArray = inputArray.filter(function(elem, pos) { return inputArray.indexOf(elem) == pos; }); return uniqueArray; } /** * Sorts the log array by date. We expect the first 23 chars of each element being a date in string format. * @param {array} arrayInput * @param {string} order asc or desc for ascending or descending order */ function L_SortLogByDate(arrayInput, order) { var result = arrayInput.sort(function(a,b){ // Turn your strings into dates, and then subtract them // to get a value that is either negative, positive, or zero. a = new Date(a.substr(0,23)); b = new Date(b.substr(0,23)); if (order === "asc") { return a - b; } else { return b - a; } }); return result; } /** * Returns the current date in ISO format "YYYY-MM-DD". * @return {string} Date in ISO format */ function L_GetCurrentISODate() { var currDate = new Date(); return currDate.getFullYear() + '-' + L_ZeroPad((currDate.getMonth() + 1), 2) + '-' + L_ZeroPad(currDate.getDate(), 2); } /** * Fügt Vornullen zu einer Zahl hinzu, macht also z.B. aus 7 eine "007". * zeroPad(5, 4); // wird "0005" * zeroPad('5', 6); // wird "000005" * zeroPad(1234, 2); // wird "1234" * @param {string|number} num Zahl, die Vornull(en) bekommen soll * @param {number} places Anzahl Stellen. * @return {string} Zahl mit Vornullen wie gewünscht. */ function L_ZeroPad(num, places) { if (L_IsNumber(num)) { // isNumber will also be true for a string which is actually a number, like '123'. var zero = places - num.toString().length + 1; return Array(+(zero > 0 && zero)).join("0") + num; } else { // No number provided, so we through an eror L_Log2('Function [' + arguments.callee.toString().match(/function ([^\(]+)/)[1] + '] - no number/string provided', 'error'); } } /** * Reformats a log date string accordingly * @param {date} strDate The date to convert * @param {string} format e.g. 'yyyy-mm-dd HH:MM:SS'. * */ function L_ReformatLogDate(strDate, format) { var strResult = format; strResult = strResult.replace('yyyy', strDate.substr(0,4)); strResult = strResult.replace('mm', strDate.substr(5,2)); strResult = strResult.replace('dd', strDate.substr(8,2)); strResult = strResult.replace('HH', strDate.substr(11,2)); strResult = strResult.replace('MM', strDate.substr(14,2)); strResult = strResult.replace('SS', strDate.substr(17,2)); return strResult; } /** * Prüft ob Variableninhalt eine Zahl ist. * @param {any} Variable, die zu prüfen ist auf Zahl * @return true falls Zahl, false falls nicht. * isNumber ('123'); // true * isNumber ('123abc'); // false * isNumber (5); // true * isNumber ('q345'); // false * isNumber(null); // false * isNumber(undefined); // false * isNumber(false); // false * isNumber(' '); // false * @source https://stackoverflow.com/questions/1303646/check-whether-variable-is-number-or-string-in-javascript */ function L_IsNumber(n) { return /^-?[\d.]+(?:e-?\d+)?$/.test(n); } /** * Checks if Array or String is not undefined, null or empty. * @param inputVar - Input Array or String, Number, etc. * @return true if it is undefined/null/empty, false if it contains value(s) * Array or String containing just whitespaces or >'< or >"< is considered empty */ function L_IsValueEmptyNullUndefined(inputVar) { if (typeof inputVar !== 'undefined' && inputVar !== null) { var strTemp = JSON.stringify(inputVar); strTemp = strTemp.replace(/\s+/g, ''); // remove all whitespaces strTemp = strTemp.replace(/\"+/g, ""); // remove all >"< strTemp = strTemp.replace(/\'+/g, ""); // remove all >'< if (strTemp !== '') { return false; } else { return true; } } else { return true; } } /** * Logs a message * @param string strMessage - die Message * @param string strType - don't add if [info], use "warn" for [warn] and "error" for [error] */ function L_Log(strMessage) { L_Log2(strMessage, 'info'); } function L_Log2(strMessage, strType) { var strMsgFinal = '[L] ' + strMessage + ''; if (strType === "error") { log(strMsgFinal, "error"); } else if (strType === "warn") { log(strMsgFinal, "warn"); } else { log(strMsgFinal, "info"); } } /********************************************* * Checks wether the current date/time is within the range provided. * Source: https://forum.iobroker.net/viewtopic.php?t=1072#p8484 * @param string strLower - a time as string, e.g. '20:00:00' * @param string strUpper - a time as string, e.g. '22:30:00' * @return boolean true if current time is within range, false if not ********************************************/ function L_IsTimeInRange(strLower, strUpper) { var now = new Date(); var lower = addTime(strLower); var upper = addTime(strUpper); var inRange = false; if (upper > lower) { // opens and closes in same day inRange = (now >= lower && now <= upper) ? true : false; } else { // closes in the following day inRange = (now >= upper && now <= lower) ? false : true; } return inRange; // Additional function needed to get a date/time value of the current date with the time provided as string function addTime(strTime) { var time = strTime.split(':'); var dNewDate = new Date(); var d = new Date(dNewDate.getFullYear(), dNewDate.getMonth(), dNewDate.getDate()); d.setHours(time[0]); d.setMinutes(time[1]); d.setSeconds(time[2]); return d; } }