Skip to content
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Logo
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. [Vorlage] JS: Log-Datei aufbereiten für VIS

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.1k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    1.8k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.1k

[Vorlage] JS: Log-Datei aufbereiten für VIS

Scheduled Pinned Locked Moved JavaScript
617 Posts 60 Posters 151.7k Views 49 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • MicM Mic

    @Negalein sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

    Beim Start kommen sofort Fehler.

    Oh, das sollte nicht sein. Von welcher Version kommst du denn?

    NegaleinN Offline
    NegaleinN Offline
    Negalein
    Global Moderator
    wrote on last edited by
    #452

    @Mic sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

    Oh, das sollte nicht sein. Von welcher Version kommst du denn?

    Habs gerade nochmal neu von deiner GIT-Seite kopiert und meine Config eingefügt.
    Mic-M iobroker.logfile-script  Parses log file, applies filters, and sets states for visualization.png

    Selbes Ergebnis

    javascript.0	2020-02-11 20:51:01.763	error	(2798) at process._tickCallback (internal/process/next_tick.js:68:7)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at promise.then (/opt/iobroker/node_modules/standard-as-callback/built/index.js:19:49)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at tryCatcher (/opt/iobroker/node_modules/standard-as-callback/built/utils.js:11:23)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at client.get (/opt/iobroker/node_modules/iobroker.js-controller/lib/states/statesInRedis.js:572:33)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at adapter.getForeignState (/opt/iobroker/node_modules/iobroker.javascript/main.js:1055:17)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at createProblemObject (/opt/iobroker/node_modules/iobroker.javascript/main.js:1464:17)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at prepareScript (/opt/iobroker/node_modules/iobroker.javascript/main.js:1411:37)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at compile (/opt/iobroker/node_modules/iobroker.javascript/main.js:1188:28)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at Object.createScript (vm.js:277:10)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) at new Script (vm.js:83:7)
    javascript.0	2020-02-11 20:51:01.763	error	(2798) SyntaxError: Identifier 'LOG_PATT' has already been declared
    javascript.0	2020-02-11 20:51:01.762	error	(2798) ^
    javascript.0	2020-02-11 20:51:01.762	error	(2798) const LOG_PATT = '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
    javascript.0	2020-02-11 20:51:01.762	error	at script.js.System.IOBroker_Log:413
    javascript.0	2020-02-11 20:51:01.762	error	(2798) script.js.System.IOBroker_Log compile failed:
    

    /*************************************************************************************************************************
     * ---------------------------
     * Log Script für ioBroker zum Aufbereiten des Logs für Visualisierungen (vis), oder um
     * auf Log-Ereignisse zu reagieren.
     * ---------------------------
     *
     * Das Script nimmt jeden neuen Logeintrag des ioBrokers und wendet entsprechend gesetzte
     * Filter an, um den Eintrag dann in den entsprechenden Datenpunkten dieses Scripts abzulegen.
     
     * Es stehen auch JSON-Datenpunkte zur Verfügung, mit diesen kann im vis eine
     * Tabelle ausgegeben werden (z.B. über das Widget 'basic - Table' oder 'materialdesign - Table').
     *
     * Aktuelle Version: https://github.com/Mic-M/iobroker.logfile-script
     * Support:          https://forum.iobroker.net/topic/13971/vorlage-log-datei-aufbereiten-f%C3%BCr-vis-javascript
     * Autor:            Mic (ioBroker) | Mic-M (github)
     * -----------------------------------------------------------------------------------------------------------------------
     * VORAUSSETZUNGEN
     * 1.) Nur falls Datenpunkte unterhalb '0_userdata.0' abgelegt werden sollen:
     *     In der Instanz des JavaScript-Adapters die Option [Erlaube das Kommando "setObject"] aktivieren.
     *     Siehe auch: https://github.com/Mic-M/iobroker.createUserStates
     * 2.) Dieses Script benötigt die JavaScript-Adapter-Version 4.3.0 (2019-10-09) oder höher.
     *     Wer eine ältere Version einsetzt: Bitte Script-Version 2.0.2 verwenden.
     * =====================================================================================
     * -----------------------------------------------------------------------------------------------------------------------
     * Change Log:
     *  4.0.1 Mic   - Add "jsonDateFormat: 'dd.mm. hh:mm'," to FILTER_LOG, id 'all'.
     *  4.0 Mic     + To allow individual settings per each defined LOG_FILTER, the following global 
     *                settings were moved to LOG_FILTER:
     *                 * JSON_DATE_FORMAT                   -> jsonDateFormat
     *                 * JSON_LEN                           -> jsonLogLength
     *                 * JSON_NO_ENTRIES                    -> jsonMaxLines
     *                 * JSON_APPLY_CSS_LIMITED_TO_LEVEL    -> jsonCssToLevel
     *                 * L_SORT_ORDER_DESC                  -> sortDescending
     *              + Code improvements
     *              + Renamed LOG_NO_OF_ENTRIES to MAX_LOG_LINES
     *  ---------------------------------------------------------------------------------------------------- 
     *  3.4 Mic     + Support both '0_userdata.0' and 'javascript.x' for state creation
     *  3.3 Mic     - Fix state path
     *  3.2 Mic     + Create all states under 0_userdata.0, and no longer under javascript.<instance> (like javascript.0)
     *  3.1 Mic     + Change to stable as tests were successful
     *              + Add new option REMOVE_PID: The js-controller version 2.0+ adds the PID number inside brackets 
     *                to the beginning of the message. Setting REMOVE_PID = false will remove it.
     *  3.0Alpha Mic + Major Change: JavaScript adapter 4.3+ now provides onLog() function: 
     *                 https://github.com/ioBroker/ioBroker.javascript/blob/master/docs/en/javascript.md#onlog 
     *                 We are using this new function to streamline this log script tremendously and to remove node-tail.
     *  ---------------------------------------------------------------------------------------------------- 
     *  2.0.2 Mic   + Changed certain functions to async to get rid of setTimout() and for the sake of better error handling.
     *              + startTailingProcess(): ensure the tailing starts if the file is present (wait to be created)
     *  2.0.1a Mic  Removed constant MERGE_LOGLINES_ACTIVE
     *  2.0.0a Mic  Major improvements and fixes:
     *              + Change from instant state update to schedule (STATE_UPDATE_SCHEDULE). The instant update, so once
     *                new log entries coming in, caused several issues (setting and getting state values (getState() and 
     *                setState()) within <1ms simply does not work.
     *              - Fix issue with merging log lines
     *              + Moved global option MERGE_LOGLINES_ACTIVE to LOG_FILTER, for allowing turning on/off for each filter id.
     *              + Several other code improvements
     *              Note: For upgrading from previous version: replace script entirely, re-enter all your options, 
     *                    and delete all existing states prior to first activation of this script.
     *  ---------------------------------------------------------------------------------------------------- 
     *  1.5.1 Mic - Set option MERGE_LOGLINES_ACTIVE to 'false' as default, as users reported issues. See 
     *              https://forum.iobroker.net/post/288772 . Also option MERGE_LOGLINES_ACTIVE being marked as "experimental"
     *              in the comments. Requires further investigation.
     *  1.5  Mic - Fix issue with option MERGE_LOGLINES_ACTIVE
     *  1.4  Mic + New option MERGE_LOGLINES_TXT for an individual (e.g. localized) string other than 'entries'.
                 - Fix JSON span class closing
     *  1.3  Mic + New option MERGE_LOGLINES_ACTIVE: Merge Loglines with same log message to only one line and adds leading
     *             '[123 entries]' to log message.
     *  1.2  Mic - Fixed issue #6 (Button javascript.0.Log-Script.logXxxx.clearJSON not working reliably)
     *  1.1  Mic + 1. 1.0x script seems to work reliable per user feedback and my own test, so pushing into 1.1 stable.
     *           + New state '.logMostRecent': provides just the most recent log entry to work with "on /
     *             subscribe" on this state and trigger actions accordingly.
     *  1.02 alpha  Mic  - fix restarting at 0:00 (note: restarting is needed due to log file name change)
     *  1.01 alpha  Mic  - fix: creating new file system log file only if not yet existing
     *  1.00 alpha  Mic  + Entirely recoded to implement node-tail (https://github.com/lucagrulla/node-tail).
     *  ----------------------------------------------------------------------------------------------------
     *  0.8.1 Mic - Fix: L_SORT_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 <span class='log-info'>xxx</span>
     *            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 (Datenpunkte) in den Objekten angelegt werden.
    // Es wird die Anlage sowohl unterhalb '0_userdata.0' als auch 'javascript.x' unterstützt.
    const LOG_STATE_PATH = '0_userdata.0.Log-Script';
    
    
    // Pfad zum Log-Verzeichnis auf dem Server.
    // Standard-Pfad unter Linux: '/opt/iobroker/log/'. Wenn das bei dir auch so ist, dann einfach belassen.
    const LOG_FS_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 MAX_LOG_LINES = 100;
    
    // Der js-Controller Version 2.0 oder größer fügt Logs teils vorne die PID in Klammern hinzu, 
    // also z.B. "(12234) Terminated (15): Without reason". 
    // Mit dieser Option lassen sich die PIDs aus den Logzeilen entfernen.
    const REMOVE_PID = true;
    
    /**
     * Schwarze Liste (Black list)
     * Falls einer dieser Satzteile/Begriffe in einer Logzeile enthalten ist, dann wird der Log-Eintrag
     * komplett ignoriert, egal was weiter unten eingestellt wird.
     * Dies dient dazu, um penetrante Logeinträge gar nicht erst zu berücksichtigen.
     * Bitte beachten: 
     * 1. Mindestens 3 Zeichen erforderlich, sonst wird es nicht berücksichtigt (würde auch wenig Sinn ergeben).
     * 2. Bestehende Datenpunkt-Inhalte dieses Scripts bei Anpassung dieser Option werden nicht nachträglich neu gefiltert,
     *    sondern nur alle neu hinzugefügten Log-Einträge ab Speichern des Scripts werden berücksichtigt.
     */
    const BLACKLIST_GLOBAL = [
        '<==Disconnect system.user.admin from ::ffff:', // web.0 Adapter
        'system.adapter.ical.0 terminated with code 0 (OK)', 
        'bring.0 Cannot get translations: RequestError',
        ' reconnected. Old secret ', // Sonoff
        'Popup-News readed...', // info.0 Adapter
        '[warn] Projects disabled : set editorTheme.projects.enabled=true to enable', // see https://forum.iobroker.net/topic/12260/
    	'',
    	'',
    ];
    
    /**
     * Zusatz-Einstellung für Option "merge" für LOG_FILTER (unter "Konfiguration: Datenpunkte und Filter"):
     * In MERGE_LOGLINES_TXT kann hier ein anderes Wort eingetragen werden, z.B. 'entries' oder 'Zeilen', damit [123 entries] 
     * oder [123 Zeilen] vorangestellt wird anstatt [123 Einträge].
     * HINWEIS: Falls MERGE_LOGLINES_TXT geändert wird: bitte alle Datenpunkte des Scripts löschen und dann Script neu starten.
     */
    const MERGE_LOGLINES_TXT = 'Einträge';
    
    /**
     *  Für JSON-Tabelle: Füge CSS-Klasse hinzu je nach Log-Level (debug, silly, info, warn und error), um Tabellen-Text zu formatieren.
     *  Beispiel für Log-Level "debug": ersetzt "xxx" durch "<span class='log-debug'>xxx</span>""
     *  Es wird jeweils "log-" vorangestellt, also: debug -> log-debug, silly -> log-silly, info -> log-info, etc.
     *  Etwa für 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; }
     *  Tipp: In LOG_FILTER kann dann bei den einzelnen Filtern mittels "jsonCssToLevel" eingestellt werden, dass das CSS
     *        nur  für die Spalte "level" (also debug, error, info) und nicht auf alle Spalten angewendet wird.
     */
    const JSON_APPLY_CSS = true;
    
    
    /*******************************************************************************
     * Konfiguration: Datenpunkte und Filter
     ******************************************************************************
     * Dies ist das Herzstück dieses Scripts: hier werden die Datenpunkte konfiguriert, die erstellt werden sollen. 
     * Hierbei kannst du entsprechend Filter setzen, also z.B. Wörter/Begriffe, die in Logeinträgen enthalten sein
     * müssen, damit sie in den jeweiligen Datenpunkten aufgenommen werden.
     * --------------------------------------------------------------------------------------------------------------------------
     * id:              Ein Begriff ohne Leerzeichen, z.B. "error", "sonoff", homematic, etc. Die ID wird dann Teil der
     *                  Datenpunkte, z.B. "javascript.0.Log-Script.logHomematic.log" mit automatisch vorangestelltem "log".
     * --------------------------------------------------------------------------------------------------------------------------
     * 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:       Schwarze Liste: 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.
     *                  HINWEIS: BLACKLIST_GLOBAL wird vorher schon angewendet, hier kannst du einfach nur noch eine individuelle 
     *                  Blackliste pro id definieren.
     * --------------------------------------------------------------------------------------------------------------------------
     * 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.
     * --------------------------------------------------------------------------------------------------------------------------
     * merge:           Log-Einträge mit gleichem Text zusammenfassen. Beispiel:
     *                      -----------------------------------------------------------------------------------
     *                      2019-08-17 20:00:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
     *                      2019-08-17 20:15:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
     *                      2019-08-17 20:30:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
     *                      -----------------------------------------------------------------------------------
     *                  Daraus wird dann nur noch eine Logzeile mit letztem Datum/Uhrzeit und hinzufügen von "[3 Einträge]":
     *                      -----------------------------------------------------------------------------------
     *                      2019-08-17 20:30:00.335 - info: javascript.0 [3 Einträge] script.js.Wetter: Wetterdaten abrufen.
     *                      -----------------------------------------------------------------------------------
     *                  Zum aktivieren: true eintragen, zum deaktivieren: false eintragen.
     * --------------------------------------------------------------------------------------------------------------------------
     * sortDescending:  Wenn true: Sortiert die Logeinträge absteigend, also neuester oben. 
     *                  Wenn false: Sortiert die Logeinträge aufsteigend, also ältester oben. 
     * --------------------------------------------------------------------------------------------------------------------------
     * jsonColumns:     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 Spalten eintragen, sondern nur 'date','level','source','msg'.
     * --------------------------------------------------------------------------------------------------------------------------
     * jsonDateFormat:  Datumsformat für JSON Log. Z.B. volles 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 4stellig (z.B. 2019), YY = Jahr 2stellig (z.B. 19), MM = Monat, DD = Tag, HH = Stunde, MM = Minute, 
     *                  SS = Sekunde. Groß- oder Kleinschreibung ist egal, d.h. YYYY ist das gleiche wie yy.
     *                  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, 'DD.MM. HH:MM' für '25.07. 19:37'
     * --------------------------------------------------------------------------------------------------------------------------
     * jsonLogLength:   Maximale Anzahl Zeichen jeder einzelnen Log-Meldung im JSON-Log. Alles was länger ist, wird abgeschnitten.
     * --------------------------------------------------------------------------------------------------------------------------
     * jsonMaxLines:    Maximale Anzahl der letzten Logeinträge im JSON-Log. Alle älteren werden entfernt.
     *                  Falls in MAX_LOG_LINES z.B. "100" gesetzt wird, wird hier bei 100 der Cut gemacht, selbst wenn in 
     *                  jsonMaxLines etwa 250 eingetragen wird. D.h. im Bedarf zuerst MAX_LOG_LINES anpassen/erhöhen.
     * --------------------------------------------------------------------------------------------------------------------------
     * jsonCssToLevel:  Wenn true, dann wird JSON_APPLY_CSS nur für die Spalte "level" (also debug, error, info) angewendet, 
     *                  aber nicht für die restlichen Spalten wie Datum, Log-Eintrag, etc.
                        Falls alle Spalten das CSS bekommen sollen: auf false setzen.
     * --------------------------------------------------------------------------------------------------------------------------
     *
     * WEITERER HINWEIS: 
     * Bestehende Datenpunkt-Inhalte dieses Scripts bei Anpassung dieser Option werden nicht nachträglich neu 
     * gefiltert, sondern nur alle neu hinzugefügten Log-Einträge ab Speichern des Scripts werden berücksichtigt.
     * --------------------------------------------------------------------------------------------------------------------------
     */
    const LOG_FILTER = [
    
      // 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 jsonColumns 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'],
        merge:          true,
        sortDescending: true,
        jsonDateFormat: 'dd.mm. hh:mm',       
        jsonColumns:    ['source','date','msg'],
        jsonLogLength:  100,
        jsonMaxLines:   10,
        jsonCssToLevel: true,
      }, 
    */
    
    /*
      {
        id:             'all',    // Beispiel "all": hier kommen alle Logeinträge rein, keine Filterung
        filter_all:     ['', ''], // wird ignoriert, wenn leer
        filter_any:     ['', ''], // wird ignoriert, wenn leer
        blacklist:      ['', ''], // wird ignoriert, wenn leer
        clean:          ['', '', ''], // wird ignoriert, wenn leer
        merge:          true,
        sortDescending: true,
        jsonDateFormat: 'dd.mm. hh:mm',       
        jsonColumns:    ['date','level','source','msg'],  // Spaltenreihenfolge für JSON (Tabelle in vis)
        jsonLogLength:  100,
        jsonMaxLines:   10,
        jsonCssToLevel: true,
      },
    */
    
    /*
      {
        id:             'info',
        filter_all:     [' - info: '], // nur Logeinträge mit Level 'info'
        filter_any:     ['', ''],
        blacklist:      ['', ''],
        clean:          ['', '', ''],
        merge:          true,
        sortDescending: true,
        jsonDateFormat: 'dd.mm. hh:mm',
        jsonColumns:    ['date','level','source','msg'],
        jsonLogLength:  100,
        jsonMaxLines:   60,
        jsonCssToLevel: true,
      },
    */
    
      {
        id:             'debug',
        filter_all:     [' - debug: ', ''],  // nur Logeinträge mit Level 'debug'
        filter_any:     [''],
        blacklist:      ['', '', ''],
        clean:          ['', '', ''],
        merge:          true,
        sortDescending: true,
        jsonColumns:    ['date','level','source','msg'],
        jsonDateFormat: 'dd.mm. hh:mm',
        jsonLogLength:  100,
        jsonMaxLines:   60,
        jsonCssToLevel: true,
      },
      {
        id:             'warnanderror',
        filter_all:     ['', ''],
        filter_any:     [' - error: ', ' - warn: '],  // nur Logeinträge mit Levels 'warn' und 'error'
        blacklist:      ['', 'no playback content', 'Ignore! Actual secret is '],
        clean:          ['', '', ''],
        merge:          true,
        sortDescending: true,
        jsonDateFormat: 'dd.mm. hh:mm',
        jsonColumns:    ['date','level','source','msg'],
        jsonLogLength:  100,
        jsonMaxLines:   60,
        jsonCssToLevel: true,
      },
    
    /*
      {
        // Beispiel, um einen bestimmten Adapter zu überwachen.
        // Hier werden alle Fehler und Warnungen des Homematic-Adapters hm-rpc.0 gelistet.
        id:             'homematic',
        filter_all:     ['hm-rpc.0', ''],  // hm-rpc.0 muss enthalten sein.
        filter_any:     [' - error: ', ' - warn: '],  // entweder error oder warn
        blacklist:      ['', '', ''],
        clean:          ['', '', ''],
        merge:          true,
        sortDescending: true,
        jsonDateFormat: 'dd.mm. hh:mm',
        jsonColumns:    ['date','level','source','msg'],
        jsonLogLength:  100,
        jsonMaxLines:   60,
        jsonCssToLevel: true,
      },
    */
    
    ];
    
    
    /*******************************************************************************
     * 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 dieses Scripts im Log ausgegeben werden dürfen, bei false bleiben die Infos komplett weg.
    const LOG_INFO = true;
    
    
    /*******************************************************************************
     * Experten-Konfiguration
     ******************************************************************************/
    
    // Wie oft Datenpunkte aktualisieren?
    // Neu reinkommende Logeinträge werden erst mal gesammelt (in Variable G_NewLogLinesArrayToProcess). Diese werden dann 
    // regelmäßig in den Datenpunkten geschrieben. Sinnvoll ist hier nicht kürzer als 2-3 Sekunden, und nicht länger als 
    // ein paar Minuten. Zu kurzes Intervall: Script kommt nicht mehr nach. Zu lange: falls viele Logeinträge reinkommen, 
    // kann sich vieles "aufstauen" zur Abarbeitung. Benutze den "Cron"-Button oben rechts für komfortable Einstellung.
    const STATE_UPDATE_SCHEDULE = '*/1 * * * *'; // jede Minute
    
    
    // Leer lassen! Nur setzen, falls ein eigener Filename für das Logfile verwendet wird für Debug.
    const DEBUG_CUSTOM_FILENAME = '';
    
    // 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 z.B.: '2018-07-22 12:45:02.769  - info: javascript.0 Stop script script.js.ScriptAbc'
    // Da als String, wurden alle Backslashes "\" mit einem zweiten Backslash escaped.
    const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
    
    // Debug: Ignore. Wenn dieses String in der Logzeile enthalten ist, dann ignorieren wir es.
    // Dient dazu, dass wir während des Scripts ins Log schreiben können, ohne dass das dieses Script berücksichtigt.
    const DEBUG_IGNORE_STR = '[LOGSCRIPT_IGNORE]'; // Muss ein  individuelles String sein. Sonst gibt es ggf. eine Endlos-Schleife.
    
    
    // Debug: Prüfen, ob jede Logzeile erfasst wird, in dem wir diese direkt danach noch mal ins Log schreiben.
    // Bitte nur auf Anweisung vom Entwickler einschalten. Sonst wird jeder Logeintrag noch einmal wiederholt, 
    // mit führendem DEBUG_EXTENDED_STR am Anfang und max. DEBUG_EXTENDED_NO_OF_CHARS an Anzahl Zeichen.
    const DEBUG_EXTENDED = false;
    const DEBUG_EXTENDED_STR = '[LOGSCRIPT_DEBUG_EXTENDED]'; // Muss ein  individuelles String sein. Sonst gibt es ggf. eine Endlos-Schleife.
    const DEBUG_EXTENDED_NO_OF_CHARS = 120;
    
    /*************************************************************************************************************************
     * Ab hier nichts mehr ändern / Stop editing here!
     *************************************************************************************************************************/
    
    
    /*************************************************************************************************************************
     * Global variables and constants
     *************************************************************************************************************************/
    
    // Final state path
    const FINAL_STATE_LOCATION = validateStatePath(LOG_STATE_PATH, false);
    const FINAL_STATE_PATH = validateStatePath(LOG_STATE_PATH, true);
    
    // 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 z.B.: '2018-07-22 12:45:02.769  - info: javascript.0 Stop script script.js.ScriptAbc'
    // Da als String, wurden alle Backslashes "\" mit einem zweiten Backslash escaped.
    const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
    
    // Final number of VIS states. We do not allow more than 9.
    function finalVisStatesQty(qty) {
        let result = 0;
        if( (!isLikeEmpty(qty)) && (qty > 0) )  {
            if(qty > 9) {
                result = 9;
            } else {
                result = qty;
            }
        }
        return result; 
    };
    const FINAL_NUM_OF_VIS_VIEWS = finalVisStatesQty(NUMBER_OF_VIS_VIEWS);
    
    // Merge loglines: define pattern (and escape the merge text)
    // We added an additional backslash '\' to each backslash as these need to be escaped.
    const MERGE_REGEX_PATT = '^\\[(\\d+)\\s' + escapeRegExp(MERGE_LOGLINES_TXT) + '\\]\\s(.*)';
    
    // Log Handler variable for ioBroker function onLog()
    let G_LogHandler;  // being set later
    
    // Schedule for logfile update
    let G_Schedule_StateUpdate; // being set later
    
    // We add here all the new log lines to be processed regularly (per STATE_UPDATE_SCHEDULE);
    let G_NewLogLinesArrayToProcess = [];
    
    /*************************************************************************************************************************
     * init - This is executed on every script (re)start.
     *************************************************************************************************************************/
    init();
    function init() {
    
        // Unsubscribe log handler
        onLogUnregister(G_LogHandler);
        
        // Create all script states
        const NEEDED_STATES = buildNeededStates();
        createUserStates(FINAL_STATE_LOCATION, false, NEEDED_STATES[0], function() { // force = false
            createUserStates(FINAL_STATE_LOCATION, true, NEEDED_STATES[1], function() { // force = true
                
                // -- All states created, so we continue by using callback
    
                // Subscribe on changes: Pressed button "clearJSON"
                subscribeClearJson();
    
                // Subscribe to log handler
                G_LogHandler = onLog('*', data => { // please ignore the red squiggly underline under '*', see Github issue: https://github.com/ioBroker/ioBroker.javascript/issues/457
                    processNewLogLine(data);
                });
    
                // Schedule writing changes into states
                clearSchedule(G_Schedule_StateUpdate);
                G_Schedule_StateUpdate = schedule(STATE_UPDATE_SCHEDULE, processNewLogsPerSchedule);
    
                // Subscribe to clear all states
                stateSubscribeClearAllJSON()
    
                // Subscribe to states for the VIS views
                if(FINAL_NUM_OF_VIS_VIEWS > 0) {
                    stateSubscribeVisWhichFilter(); // which Filter
                    stateSubscribeVisClearJson(); // clear JSON
                }
    
                // Message
                if (LOG_INFO) log('Start monitoring of the ioBroker log...', 'info');
    
            });
        });
    
    }
    
    function processNewLogLine(data) {
        
        // Convert to Log Line
        // TODO: This is a quick implementation of new function onLog().
        //       We need to entirely rewrite script later to fully use the data object.
        //       However, at this time, we convert it to a standard log line being expected.
    
        // First, remove PID if desired
        let msg = data.message;
        if (REMOVE_PID) msg = removePID(msg);
    
        // Now convert to log line
        let newLogEntry = timestampToLogDate(data.ts) + '  - ' + data.severity + ': ' + msg;
    
        // Check if we have DEBUG_IGNORE_STR in the new log line
        if(! newLogEntry.includes(DEBUG_IGNORE_STR)) {
    
            if (newLogEntry.length > 45) {  // a log line with less than 45 chars is not a valid log line.
    
                // Cleanse and apply blacklist
                newLogEntry = cleanseLogLine(newLogEntry);
    
                // Push result into logArrayFinal
                G_NewLogLinesArrayToProcess.push(newLogEntry);
    
                // some debugging
                if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + '===============================================================');
                if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'New Log Entry, Len (' + newLogEntry.length + '), content: [' + newLogEntry + ']');
    
                // This is for debugging purposes, and it will log every new log entry once again. See DEBUG_EXTENDED option above.
                if (DEBUG_EXTENDED) {
                    if (! newLogEntry.includes(DEBUG_EXTENDED_STR)) { // makes sure no endless loop here.
                        log(DEBUG_EXTENDED_STR + newLogEntry.substring(0, DEBUG_EXTENDED_NO_OF_CHARS));
                            
                    }
                }
            }
        }
    
    }
    
    /**
     * Called per schedule STATE_UPDATE_SCHEDULE.
     * It processes G_NewLogLinesArrayToProcess
     */
    function processNewLogsPerSchedule() {
        if (! isLikeEmpty (G_NewLogLinesArrayToProcess) ) {
    
            // We use array spreads '...' to copy array. If not, array is changed by reference and not value.
            // That means, if we change the target array, it will also change the source array.
            // See https://stackoverflow.com/questions/7486085/copy-array-by-value
            let logArrayToProcess = [...G_NewLogLinesArrayToProcess];
            G_NewLogLinesArrayToProcess.length = 0; // emptying array. https://stackoverflow.com/questions/4804235/difference-between-array-length-0-and-array
    
            /**
             * Apply the filters as set in LOG_FILTER and split up log levels into elements of an array
             * logArrayToProcessFiltered will look as follows:
             *   logArrayToProcessFiltered = [
             *     ['info':'15.08.2019 09:27:55.476 info adapt.0 some log', 'error':''],
             *     ['info':'15.08.2019 09:33:58.522 info adapt.0 some more log', 'error':''],
             *     ['info':'', 'error':'15.08.2019 09:37:55.807 error adapt.0 some error log']
             *   ]
             */
            let logArrayToProcessFiltered = [];
            for (let lpEntry of logArrayToProcess) {
                let logEntryFilteredArray = applyFilter(lpEntry);
                logArrayToProcessFiltered.push(logEntryFilteredArray);
            }
    
            // Further process and finally set states with our results.
            processLogArrayAndSetStates(logArrayToProcessFiltered);
    
        }
    
        // Finally, set updatestate with current date/time
        setState(FINAL_STATE_PATH + '.All.lastTimeUpdated', Date.now());    
    
    }
    
    
    
    /*************************************************************************************************************************
     * Filtering
     *************************************************************************************************************************/
    
    /**
     * This function applies the filters as set in LOG_FILTER.
     * Also, it splits up the log levels into elements of an array we return by this function.
     * @param {string} strLogEntry
     * @return {array}  split up log levels as elements within this array, like: ['info':'logtext', 'error':'logtext'] etc.
     */
    function applyFilter(strLogEntry) {
        // We add one element per each filter to the Array ('all', 'error', etc.)
        let logArrayProcessed = [];
        for (let j = 0; j < LOG_FILTER.length; j++) {
            logArrayProcessed[LOG_FILTER[j].id] = '';
        }
    
        // We apply regex here. This will also eliminate all log lines without proper info
        // like date/time, log level, and entry.
        let arrSplitLogLine = logLineSplit(strLogEntry);
        if (arrSplitLogLine !== false) {
    
            if (isLikeEmpty(LOG_FILTER) === false) {
                        
                // Now let's iterate 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 (let k = 0; k < LOG_FILTER.length; k++) {
                    if ( (strMatchesTerms(strLogEntry, LOG_FILTER[k].filter_all, 'every') === true)
                    && (strMatchesTerms(strLogEntry, LOG_FILTER[k].filter_any, 'some') === true)
                    && (strMatchesTerms(strLogEntry, LOG_FILTER[k].blacklist, 'blacklist') === false) ) {
                        logArrayProcessed[LOG_FILTER[k].id] = logArrayProcessed[LOG_FILTER[k].id] + strLogEntry + "\n";
                    }
                    
                    // Now we remove terms if desired
                    if (isLikeEmpty(LOG_FILTER[k].clean) === false) {
                        for (let lpTerm of LOG_FILTER[k].clean) {
                            if (lpTerm !== '') {
                                logArrayProcessed[LOG_FILTER[k].id] = logArrayProcessed[LOG_FILTER[k].id].replace(lpTerm, '');
                            }
                        }
                    }
                }
            }
        }
        return logArrayProcessed;
    }
    
    /*************************************************************************************************************************
     * Further processing
     *************************************************************************************************************************/
    
    /**
     * Further processes the log array and set states accordingly.
     * 
     * @param  arrayLogInput             The Array of the log input.
     *                                   Array is like: 
     *                                   [
     *                                      ['info':'15.08.2019 09:27:55.476 info adapt.0 some log', 'error':''],
     *                                      ['info':'15.08.2019 09:33:58.522 info adapt.0 some more log', 'error':''],
     *                                      ['info':'', 'error':'15.08.2019 09:37:55.807 error adapt.0 some error log'],
     *                                   ]
     **/
    function processLogArrayAndSetStates(arrayLogInput) {
    
        /*****************
         * [1] Build array from LOG_FILTER. Looks like: arrayFilterIds = ['info', 'error', 'warn'].
         * Also, build result array to keep our results. Lools like resultArr = [info: '', error: '', warn: '']
         *****************/
        let arrayFilterIds = [];
        let resultArr = [];
        for (let i = 0; i < LOG_FILTER.length; i++) {
            arrayFilterIds.push(LOG_FILTER[i].id); // each LOG_FILTER id into array
            resultArr[LOG_FILTER[i].id] = '';
        }
        /*****************
         * [2] Process element by element, so ['info':'log test', 'error':'log test'] of given array.
         * We fill the result array accordingly.
         *****************/
        for (let lpElement of arrayLogInput) {
    
            // Loop thru our new array arrayFilterIds and fill result array
            for (let k = 0; k < arrayFilterIds.length; k++) {
    
                // some variables
                let lpFilterId = arrayFilterIds[k]; // Filter ID from LOG_FILTER, like 'error', 'info', 'custom', etc.
                let lpNewLogLine = lpElement[lpFilterId]; // Current log line of provided array element of 'error', 'info', 'custom' etc.
    
                if (isLikeEmpty(lpNewLogLine)) {
                    // No log content for the given filter id.
                    if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Filter  [' + lpFilterId + ']: No match.');
                } else {
    
                    if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Filter [' + lpFilterId + ']: Match! New Log Line length: (' + lpNewLogLine.length + ')');
    
                    // Append new log line to result array
                    if (isLikeEmpty(resultArr[lpFilterId])) {
                        resultArr[lpFilterId] = lpNewLogLine; 
                    } else {
                        resultArr[lpFilterId] = lpNewLogLine + resultArr[lpFilterId]; // "\n" not needed, always added above
                    }
                }
            }
        }
    
        /*****************
         * [3] We merge with the current state.
         *****************/
        for (let k = 0; k < arrayFilterIds.length; k++) {
            let lpFilterId = arrayFilterIds[k]; // Filter ID from LOG_FILTER, like 'error', 'info', 'custom', etc.
            let lpStatePath1stPart = FINAL_STATE_PATH + '.log' + cleanseStatePath(lpFilterId); // Get Path to state
            let lpNewFinalLog = resultArr[lpFilterId];
    
            if (! isLikeEmpty(lpNewFinalLog) )  {
    
                // Get state value
    			let strCurrentStateLog = getState(lpStatePath1stPart + '.log').val; // Get state contents of loop item
                
                // Add state log lines to our final log
                if (! isLikeEmpty(strCurrentStateLog)) {
                    lpNewFinalLog = lpNewFinalLog + strCurrentStateLog; // "\n" not needed, always added above
                }            
    
                // Convert to array for easier handling
                let lpNewFinalLogArray = lpNewFinalLog.split(/\r?\n/);
    
                // Remove duplicates
                lpNewFinalLogArray = arrayRemoveDublicates(lpNewFinalLogArray);
    
                // Remove empty values
                lpNewFinalLogArray = cleanArray(lpNewFinalLogArray);
    
                // Sort array descending
                lpNewFinalLogArray = sortLogArrayByDate(lpNewFinalLogArray, 'desc');
    
                // Merge Loglines if multiple values and add leading '[123 entries]' to log message
                let doMerge = getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'merge');
                if (doMerge || doMerge === 'true') {    // also check for string 'true' in case user used string
                    lpNewFinalLogArray = mergeLogLines(lpNewFinalLogArray);
                }
    
                // We need a separate array for JSON
                let lpNewFinalLogArrayJSON = lpNewFinalLogArray;
    
                // Let's remove elements if time of when button '.clearJSON' was pressed is greater than log date.
                lpNewFinalLogArrayJSON = clearJsonByDate(lpNewFinalLogArrayJSON, lpStatePath1stPart + '.clearJSON');              
    
                // Just keep the first x elements of the log. JSON log length is being set individually.
                lpNewFinalLogArray = lpNewFinalLogArray.slice(0, MAX_LOG_LINES);
                lpNewFinalLogArrayJSON = lpNewFinalLogArrayJSON.slice(0, getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'jsonMaxLines'));
    
                // Get just the most recent log entry into string
                let lpMostRecent = lpNewFinalLogArray[0];
    
                // Sort ascending if desired
                if (!getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'sortDescending')) {
                    lpNewFinalLogArray = lpNewFinalLogArray.reverse();
                    lpNewFinalLogArrayJSON = lpNewFinalLogArrayJSON.reverse();
                }
    
                // ** Finally set the states
    
                ///////////////////////////////
                // -1- Full Log, String, separated by "\n"
                ///////////////////////////////
                let strResult = lpNewFinalLogArray.join("\n");
                if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'New length to be set into state: (' + strResult.length + '), state: [' + lpStatePath1stPart + '.log' + ']');
    
                setState(lpStatePath1stPart + '.log', strResult);
    
                ///////////////////////////////
                // -2- JSON, with elements date and msg
                ///////////////////////////////
                // Let's put together the JSON
                let jsonArr = [];
                for (let j = 0; j < lpNewFinalLogArrayJSON.length; j++) {
                    // Get 4 elements in array: datetime, level, source, message
                    let arrSplitLogLine = logLineSplit(lpNewFinalLogArrayJSON[j]);
                    if (arrSplitLogLine !== false) {
                        let strLogMsg = arrSplitLogLine.message;
                        // Reduce the length for each log message per "jsonLogLength"
                        strLogMsg = strLogMsg.substr(0, LOG_FILTER[k].jsonLogLength);
                        // ++++++
                        // Build the final Array
                        // ++++++
                        // We need this section to generate the JSON with the columns (which ones, and order) as specified in LOG_FILTER
                        let objectJSONentry = {}; // object (https://stackoverflow.com/a/13488998)
                        if (isLikeEmpty(LOG_FILTER[k].jsonColumns)) log('Columns not specified in "jsonColumns".', 'warn');
                        // Prepare CSS
                        let strCSS1, strCSS2;
                        let strCSS1_level, strCSS2_level;
                        if (JSON_APPLY_CSS) {
                            strCSS1 = "<span class='log-" + arrSplitLogLine.level + "'>";
                            strCSS2 = '</span>';
                            strCSS1_level = strCSS1;
                            strCSS2_level = strCSS2;
                            if (LOG_FILTER[k].jsonCssToLevel) {
                                strCSS1 = '';
                                strCSS2 = '';
                            }
                        }
    
                        for (let lpCol of LOG_FILTER[k].jsonColumns) {
                            switch (lpCol) {
                                case 'date' :
                                    objectJSONentry.date = strCSS1 + formatLogDateStr(arrSplitLogLine.datetime, LOG_FILTER[k].jsonDateFormat) + 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 (! isLikeEmpty(lpNewFinalLogArrayJSON)) {
                    setState(lpStatePath1stPart + '.logJSON', JSON.stringify(jsonArr));
                    setState(lpStatePath1stPart + '.logJSONcount', lpNewFinalLogArrayJSON.length);
                } 
            }
        }
    
        /*****************
         * [4] Finally done. Now we set states for the VIS views.
         *****************/
        if(FINAL_NUM_OF_VIS_VIEWS > 0) {
            
            setTimeout(function() { // Apply timeout, to ensure all states are set.
    
                for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                    let lpStateViewSelFilter = FINAL_STATE_PATH + '.All.visView' + (i+1); // like: 0_userdata.0.Log-Script.All.visView1.whichFilter
                    let currentVisFilter = getState(lpStateViewSelFilter + '.whichFilter').val;
                    if(! isLikeEmpty(currentVisFilter)) {
                        let jsonStateToGet = LOG_STATE_PATH + '.log' + currentVisFilter + '.logJSON';
                        if( isState(jsonStateToGet) ) {
                            setState(lpStateViewSelFilter + '.outputJSON', getState(jsonStateToGet).val); // Finally: set state
                            setState(lpStateViewSelFilter + '.outputJSONcount', getState(jsonStateToGet + 'count').val); // Finally: set state
                        } else {
                            log('Log-Script-Fehler: Gewählter Filter ' +  currentVisFilter + ', aber Datenpunkt [' + jsonStateToGet + '] ist nicht vorhanden.', 'error');
                        }
                    }
                }
            }, 2000);
        }
    }
    
    /*************************************************************************************************************************
     * Script specific supporting functions
     *************************************************************************************************************************/
    
    /**
     * Subscribe to all Log-Script.logXXXX.clearJSON
     * This will allow to clear JSON log if button is pressed.
     */
    function subscribeClearJson() {
        // Set current date to state if button is pressed
        let logSubscribe = '';
        for (let i = 0; i < LOG_FILTER.length; i++) {
            let lpFilterId = cleanseStatePath(LOG_FILTER[i].id);
            let lpStateFirstPart = FINAL_STATE_PATH + '.log' + lpFilterId;
            logSubscribe += ( (logSubscribe === '') ? '' : ', ') + lpFilterId;
            on({id: lpStateFirstPart + '.clearJSON', change: 'any', val: true}, function(obj) {
                let stateBtnPth = obj.id // e.g. [javascript.0.Log-Script.logInfo.clearJSON]
                let firstPart = stateBtnPth.substring(0, stateBtnPth.length-10); // get first part of obj.id, like "javascript.0.Log-Script.logInfo"
                let filterID = firstPart.slice(firstPart.lastIndexOf('.') + 1); // gets the filter id, like "logInfo"
                if (SCRIPT_DEBUG) log(DEBUG_IGNORE_STR + 'Clear JSON states for [' + filterID + '].');
                // We clear the according JSON states
                setState(firstPart + '.logJSON', '[]');
                setState(firstPart + '.logJSONcount', 0);
    
            });
        }
        if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Subscribing to Clear JSON Buttons: ' + logSubscribe)
    }
    
    /**
     * Subscribe to all .All.clearAllJSON
     */
    function stateSubscribeClearAllJSON() {
    
        on({id: FINAL_STATE_PATH + '.All.clearAllJSON', change: 'any', val: true}, function (obj) {
    
            // 1. All fitered log states
            const ALL_FILTER_IDS = getAllFilterIds();
            for (let loopFilterId of ALL_FILTER_IDS) {
                // Version 4.5.6
                // See https://forum.iobroker.net/post/375932
                // We do no longer set the target states directly, but use the according clearJSON instead.
                // This is to ensure that user can use the clearJSON timestamp for the time of last clearJSON.
                setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.clearJSON', true);
                // setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.logJSON', '[]');
                // setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.logJSONcount', 0);
    
            }
    
            // 2. Vis add-ons
            if(FINAL_NUM_OF_VIS_VIEWS > 0) {
                for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                    let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                    setState(lpState + '.outputJSON', '[]');
                    setState(lpState + '.outputJSONcount', 0);
                }
            }
        });
    
    }
    
    
    /**
     * Subscribe to .All.visViewX.whichFilter
     */
    function stateSubscribeVisWhichFilter() {
        if(FINAL_NUM_OF_VIS_VIEWS > 0) {
            for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                on({id: lpState + '.whichFilter', change: 'ne'}, function (obj) {
                    if(!isLikeEmpty(obj.state.val)) {
                        let firstStatePart = obj.id.substr(0, (obj.id.length - obj.id.split('.').pop().length - 1)); // Gibt von '0_userdata.0.System.Log-Script.All.visView1.whichFilter' nur den Hauptteil ohne String nach letztem Punkt zurück, also "0_userdata.0.System.Log-Script.All.visView1"
                        setState(firstStatePart + '.' + 'outputJSON', getState(LOG_STATE_PATH + '.log' + obj.state.val + '.logJSON').val);
                        setState(firstStatePart + '.' + 'outputJSONcount', getState(LOG_STATE_PATH + '.log' + obj.state.val + '.logJSONcount').val);
                    }
                });
            }
        }
    }
    
    /**
     * Subscribe to all .All.visViewX.clearJSON
     */
    function stateSubscribeVisClearJson() {
        if(FINAL_NUM_OF_VIS_VIEWS > 0) {
            for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                on({id: lpState + '.clearJSON', change: 'any', val: true}, function (obj) {
                    let firstStatePart = obj.id.substr(0, (obj.id.length - obj.id.split('.').pop().length - 1)); // Gibt von '0_userdata.0.System.Log-Script.All.visView1.clearJSON' nur den Hauptteil ohne String nach letztem Punkt zurück, also "0_userdata.0.System.Log-Script.All.visView1"
                    let selectedFilter = getState(firstStatePart + '.whichFilter').val;
                    if(!isLikeEmpty(selectedFilter)) {    
                        setState(firstStatePart + '.outputJSON', '[]'); // .All.visViewX.outputJSON leeren
                        setState(firstStatePart + '.outputJSONcount', 0); // .All.visViewX.outputJSONcount leeren
                        setState(FINAL_STATE_PATH + '.log' + selectedFilter + '.clearJSON', true); // Log-Script.logXXXX.clearJSON leeren
                    }             
                });
            }
        }
    }
    
    /**
     * Get all filter ids into an array (Homematic, Warnanderror, etc.)
     * @return [array] Filter ids in Array
     */
    function getAllFilterIds() {
        let allFilterIds = [];
        for(let i = 0; i < LOG_FILTER.length; i++) {
            let lpIDClean = cleanseStatePath(LOG_FILTER[i].id)
            if (LOG_FILTER[i].id !== '') allFilterIds.push(lpIDClean); // für VIS add-on states
        };
        return allFilterIds;
    }
    
    
    /**
     * Reformats a log date string accordingly
     * @param {string}    strDate   The date to convert
     * @param {string}    format    e.g. 'yyyy-mm-dd HH:MM:SS'. Both upper case and lower case letters are allowed.
     * @return {string}             Returns the resulting date string
     */
    function formatLogDateStr(strDate, format) {
    
        let strResult = format.toLowerCase();
        strResult = strResult.replace('yyyy', strDate.substr(0,4));
        strResult = strResult.replace('yy', strDate.substr(2,2));
        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;
    
    }
    
    /**
     * Cleanse the log line
     * @param {string}   logLine    The log line to be cleansed.
     * @return {string}             The cleaned log line
     */
    function cleanseLogLine(logLine) {
        // Remove color escapes - https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings
        let logLineResult = logLine.replace(/\u001b\[.*?m/g, ''); 
        // Sometimes, a log line starts with the term "undefined", so we remove it.
        if (logLineResult.substr(0,9) === 'undefined') logLineResult = logLineResult.substr(9,99999);
        // Remove white space, tab stops, new line
        logLineResult = logLineResult.replace(/\s\s+/g, ' ');
        // Check against global blacklist
        if(strMatchesTerms(logLineResult, BLACKLIST_GLOBAL, 'blacklist')) logLineResult = '';
    
    
        return logLineResult;
    }
    
    
    
    /**
     * Sorts the log array by date. We expect the first 23 chars of each element being a date in string format.
     * @param {array} inputArray       Array to process
     * @param {string}  order          'asc' or 'desc' for ascending or descending order
     */
    function sortLogArrayByDate(inputArray, order) {
        var result = inputArray.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;
    }
    
    
    /**
     * Splits a given log entry into an array with 4 elements.
     * @param {string}  inputValue  Log line like '2018-07-22 11:47:53.019  - info: javascript.0 script.js ...'
     * @return {object}   Array with 4 elements: 
     *                     0. datetime (e.g. 2018-07-22 11:47:53.019),
     *                     1. level (e.g. info)
     *                     2. source (e.g. javascript.0)
     *                     3. message (e.g. script.js....)
     *                     Returns FALSE if no match or input value not valid
     */
    function logLineSplit(inputValue) {
    
        // Get RegEx ready
        let mRegEx = new RegExp(LOG_PATT, 'g');
    
        // Split
        let returnObj = {}
        let m;
        do {
            m = mRegEx.exec(inputValue);
            if (m) {
                returnObj.datetime = m[1];
                returnObj.spaceAt2 = m[2];
                returnObj.level = m[3];
                returnObj.spaceAt4 = m[4];
                returnObj.source = m[5];
                returnObj.spaceAt6 = m[6];
                returnObj.message = m[7];
            } 
        } while (m);
    
        // Now we check if we have valid entries we want
        if ((returnObj.datetime === undefined)
            || (returnObj.level === undefined)
            || (returnObj.source === undefined)
            || (returnObj.message === undefined)
        ) {
           return false; // no valid hits
        }
        // We can return the array now, since it meets all requirements
        return returnObj;
    
    }
    
    /**
     * Merges date/time, level, source and message to a logline
     * @param  {array}    inputValue   Array with 4 elements: date/time, level, source, message
     * @return {string}   Merged log line as string. Empty string '', if input value not valid.
    																			  
     */
    function logLineMerge(inputValue) {
    
        if (inputValue.length === 4) {
            let mergedLine = inputValue[0] + ' - ' + inputValue[1] + ': ' + inputValue[2] + ' ' + inputValue[3];
            return mergedLine;
        } else {
            // We expect a size of 4, so go out
            return '';
        }
    
    }
    
    
    /**
     * Merge Loglines if multiple values and add leading '[123 entries]' to log message
     * @param {array}  logArray        array of log entries
     * @return {array} the new merged log array
     */
    function mergeLogLines(logArray) {
    
        // We use array spreads '...' to copy array. If not, array is changed by reference and not value.
        // That means, if we change the target array, it will also change the source array.
        // See https://stackoverflow.com/questions/7486085/copy-array-by-value
        let arrCopy = [...logArray];
        let arrNew = [];
    
        for (let i = 0; i < arrCopy.length; i++) {
    
            if (! isLikeEmpty(arrCopy[i])) {
    
                let lpEntry = arrCopy[i];
                let lineWithoutDate = lpEntry.substring(23);
                let lpLineSplit = logLineSplit(lpEntry);
    
                // Get multiple values
                let lpMulti = arrayGetElements(arrCopy, removeLeading123entries(lpLineSplit.message), false);
                let result = lpEntry;
                let lineCounter = 0;
                if (lpMulti.length > 1) { // Treffer - die aktuelle Zeile zählt ja auch mit.
                    lineCounter = lpMulti.length;
                    let hitLeadingNumber = -1;
                    for (let hitLine of lpMulti) {
                        let hitLineSplit = logLineSplit(hitLine);
                        // Check if hit contains '[123 entries]'. If yes, get the number out of it into lineCounter.
                        // If not, we just count with 1.
                        hitLeadingNumber = checkForMultiEntry(hitLineSplit.message);
                        if (hitLeadingNumber > 1) {
                            lineCounter = hitLeadingNumber + lpMulti.length - 1;
                        }
                    }
                } else {
                    lineCounter = 1;
                }
    
                if (lineCounter > 1) {
    
                        // remove from array by filling empty value
                        arrCopy = arrayReplaceElementsByValue(arrCopy, removeLeading123entries(lpLineSplit.message), '', false);
                        // new result
                        result = logLineMerge([lpLineSplit.datetime, lpLineSplit.level, lpLineSplit.source, '[' + lineCounter + ' ' + MERGE_LOGLINES_TXT + '] ' + removeLeading123entries(lpLineSplit.message)]);
                }
                arrNew.push(result);
            } 
        }
    
        return arrNew;
    
        /**
         * @param  {string}   strInput    A log message with potential leading '[123 entries]'
         * @return {string}   string without leading '[123 entries]', if it is there
         */
        function removeLeading123entries(strInput) {
    
            let mRegEx = new RegExp(MERGE_REGEX_PATT);
            let matches = mRegEx.exec(strInput);
            if (matches === null) {
                return strInput;
            } else {
                return matches[2];
            }
        }
    
        /**
         * @param  {string}   strInput    A log message checking for leading '[123 entries]'
         * @return {number}   returns the number 123 from '[123 entries]' if any match, or -1 if not found
         */
        function checkForMultiEntry(strInput) {
    
            // Get RegEx ready
            let mRegEx = new RegExp(MERGE_REGEX_PATT);
            let matches = mRegEx.exec(strInput);
            if (matches === null) {
                return -1;
            } else {
                return parseInt(matches[1]);
            }
        }
    
    }
    
    
    
    /*************
     * Get the file system path and filename of the current log file.
     * 
     * ioBroker creates a log file every midnight at 0:00 under '/opt/iobroker/log/'
     * Syntax of the log file is: iobroker.YYYY-MM-DD.log
     * This function returns the full path to the log file, considering the current date/time when this function is called.
     * @return {string}      Path and file name to log file.
     */
    function getCurrentFullFsLogPath() {
        let strLogPathFinal = LOG_FS_PATH;
        if (strLogPathFinal.slice(-1) !== '/') strLogPathFinal = strLogPathFinal + '/';
        let strFullLogPath = strLogPathFinal + DEBUG_CUSTOM_FILENAME;
        if (DEBUG_CUSTOM_FILENAME === '') strFullLogPath = strLogPathFinal + 'iobroker.' + getCurrentISODate() + '.log';
        return strFullLogPath;
    }
    
    /**
     * Clear array: if stateForTimeStamp is greater or equal than log date, we remove the entire log entry
     * @param {array} inputArray     Array of log entries
     * @param {string} stateForTimeStamp     state of which we need the time stamp
     * @return {array} cleaned log
     */
    function clearJsonByDate(inputArray, stateForTimeStamp) {
        let dtState = new Date(getState(stateForTimeStamp).ts);
        if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Time of last change of state [' + stateForTimeStamp + ']: ' + dtState);
        let newArray = [];
        for (let lpLog of inputArray) {
            let dtLog = new Date(lpLog.substr(0,23));
            if (dtLog.getTime() >= dtState.getTime()) {
                newArray.push(lpLog);            
            }
      }
      return newArray;
    }
    
    /**
     * Build an array of states we need to create.
     * @return {array} 2 array of states to be created. First is for force=false, second is for force=true.
     */
    function buildNeededStates() {
    
        let debugCleanIDs = '';  // für Debug-Ausgabe
        let statesArray = [];
    
        for(let i = 0; i < LOG_FILTER.length; i++) {
            if (LOG_FILTER[i].id !== '') {
                let lpIDClean = cleanseStatePath(LOG_FILTER[i].id);
                debugCleanIDs += ((debugCleanIDs === '') ? '' : '; ') + lpIDClean; // für Debug-Ausgabe
    
                statesArray.push({ id:'log' + lpIDClean + '.log', name:'Filtered Log - ' + lpIDClean, type:"string", role: "state", def: ""});
                statesArray.push({ id:'log' + lpIDClean + '.logJSON', name:'Filtered Log - ' + lpIDClean + ' - JSON', type:"string", role: "state", def: ""});
                statesArray.push({ id:'log' + lpIDClean + '.logJSONcount', name:'Filtered Log - Count of JSON ' + lpIDClean, role: "state", type:"number", def: 0});
                statesArray.push({ id:'log' + lpIDClean + '.clearJSON', name:'Clear JSON log ' + lpIDClean, role: "button", type:"boolean", def: false});
    
            }
        }
        if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'createLogStates(): Clean IDs: ' + debugCleanIDs);
    
    
        let finalStatesForceFalse = [];
        let finalStatesForceTrue = [];
        for (let s=0; s < statesArray.length; s++) {
            finalStatesForceFalse.push([FINAL_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,
            }]);
        }
    
        // Jetzt noch die VIS Add-on states (All.visView1, All.visView2, etc.)
        if(FINAL_NUM_OF_VIS_VIEWS > 0) {
            let dropdown = '';
            const ALL_FILTER_IDS = getAllFilterIds();
            for (let lpEntry of ALL_FILTER_IDS) {
                dropdown += '"' + lpEntry + '":"' + lpEntry + '",'; // fill JSON string
            }
            dropdown = dropdown.substr(0, dropdown.length-1); // remove last comma ","
            dropdown = '{' + dropdown + '}'; // finalize JSON string
            let dropdownJSON = JSON.parse(dropdown); // convert to JSON
            for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                let lpStateVisViews = FINAL_STATE_PATH + '.All.visView' + (i+1);
                finalStatesForceTrue.push ([lpStateVisViews + '.whichFilter', {'name':'Für VIS-Button/Auswahlmenü des anzuzeigenden Filter in JSON-Tabelle', 'type':'string', 'read':false, 'write':true, 'role':'value', 'states': dropdownJSON, 'def': ALL_FILTER_IDS[0]}]); // force true to rebuild state drop-down
                finalStatesForceTrue.push ([lpStateVisViews + '.outputJSON',  {'name': 'JSON-Ausgabe des in whichFilter gewählten Filters', 'type': 'string', 'read': true, 'write': false, 'role': 'state', 'def': '' }]); // force true to empty on re-start of script.
                finalStatesForceFalse.push ([lpStateVisViews + '.outputJSONcount',  {'name': 'Anzahl Zeilen in JSON-Ausgabe des in whichFilter gewählten Filters', 'type': 'number', 'read': true, 'write': false, 'role': 'state', 'def': 0 }]);
                finalStatesForceFalse.push([lpStateVisViews + '.clearJSON',   {'name':'Clear currently selected JSON Log', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def': false }]);
            }
        }
    
        // Nun noch Button zum leeren ALLER JSONs
        finalStatesForceFalse.push([FINAL_STATE_PATH + '.All.clearAllJSON',   {'name':'Clear ALL JSON logs', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def': false }]);
    
        // Zeit letztes Update
        finalStatesForceFalse.push([FINAL_STATE_PATH + '.All.lastTimeUpdated',   {'name':'Date/Time of last update', 'type':'number', 'read':true, 'write':false, 'role':'value.time'}]);
    
        // Done.
        return [finalStatesForceFalse, finalStatesForceTrue];
    }
    
    /**
     * Converts a timestamp to log date format, like 2019-10-15 16:38:00.260.
     * @param {object}  timeStamp   The date/time timestamp to convert.
     * @return {string} The resulting log date format as string.
     */
    function timestampToLogDate(timeStamp) {
    
        let date = new Date(timeStamp);
        // Need to convert to local time as this time provided from onLog() is UTC
        // https://stackoverflow.com/questions/6525538/convert-utc-date-time-to-local-date-time/18330682
        let localDate = new Date(date.getTime() - date.getTimezoneOffset()*60*1000);
    
        // Convert to ISO string, so like 2019-10-15T16:38:00.260Z
        let strResult = localDate.toISOString();
    
        // date.toISOString() adds T and Z, so we remove these letters, as the log do not show these.
        strResult = strResult.replace('T', ' ');  // remove T
        strResult = strResult.replace('Z', '');  // remove Z at the end
        return strResult;
    
    }
    
    /**
     * Remove PID in log message 
     * The js-controller version 2.0+ adds the PID number inside brackets to the beginning of the message. We remove it here.
     * @param {string} msg   The log message, like: 'javascript.0 (123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
     */
    function removePID(msg) {
    
        // First: Split source and message text. 
        // Input is like: 'javascript.0 (123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
        let regexp = /^(\S+)\s(.*)/;
        let matches_array = msg.match(regexp);
        let strFirst = matches_array[1];    // like 'javascript.0'
        let strRest = matches_array[2];     // like '(123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
        
        // Next, we remove the PID
        strRest = strRest.replace(/^\([0-9]{1,9}\)\s/, '');
    
        // Last, we put the two strings together again
        return strFirst + ' ' + strRest;
    
    }
    
    
    
    
    /*************************************************************************************************************************
     * onStop - Being executed once this ioBroker Script stops. 
     *************************************************************************************************************************/
    // This is to end the Tale. Not sure, if we indeed need it, but just in case...
    onStop(function myScriptStop () {
    
        // Unsubscribe log handler
        onLogUnregister(G_LogHandler);
        if (LOG_INFO) log('Unsubscribed to Log Handler.', 'info');
    
    }, 0);
    
    
    
    /*************************************************************************************************************************
     * General supporting functions
     *************************************************************************************************************************/
    
    /**
     * Remove Duplicates from Array
     * Source - https://stackoverflow.com/questions/23237704/nodejs-how-to-remove-duplicates-from-array
     * @param {array} inputArray       Array to process
     * @return {array}  Array without duplicates.
     */
    function arrayRemoveDublicates(inputArray) {
        let uniqueArray;
        uniqueArray = inputArray.filter(function(elem, pos) {
            return inputArray.indexOf(elem) == pos;
        });
        return uniqueArray;
    }
    
    /**
     * Clean Array: Removes 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
     * @param {array} inputArray       Array to process
     * @return {array}  Cleaned array
     */
    function cleanArray(inputArray) {
      var newArray = [];
      for (let i = 0; i < inputArray.length; i++) {
        if (inputArray[i]) {
          newArray.push(inputArray[i]);
        }
      }
      return newArray;
    }
    
    
    /**
     * Checks if Array or String is not undefined, null or empty.
     * 08-Sep-2019: added check for [ and ] to also catch arrays with empty strings.
     * @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 >"< or >[< or >]< is considered empty
     */
    function isLikeEmpty(inputVar) {
        if (typeof inputVar !== 'undefined' && inputVar !== null) {
            let strTemp = JSON.stringify(inputVar);
            strTemp = strTemp.replace(/\s+/g, ''); // remove all whitespaces
            strTemp = strTemp.replace(/\"+/g, "");  // remove all >"<
            strTemp = strTemp.replace(/\'+/g, "");  // remove all >'<
            strTemp = strTemp.replace(/\[+/g, "");  // remove all >[<
            strTemp = strTemp.replace(/\]+/g, "");  // remove all >]<
            if (strTemp !== '') {
                return false;
            } else {
                return true;
            }
        } else {
            return true;
        }
    }
    
    /**
     * Returns the current date in ISO format "YYYY-MM-DD".
     * @return  {string}    Date in ISO format
     */
    function getCurrentISODate() {
        let currDate = new Date();
        return currDate.getFullYear() + '-' + zeroPad((currDate.getMonth() + 1), 2) + '-' + 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 zeroPad(num, places) {
        let zero = places - num.toString().length + 1;
        return Array(+(zero > 0 && zero)).join("0") + num;        
    } 
    
    
    /**
     * Will just keep lower case letters, numbers, '-' and '_' and removes the rest
     * Also, capitalize first Letter.
     */
    function cleanseStatePath(stringInput) {
        let strProcess = stringInput;
        strProcess = strProcess.replace(/([^a-z0-9_\-]+)/gi, '');
        strProcess = strProcess.toLowerCase();
        strProcess = strProcess.charAt(0).toUpperCase() + strProcess.slice(1);
        return strProcess;
    
    }
    
    
    /**
     * Checks if the string provided contains either every or some terms.
     * Source: https://stackoverflow.com/questions/36283767/javascript-select-the-string-if-it-matches-multiple-words-in-array
     * @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 {boolean}       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
     */
    function strMatchesTerms(strInput, arrayTerms, type) {
        if(type === 'blacklist') {
            if (Array.isArray(arrayTerms)) {
                let arrayTermsNew = [];
                for (let lpTerm of arrayTerms) {
                    if (lpTerm.length >= 3) {
                        arrayTermsNew.push(lpTerm);
                    }
                }
                if(isLikeEmpty(arrayTermsNew) === false) {
                    let 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') {
                    let bResultEvery = arrayTerms.every(function(word) {
                        return strInput.indexOf(word) > -1;
                    });
                    return bResultEvery;
                } else if(type === 'some') {
                    let 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
            }
        }
    }
    
    /**
     * Checks if a a given state or part of state is existing.
     * This is a workaround, as getObject() or getState() throw warnings in the log.
     * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
     * See: https://forum.iobroker.net/topic/11354/
     * @param {string}    strStatePath     Input string of state, like 'javas-cript.0.switches.Osram.Bedroom'
     * @param {boolean}   [strict=true]    Optional: Default is true. If true, it will work strict, if false, it will add a wildcard * to the end of the string
     * @return {boolean}                   true if state exists, false if not
     */
    function isState(strStatePath, strict) {
    
        if(strict === undefined) strict = true;
    
        let mSelector;
        if (strict) {
            mSelector = $('state[id=' + strStatePath + '$]');
        } else {
            mSelector = $('state[id=' + strStatePath + ']');
        }
        if (mSelector.length > 0) {
            return true;
        } else {
            return false;
        }
    }
    
    
    /**
     * Removing Array element(s) by input value. 
     * @param {array}   arr             the input array
     * @param {string}  valRemove       the value to be removed
     * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
     * @return {array}  the array without the element(s)
     */
    function arrayRemoveElementsByValue(arr, valRemove, exact) {
    
        for ( let i = 0; i < arr.length; i++){ 
            if (exact) {
                if ( arr[i] === valRemove) {
                    arr.splice(i, 1);
                    i--; // required, see https://love2dev.com/blog/javascript-remove-from-array/
                }
            } else {
                if (arr[i].indexOf(valRemove) != -1) {
                    arr.splice(i, 1);
                    i--; // see above
                }
            }
        }
        return arr;
    }
    
    /**
     * Escapes a string for use in RegEx as (part of) pattern
     * Source: https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
     * @param {string} inputStr  The input string to be escaped
     * @return {string}  The escaped string
     */
    function escapeRegExp(inputStr) {
        return inputStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
    }
    
    
    /**
     * Get all elements of an array if found
     * @param {array}   arr             the input array
     * @param {string}  valFind         the value to find
     * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
     * @return {array}  an array with all hits or empty array if no hits.
     */
    function arrayGetElements(arr, valFind, exact) {
        let resultArr = [];
        for ( let i = 0; i < arr.length; i++){ 
            if (exact) {
                if ( arr[i] === valFind) {
                    resultArr.push(arr[i]);
                }
            } else {
                if (arr[i].indexOf(valFind) != -1) {
                    resultArr.push(arr[i]);
                }
            }
        }
        return resultArr;
    }
    
    /**
     * Replace Array element(s) by input value. 
     * @param {array}   arr             the input array
     * @param {string}  valReplace      the value to search for
     * @param {string}  newValue        the new value
     * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
     * @return {array}  the array with replaced the element(s)
     */
    function arrayReplaceElementsByValue(arr, valReplace, newValue, exact) {
    
        for ( let i = 0; i < arr.length; i++){ 
            if (exact) {
                if ( arr[i] === valReplace) {
                    arr[i] = newValue;
                }
            } else {
                if (arr[i].indexOf(valReplace) != -1) {
                    arr[i] = newValue;
                }
            }
        }
        return arr;
    }
    
    /**
     * Retrieve values from a CONFIG variable, example:
     * const CONF = [{car: 'bmw', color: 'black', hp: '250'}, {car: 'audi', color: 'blue', hp: '190'}]
     * To get the color of the Audi, use: getConfigValuePerKey(CONF, 'car', 'audi', 'color')
     * To find out which car has 190 hp, use: getConfigValuePerKey(CONF, 'hp', '190', 'car')
     * @param {object}  config     The configuration variable/constant
     * @param {string}  key1       Key to look for.
     * @param {string}  key1Value  The value the key should have
     * @param {string}  key2       The key which value we return
     * @returns {any}    Returns the element's value, or number -1 of nothing found.
     */
    function getConfigValuePerKey(config, key1, key1Value, key2) {
        for (let lpConfDevice of config) {
            if ( lpConfDevice[key1] === key1Value ) {
                if (lpConfDevice[key2] === undefined) {
                    return -1;
                } else {
                    return lpConfDevice[key2];
                }
            }
        }
        return -1;
    }
    
    
    /**
     * For a given state path, we extract the location '0_userdata.0' or 'javascript.0' or add '0_userdata.0', if missing.
     * @param {string}  path            Like: 'Computer.Control-PC', 'javascript.0.Computer.Control-PC', '0_userdata.0.Computer.Control-PC'
     * @param {boolean} returnFullPath  If true: full path like '0_userdata.0.Computer.Control-PC', if false: just location like '0_userdata.0' or 'javascript.0'
     * @return {string}                 Path
     */
    function validateStatePath(path, returnFullPath) {
        if (path.startsWith('.')) path = path.substr(1);    // Remove first dot
        if (path.endsWith('.'))   path = path.slice(0, -1); // Remove trailing dot
        if (path.length < 1) log('Provided state path is not valid / too short.', 'error')
        let match = path.match(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/);
        let location = (match == null) ? '0_userdata.0' : match[0].slice(0, -1); // default is '0_userdata.0'.
        if(returnFullPath) {
            return (path.indexOf(location) == 0) ? path : (location + '.' + path);
        } else {
            return location;
        }
    }
    
    
    /**
     * Create states under 0_userdata.0 or javascript.x
     * Current Version:     https://github.com/Mic-M/iobroker.createUserStates
     * Support:             https://forum.iobroker.net/topic/26839/
     * Autor:               Mic (ioBroker) | Mic-M (github)
     * Version:             1.1 (26 January 2020)
     * Example:             see https://github.com/Mic-M/iobroker.createUserStates#beispiel
     * -----------------------------------------------
     * PLEASE NOTE: Per https://github.com/ioBroker/ioBroker.javascript/issues/474, the used function setObject() 
     *              executes the callback PRIOR to completing the state creation. Therefore, we use a setTimeout and counter. 
     * -----------------------------------------------
     * @param {string} where          Where to create the state: '0_userdata.0' or 'javascript.x'.
     * @param {boolean} force         Force state creation (overwrite), if state is existing.
     * @param {array} statesToCreate  State(s) to create. single array or array of arrays
     * @param {object} [callback]     Optional: a callback function -- This provided function will be executed after all states are created.
     */
    function createUserStates(where, force, statesToCreate, callback = undefined) {
     
        const WARN = false; // Only for 0_userdata.0: Throws warning in log, if state is already existing and force=false. Default is false, so no warning in log, if state exists.
        const LOG_DEBUG = false; // To debug this function, set to true
        // Per issue #474 (https://github.com/ioBroker/ioBroker.javascript/issues/474), the used function setObject() executes the callback 
        // before the state is actual created. Therefore, we use a setTimeout and counter as a workaround.
        const DELAY = 50; // Delay in milliseconds (ms). Increase this to 100, if it is not working.
    
        // Validate "where"
        if (where.endsWith('.')) where = where.slice(0, -1); // Remove trailing dot
        if ( (where.match(/^((javascript\.([1-9][0-9]|[0-9]))$|0_userdata\.0$)/) == null) ) {
            log('This script does not support to create states under [' + where + ']', 'error');
            return;
        }
    
        // Prepare "statesToCreate" since we also allow a single state to create
        if(!Array.isArray(statesToCreate[0])) statesToCreate = [statesToCreate]; // wrap into array, if just one array and not inside an array
    
        // Add "where" to STATES_TO_CREATE
        for (let i = 0; i < statesToCreate.length; i++) {
            let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
            lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/,'') // remove any javascript.x. / 0_userdata.0. from beginning
            lpPath = where + '.' + lpPath; // add where to beginning of string
            statesToCreate[i][0] = lpPath;
        }
    
        if (where != '0_userdata.0') {
            // Create States under javascript.x
            let numStates = statesToCreate.length;
            statesToCreate.forEach(function(loopParam) {
                if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']');
                let loopInit = (loopParam[1]['def'] == undefined) ? null : loopParam[1]['def']; // mimic same behavior as createState if no init value is provided
                createState(loopParam[0], loopInit, force, loopParam[1], function() {
                    numStates--;
                    if (numStates === 0) {
                        if (LOG_DEBUG) log('[Debug] All states processed.');
                        if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                            if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                            return callback();
                        } else {
                            return;
                        }
                    }
                });
            });
        } else {
            // Create States under 0_userdata.0
            let numStates = statesToCreate.length;
            let counter = -1;
            statesToCreate.forEach(function(loopParam) {
                counter += 1;
                if (LOG_DEBUG) log ('[Debug] Currently processing following state: [' + loopParam[0] + ']');
                if( ($(loopParam[0]).length > 0) && (existsState(loopParam[0])) ) { // Workaround due to https://github.com/ioBroker/ioBroker.javascript/issues/478
                    // State is existing.
                    if (WARN && !force) log('State [' + loopParam[0] + '] is already existing and will no longer be created.', 'warn');
                    if (!WARN && LOG_DEBUG) log('[Debug] State [' + loopParam[0] + '] is already existing. Option force (=overwrite) is set to [' + force + '].');
                    if(!force) {
                        // State exists and shall not be overwritten since force=false
                        // So, we do not proceed.
                        numStates--;
                        if (numStates === 0) {
                            if (LOG_DEBUG) log('[Debug] All states successfully processed!');
                            if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                if (LOG_DEBUG) log('[Debug] An optional callback function was provided, which we are going to execute now.');
                                return callback();
                            }
                        } else {
                            // We need to go out and continue with next element in loop.
                            return; // https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach
                        }
                    } // if(!force)
                }
    
                // State is not existing or force = true, so we are continuing to create the state through setObject().
                let obj = {};
                obj.type = 'state';
                obj.native = {};
                obj.common = loopParam[1];
                setObject(loopParam[0], obj, function (err) {
                    if (err) {
                        log('Cannot write object for state [' + loopParam[0] + ']: ' + err);
                    } else {
                        if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']')
                        let init = null;
                        if(loopParam[1].def === undefined) {
                            if(loopParam[1].type === 'number') init = 0;
                            if(loopParam[1].type === 'boolean') init = false;
                            if(loopParam[1].type === 'string') init = '';
                        } else {
                            init = loopParam[1].def;
                        }
                        setTimeout(function() {
                            setState(loopParam[0], init, true, function() {
                                if (LOG_DEBUG) log('[Debug] setState durchgeführt: ' + loopParam[0]);
                                numStates--;
                                if (numStates === 0) {
                                    if (LOG_DEBUG) log('[Debug] All states processed.');
                                    if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                        if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                                        return callback();
                                    }
                                }
                            });
                        }, DELAY + (20 * counter) );
                    }
                });
            });
        }
    }
    

    ° Node.js: 20.17.0 NPM: 10.8.2
    ° Proxmox, Ubuntu 22.04.3 LTS
    ° Fixer ---> iob fix

    MicM 1 Reply Last reply
    0
    • NegaleinN Negalein

      @Mic sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

      Oh, das sollte nicht sein. Von welcher Version kommst du denn?

      Habs gerade nochmal neu von deiner GIT-Seite kopiert und meine Config eingefügt.
      Mic-M iobroker.logfile-script  Parses log file, applies filters, and sets states for visualization.png

      Selbes Ergebnis

      javascript.0	2020-02-11 20:51:01.763	error	(2798) at process._tickCallback (internal/process/next_tick.js:68:7)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at promise.then (/opt/iobroker/node_modules/standard-as-callback/built/index.js:19:49)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at tryCatcher (/opt/iobroker/node_modules/standard-as-callback/built/utils.js:11:23)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at client.get (/opt/iobroker/node_modules/iobroker.js-controller/lib/states/statesInRedis.js:572:33)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at adapter.getForeignState (/opt/iobroker/node_modules/iobroker.javascript/main.js:1055:17)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at createProblemObject (/opt/iobroker/node_modules/iobroker.javascript/main.js:1464:17)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at prepareScript (/opt/iobroker/node_modules/iobroker.javascript/main.js:1411:37)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at compile (/opt/iobroker/node_modules/iobroker.javascript/main.js:1188:28)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at Object.createScript (vm.js:277:10)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) at new Script (vm.js:83:7)
      javascript.0	2020-02-11 20:51:01.763	error	(2798) SyntaxError: Identifier 'LOG_PATT' has already been declared
      javascript.0	2020-02-11 20:51:01.762	error	(2798) ^
      javascript.0	2020-02-11 20:51:01.762	error	(2798) const LOG_PATT = '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
      javascript.0	2020-02-11 20:51:01.762	error	at script.js.System.IOBroker_Log:413
      javascript.0	2020-02-11 20:51:01.762	error	(2798) script.js.System.IOBroker_Log compile failed:
      

      /*************************************************************************************************************************
       * ---------------------------
       * Log Script für ioBroker zum Aufbereiten des Logs für Visualisierungen (vis), oder um
       * auf Log-Ereignisse zu reagieren.
       * ---------------------------
       *
       * Das Script nimmt jeden neuen Logeintrag des ioBrokers und wendet entsprechend gesetzte
       * Filter an, um den Eintrag dann in den entsprechenden Datenpunkten dieses Scripts abzulegen.
       
       * Es stehen auch JSON-Datenpunkte zur Verfügung, mit diesen kann im vis eine
       * Tabelle ausgegeben werden (z.B. über das Widget 'basic - Table' oder 'materialdesign - Table').
       *
       * Aktuelle Version: https://github.com/Mic-M/iobroker.logfile-script
       * Support:          https://forum.iobroker.net/topic/13971/vorlage-log-datei-aufbereiten-f%C3%BCr-vis-javascript
       * Autor:            Mic (ioBroker) | Mic-M (github)
       * -----------------------------------------------------------------------------------------------------------------------
       * VORAUSSETZUNGEN
       * 1.) Nur falls Datenpunkte unterhalb '0_userdata.0' abgelegt werden sollen:
       *     In der Instanz des JavaScript-Adapters die Option [Erlaube das Kommando "setObject"] aktivieren.
       *     Siehe auch: https://github.com/Mic-M/iobroker.createUserStates
       * 2.) Dieses Script benötigt die JavaScript-Adapter-Version 4.3.0 (2019-10-09) oder höher.
       *     Wer eine ältere Version einsetzt: Bitte Script-Version 2.0.2 verwenden.
       * =====================================================================================
       * -----------------------------------------------------------------------------------------------------------------------
       * Change Log:
       *  4.0.1 Mic   - Add "jsonDateFormat: 'dd.mm. hh:mm'," to FILTER_LOG, id 'all'.
       *  4.0 Mic     + To allow individual settings per each defined LOG_FILTER, the following global 
       *                settings were moved to LOG_FILTER:
       *                 * JSON_DATE_FORMAT                   -> jsonDateFormat
       *                 * JSON_LEN                           -> jsonLogLength
       *                 * JSON_NO_ENTRIES                    -> jsonMaxLines
       *                 * JSON_APPLY_CSS_LIMITED_TO_LEVEL    -> jsonCssToLevel
       *                 * L_SORT_ORDER_DESC                  -> sortDescending
       *              + Code improvements
       *              + Renamed LOG_NO_OF_ENTRIES to MAX_LOG_LINES
       *  ---------------------------------------------------------------------------------------------------- 
       *  3.4 Mic     + Support both '0_userdata.0' and 'javascript.x' for state creation
       *  3.3 Mic     - Fix state path
       *  3.2 Mic     + Create all states under 0_userdata.0, and no longer under javascript.<instance> (like javascript.0)
       *  3.1 Mic     + Change to stable as tests were successful
       *              + Add new option REMOVE_PID: The js-controller version 2.0+ adds the PID number inside brackets 
       *                to the beginning of the message. Setting REMOVE_PID = false will remove it.
       *  3.0Alpha Mic + Major Change: JavaScript adapter 4.3+ now provides onLog() function: 
       *                 https://github.com/ioBroker/ioBroker.javascript/blob/master/docs/en/javascript.md#onlog 
       *                 We are using this new function to streamline this log script tremendously and to remove node-tail.
       *  ---------------------------------------------------------------------------------------------------- 
       *  2.0.2 Mic   + Changed certain functions to async to get rid of setTimout() and for the sake of better error handling.
       *              + startTailingProcess(): ensure the tailing starts if the file is present (wait to be created)
       *  2.0.1a Mic  Removed constant MERGE_LOGLINES_ACTIVE
       *  2.0.0a Mic  Major improvements and fixes:
       *              + Change from instant state update to schedule (STATE_UPDATE_SCHEDULE). The instant update, so once
       *                new log entries coming in, caused several issues (setting and getting state values (getState() and 
       *                setState()) within <1ms simply does not work.
       *              - Fix issue with merging log lines
       *              + Moved global option MERGE_LOGLINES_ACTIVE to LOG_FILTER, for allowing turning on/off for each filter id.
       *              + Several other code improvements
       *              Note: For upgrading from previous version: replace script entirely, re-enter all your options, 
       *                    and delete all existing states prior to first activation of this script.
       *  ---------------------------------------------------------------------------------------------------- 
       *  1.5.1 Mic - Set option MERGE_LOGLINES_ACTIVE to 'false' as default, as users reported issues. See 
       *              https://forum.iobroker.net/post/288772 . Also option MERGE_LOGLINES_ACTIVE being marked as "experimental"
       *              in the comments. Requires further investigation.
       *  1.5  Mic - Fix issue with option MERGE_LOGLINES_ACTIVE
       *  1.4  Mic + New option MERGE_LOGLINES_TXT for an individual (e.g. localized) string other than 'entries'.
                   - Fix JSON span class closing
       *  1.3  Mic + New option MERGE_LOGLINES_ACTIVE: Merge Loglines with same log message to only one line and adds leading
       *             '[123 entries]' to log message.
       *  1.2  Mic - Fixed issue #6 (Button javascript.0.Log-Script.logXxxx.clearJSON not working reliably)
       *  1.1  Mic + 1. 1.0x script seems to work reliable per user feedback and my own test, so pushing into 1.1 stable.
       *           + New state '.logMostRecent': provides just the most recent log entry to work with "on /
       *             subscribe" on this state and trigger actions accordingly.
       *  1.02 alpha  Mic  - fix restarting at 0:00 (note: restarting is needed due to log file name change)
       *  1.01 alpha  Mic  - fix: creating new file system log file only if not yet existing
       *  1.00 alpha  Mic  + Entirely recoded to implement node-tail (https://github.com/lucagrulla/node-tail).
       *  ----------------------------------------------------------------------------------------------------
       *  0.8.1 Mic - Fix: L_SORT_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 <span class='log-info'>xxx</span>
       *            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 (Datenpunkte) in den Objekten angelegt werden.
      // Es wird die Anlage sowohl unterhalb '0_userdata.0' als auch 'javascript.x' unterstützt.
      const LOG_STATE_PATH = '0_userdata.0.Log-Script';
      
      
      // Pfad zum Log-Verzeichnis auf dem Server.
      // Standard-Pfad unter Linux: '/opt/iobroker/log/'. Wenn das bei dir auch so ist, dann einfach belassen.
      const LOG_FS_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 MAX_LOG_LINES = 100;
      
      // Der js-Controller Version 2.0 oder größer fügt Logs teils vorne die PID in Klammern hinzu, 
      // also z.B. "(12234) Terminated (15): Without reason". 
      // Mit dieser Option lassen sich die PIDs aus den Logzeilen entfernen.
      const REMOVE_PID = true;
      
      /**
       * Schwarze Liste (Black list)
       * Falls einer dieser Satzteile/Begriffe in einer Logzeile enthalten ist, dann wird der Log-Eintrag
       * komplett ignoriert, egal was weiter unten eingestellt wird.
       * Dies dient dazu, um penetrante Logeinträge gar nicht erst zu berücksichtigen.
       * Bitte beachten: 
       * 1. Mindestens 3 Zeichen erforderlich, sonst wird es nicht berücksichtigt (würde auch wenig Sinn ergeben).
       * 2. Bestehende Datenpunkt-Inhalte dieses Scripts bei Anpassung dieser Option werden nicht nachträglich neu gefiltert,
       *    sondern nur alle neu hinzugefügten Log-Einträge ab Speichern des Scripts werden berücksichtigt.
       */
      const BLACKLIST_GLOBAL = [
          '<==Disconnect system.user.admin from ::ffff:', // web.0 Adapter
          'system.adapter.ical.0 terminated with code 0 (OK)', 
          'bring.0 Cannot get translations: RequestError',
          ' reconnected. Old secret ', // Sonoff
          'Popup-News readed...', // info.0 Adapter
          '[warn] Projects disabled : set editorTheme.projects.enabled=true to enable', // see https://forum.iobroker.net/topic/12260/
      	'',
      	'',
      ];
      
      /**
       * Zusatz-Einstellung für Option "merge" für LOG_FILTER (unter "Konfiguration: Datenpunkte und Filter"):
       * In MERGE_LOGLINES_TXT kann hier ein anderes Wort eingetragen werden, z.B. 'entries' oder 'Zeilen', damit [123 entries] 
       * oder [123 Zeilen] vorangestellt wird anstatt [123 Einträge].
       * HINWEIS: Falls MERGE_LOGLINES_TXT geändert wird: bitte alle Datenpunkte des Scripts löschen und dann Script neu starten.
       */
      const MERGE_LOGLINES_TXT = 'Einträge';
      
      /**
       *  Für JSON-Tabelle: Füge CSS-Klasse hinzu je nach Log-Level (debug, silly, info, warn und error), um Tabellen-Text zu formatieren.
       *  Beispiel für Log-Level "debug": ersetzt "xxx" durch "<span class='log-debug'>xxx</span>""
       *  Es wird jeweils "log-" vorangestellt, also: debug -> log-debug, silly -> log-silly, info -> log-info, etc.
       *  Etwa für 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; }
       *  Tipp: In LOG_FILTER kann dann bei den einzelnen Filtern mittels "jsonCssToLevel" eingestellt werden, dass das CSS
       *        nur  für die Spalte "level" (also debug, error, info) und nicht auf alle Spalten angewendet wird.
       */
      const JSON_APPLY_CSS = true;
      
      
      /*******************************************************************************
       * Konfiguration: Datenpunkte und Filter
       ******************************************************************************
       * Dies ist das Herzstück dieses Scripts: hier werden die Datenpunkte konfiguriert, die erstellt werden sollen. 
       * Hierbei kannst du entsprechend Filter setzen, also z.B. Wörter/Begriffe, die in Logeinträgen enthalten sein
       * müssen, damit sie in den jeweiligen Datenpunkten aufgenommen werden.
       * --------------------------------------------------------------------------------------------------------------------------
       * id:              Ein Begriff ohne Leerzeichen, z.B. "error", "sonoff", homematic, etc. Die ID wird dann Teil der
       *                  Datenpunkte, z.B. "javascript.0.Log-Script.logHomematic.log" mit automatisch vorangestelltem "log".
       * --------------------------------------------------------------------------------------------------------------------------
       * 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:       Schwarze Liste: 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.
       *                  HINWEIS: BLACKLIST_GLOBAL wird vorher schon angewendet, hier kannst du einfach nur noch eine individuelle 
       *                  Blackliste pro id definieren.
       * --------------------------------------------------------------------------------------------------------------------------
       * 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.
       * --------------------------------------------------------------------------------------------------------------------------
       * merge:           Log-Einträge mit gleichem Text zusammenfassen. Beispiel:
       *                      -----------------------------------------------------------------------------------
       *                      2019-08-17 20:00:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
       *                      2019-08-17 20:15:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
       *                      2019-08-17 20:30:00.335 - info: javascript.0 script.js.Wetter: Wetterdaten abrufen.
       *                      -----------------------------------------------------------------------------------
       *                  Daraus wird dann nur noch eine Logzeile mit letztem Datum/Uhrzeit und hinzufügen von "[3 Einträge]":
       *                      -----------------------------------------------------------------------------------
       *                      2019-08-17 20:30:00.335 - info: javascript.0 [3 Einträge] script.js.Wetter: Wetterdaten abrufen.
       *                      -----------------------------------------------------------------------------------
       *                  Zum aktivieren: true eintragen, zum deaktivieren: false eintragen.
       * --------------------------------------------------------------------------------------------------------------------------
       * sortDescending:  Wenn true: Sortiert die Logeinträge absteigend, also neuester oben. 
       *                  Wenn false: Sortiert die Logeinträge aufsteigend, also ältester oben. 
       * --------------------------------------------------------------------------------------------------------------------------
       * jsonColumns:     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 Spalten eintragen, sondern nur 'date','level','source','msg'.
       * --------------------------------------------------------------------------------------------------------------------------
       * jsonDateFormat:  Datumsformat für JSON Log. Z.B. volles 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 4stellig (z.B. 2019), YY = Jahr 2stellig (z.B. 19), MM = Monat, DD = Tag, HH = Stunde, MM = Minute, 
       *                  SS = Sekunde. Groß- oder Kleinschreibung ist egal, d.h. YYYY ist das gleiche wie yy.
       *                  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, 'DD.MM. HH:MM' für '25.07. 19:37'
       * --------------------------------------------------------------------------------------------------------------------------
       * jsonLogLength:   Maximale Anzahl Zeichen jeder einzelnen Log-Meldung im JSON-Log. Alles was länger ist, wird abgeschnitten.
       * --------------------------------------------------------------------------------------------------------------------------
       * jsonMaxLines:    Maximale Anzahl der letzten Logeinträge im JSON-Log. Alle älteren werden entfernt.
       *                  Falls in MAX_LOG_LINES z.B. "100" gesetzt wird, wird hier bei 100 der Cut gemacht, selbst wenn in 
       *                  jsonMaxLines etwa 250 eingetragen wird. D.h. im Bedarf zuerst MAX_LOG_LINES anpassen/erhöhen.
       * --------------------------------------------------------------------------------------------------------------------------
       * jsonCssToLevel:  Wenn true, dann wird JSON_APPLY_CSS nur für die Spalte "level" (also debug, error, info) angewendet, 
       *                  aber nicht für die restlichen Spalten wie Datum, Log-Eintrag, etc.
                          Falls alle Spalten das CSS bekommen sollen: auf false setzen.
       * --------------------------------------------------------------------------------------------------------------------------
       *
       * WEITERER HINWEIS: 
       * Bestehende Datenpunkt-Inhalte dieses Scripts bei Anpassung dieser Option werden nicht nachträglich neu 
       * gefiltert, sondern nur alle neu hinzugefügten Log-Einträge ab Speichern des Scripts werden berücksichtigt.
       * --------------------------------------------------------------------------------------------------------------------------
       */
      const LOG_FILTER = [
      
        // 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 jsonColumns 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'],
          merge:          true,
          sortDescending: true,
          jsonDateFormat: 'dd.mm. hh:mm',       
          jsonColumns:    ['source','date','msg'],
          jsonLogLength:  100,
          jsonMaxLines:   10,
          jsonCssToLevel: true,
        }, 
      */
      
      /*
        {
          id:             'all',    // Beispiel "all": hier kommen alle Logeinträge rein, keine Filterung
          filter_all:     ['', ''], // wird ignoriert, wenn leer
          filter_any:     ['', ''], // wird ignoriert, wenn leer
          blacklist:      ['', ''], // wird ignoriert, wenn leer
          clean:          ['', '', ''], // wird ignoriert, wenn leer
          merge:          true,
          sortDescending: true,
          jsonDateFormat: 'dd.mm. hh:mm',       
          jsonColumns:    ['date','level','source','msg'],  // Spaltenreihenfolge für JSON (Tabelle in vis)
          jsonLogLength:  100,
          jsonMaxLines:   10,
          jsonCssToLevel: true,
        },
      */
      
      /*
        {
          id:             'info',
          filter_all:     [' - info: '], // nur Logeinträge mit Level 'info'
          filter_any:     ['', ''],
          blacklist:      ['', ''],
          clean:          ['', '', ''],
          merge:          true,
          sortDescending: true,
          jsonDateFormat: 'dd.mm. hh:mm',
          jsonColumns:    ['date','level','source','msg'],
          jsonLogLength:  100,
          jsonMaxLines:   60,
          jsonCssToLevel: true,
        },
      */
      
        {
          id:             'debug',
          filter_all:     [' - debug: ', ''],  // nur Logeinträge mit Level 'debug'
          filter_any:     [''],
          blacklist:      ['', '', ''],
          clean:          ['', '', ''],
          merge:          true,
          sortDescending: true,
          jsonColumns:    ['date','level','source','msg'],
          jsonDateFormat: 'dd.mm. hh:mm',
          jsonLogLength:  100,
          jsonMaxLines:   60,
          jsonCssToLevel: true,
        },
        {
          id:             'warnanderror',
          filter_all:     ['', ''],
          filter_any:     [' - error: ', ' - warn: '],  // nur Logeinträge mit Levels 'warn' und 'error'
          blacklist:      ['', 'no playback content', 'Ignore! Actual secret is '],
          clean:          ['', '', ''],
          merge:          true,
          sortDescending: true,
          jsonDateFormat: 'dd.mm. hh:mm',
          jsonColumns:    ['date','level','source','msg'],
          jsonLogLength:  100,
          jsonMaxLines:   60,
          jsonCssToLevel: true,
        },
      
      /*
        {
          // Beispiel, um einen bestimmten Adapter zu überwachen.
          // Hier werden alle Fehler und Warnungen des Homematic-Adapters hm-rpc.0 gelistet.
          id:             'homematic',
          filter_all:     ['hm-rpc.0', ''],  // hm-rpc.0 muss enthalten sein.
          filter_any:     [' - error: ', ' - warn: '],  // entweder error oder warn
          blacklist:      ['', '', ''],
          clean:          ['', '', ''],
          merge:          true,
          sortDescending: true,
          jsonDateFormat: 'dd.mm. hh:mm',
          jsonColumns:    ['date','level','source','msg'],
          jsonLogLength:  100,
          jsonMaxLines:   60,
          jsonCssToLevel: true,
        },
      */
      
      ];
      
      
      /*******************************************************************************
       * 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 dieses Scripts im Log ausgegeben werden dürfen, bei false bleiben die Infos komplett weg.
      const LOG_INFO = true;
      
      
      /*******************************************************************************
       * Experten-Konfiguration
       ******************************************************************************/
      
      // Wie oft Datenpunkte aktualisieren?
      // Neu reinkommende Logeinträge werden erst mal gesammelt (in Variable G_NewLogLinesArrayToProcess). Diese werden dann 
      // regelmäßig in den Datenpunkten geschrieben. Sinnvoll ist hier nicht kürzer als 2-3 Sekunden, und nicht länger als 
      // ein paar Minuten. Zu kurzes Intervall: Script kommt nicht mehr nach. Zu lange: falls viele Logeinträge reinkommen, 
      // kann sich vieles "aufstauen" zur Abarbeitung. Benutze den "Cron"-Button oben rechts für komfortable Einstellung.
      const STATE_UPDATE_SCHEDULE = '*/1 * * * *'; // jede Minute
      
      
      // Leer lassen! Nur setzen, falls ein eigener Filename für das Logfile verwendet wird für Debug.
      const DEBUG_CUSTOM_FILENAME = '';
      
      // 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 z.B.: '2018-07-22 12:45:02.769  - info: javascript.0 Stop script script.js.ScriptAbc'
      // Da als String, wurden alle Backslashes "\" mit einem zweiten Backslash escaped.
      const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
      
      // Debug: Ignore. Wenn dieses String in der Logzeile enthalten ist, dann ignorieren wir es.
      // Dient dazu, dass wir während des Scripts ins Log schreiben können, ohne dass das dieses Script berücksichtigt.
      const DEBUG_IGNORE_STR = '[LOGSCRIPT_IGNORE]'; // Muss ein  individuelles String sein. Sonst gibt es ggf. eine Endlos-Schleife.
      
      
      // Debug: Prüfen, ob jede Logzeile erfasst wird, in dem wir diese direkt danach noch mal ins Log schreiben.
      // Bitte nur auf Anweisung vom Entwickler einschalten. Sonst wird jeder Logeintrag noch einmal wiederholt, 
      // mit führendem DEBUG_EXTENDED_STR am Anfang und max. DEBUG_EXTENDED_NO_OF_CHARS an Anzahl Zeichen.
      const DEBUG_EXTENDED = false;
      const DEBUG_EXTENDED_STR = '[LOGSCRIPT_DEBUG_EXTENDED]'; // Muss ein  individuelles String sein. Sonst gibt es ggf. eine Endlos-Schleife.
      const DEBUG_EXTENDED_NO_OF_CHARS = 120;
      
      /*************************************************************************************************************************
       * Ab hier nichts mehr ändern / Stop editing here!
       *************************************************************************************************************************/
      
      
      /*************************************************************************************************************************
       * Global variables and constants
       *************************************************************************************************************************/
      
      // Final state path
      const FINAL_STATE_LOCATION = validateStatePath(LOG_STATE_PATH, false);
      const FINAL_STATE_PATH = validateStatePath(LOG_STATE_PATH, true);
      
      // 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 z.B.: '2018-07-22 12:45:02.769  - info: javascript.0 Stop script script.js.ScriptAbc'
      // Da als String, wurden alle Backslashes "\" mit einem zweiten Backslash escaped.
      const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
      
      // Final number of VIS states. We do not allow more than 9.
      function finalVisStatesQty(qty) {
          let result = 0;
          if( (!isLikeEmpty(qty)) && (qty > 0) )  {
              if(qty > 9) {
                  result = 9;
              } else {
                  result = qty;
              }
          }
          return result; 
      };
      const FINAL_NUM_OF_VIS_VIEWS = finalVisStatesQty(NUMBER_OF_VIS_VIEWS);
      
      // Merge loglines: define pattern (and escape the merge text)
      // We added an additional backslash '\' to each backslash as these need to be escaped.
      const MERGE_REGEX_PATT = '^\\[(\\d+)\\s' + escapeRegExp(MERGE_LOGLINES_TXT) + '\\]\\s(.*)';
      
      // Log Handler variable for ioBroker function onLog()
      let G_LogHandler;  // being set later
      
      // Schedule for logfile update
      let G_Schedule_StateUpdate; // being set later
      
      // We add here all the new log lines to be processed regularly (per STATE_UPDATE_SCHEDULE);
      let G_NewLogLinesArrayToProcess = [];
      
      /*************************************************************************************************************************
       * init - This is executed on every script (re)start.
       *************************************************************************************************************************/
      init();
      function init() {
      
          // Unsubscribe log handler
          onLogUnregister(G_LogHandler);
          
          // Create all script states
          const NEEDED_STATES = buildNeededStates();
          createUserStates(FINAL_STATE_LOCATION, false, NEEDED_STATES[0], function() { // force = false
              createUserStates(FINAL_STATE_LOCATION, true, NEEDED_STATES[1], function() { // force = true
                  
                  // -- All states created, so we continue by using callback
      
                  // Subscribe on changes: Pressed button "clearJSON"
                  subscribeClearJson();
      
                  // Subscribe to log handler
                  G_LogHandler = onLog('*', data => { // please ignore the red squiggly underline under '*', see Github issue: https://github.com/ioBroker/ioBroker.javascript/issues/457
                      processNewLogLine(data);
                  });
      
                  // Schedule writing changes into states
                  clearSchedule(G_Schedule_StateUpdate);
                  G_Schedule_StateUpdate = schedule(STATE_UPDATE_SCHEDULE, processNewLogsPerSchedule);
      
                  // Subscribe to clear all states
                  stateSubscribeClearAllJSON()
      
                  // Subscribe to states for the VIS views
                  if(FINAL_NUM_OF_VIS_VIEWS > 0) {
                      stateSubscribeVisWhichFilter(); // which Filter
                      stateSubscribeVisClearJson(); // clear JSON
                  }
      
                  // Message
                  if (LOG_INFO) log('Start monitoring of the ioBroker log...', 'info');
      
              });
          });
      
      }
      
      function processNewLogLine(data) {
          
          // Convert to Log Line
          // TODO: This is a quick implementation of new function onLog().
          //       We need to entirely rewrite script later to fully use the data object.
          //       However, at this time, we convert it to a standard log line being expected.
      
          // First, remove PID if desired
          let msg = data.message;
          if (REMOVE_PID) msg = removePID(msg);
      
          // Now convert to log line
          let newLogEntry = timestampToLogDate(data.ts) + '  - ' + data.severity + ': ' + msg;
      
          // Check if we have DEBUG_IGNORE_STR in the new log line
          if(! newLogEntry.includes(DEBUG_IGNORE_STR)) {
      
              if (newLogEntry.length > 45) {  // a log line with less than 45 chars is not a valid log line.
      
                  // Cleanse and apply blacklist
                  newLogEntry = cleanseLogLine(newLogEntry);
      
                  // Push result into logArrayFinal
                  G_NewLogLinesArrayToProcess.push(newLogEntry);
      
                  // some debugging
                  if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + '===============================================================');
                  if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'New Log Entry, Len (' + newLogEntry.length + '), content: [' + newLogEntry + ']');
      
                  // This is for debugging purposes, and it will log every new log entry once again. See DEBUG_EXTENDED option above.
                  if (DEBUG_EXTENDED) {
                      if (! newLogEntry.includes(DEBUG_EXTENDED_STR)) { // makes sure no endless loop here.
                          log(DEBUG_EXTENDED_STR + newLogEntry.substring(0, DEBUG_EXTENDED_NO_OF_CHARS));
                              
                      }
                  }
              }
          }
      
      }
      
      /**
       * Called per schedule STATE_UPDATE_SCHEDULE.
       * It processes G_NewLogLinesArrayToProcess
       */
      function processNewLogsPerSchedule() {
          if (! isLikeEmpty (G_NewLogLinesArrayToProcess) ) {
      
              // We use array spreads '...' to copy array. If not, array is changed by reference and not value.
              // That means, if we change the target array, it will also change the source array.
              // See https://stackoverflow.com/questions/7486085/copy-array-by-value
              let logArrayToProcess = [...G_NewLogLinesArrayToProcess];
              G_NewLogLinesArrayToProcess.length = 0; // emptying array. https://stackoverflow.com/questions/4804235/difference-between-array-length-0-and-array
      
              /**
               * Apply the filters as set in LOG_FILTER and split up log levels into elements of an array
               * logArrayToProcessFiltered will look as follows:
               *   logArrayToProcessFiltered = [
               *     ['info':'15.08.2019 09:27:55.476 info adapt.0 some log', 'error':''],
               *     ['info':'15.08.2019 09:33:58.522 info adapt.0 some more log', 'error':''],
               *     ['info':'', 'error':'15.08.2019 09:37:55.807 error adapt.0 some error log']
               *   ]
               */
              let logArrayToProcessFiltered = [];
              for (let lpEntry of logArrayToProcess) {
                  let logEntryFilteredArray = applyFilter(lpEntry);
                  logArrayToProcessFiltered.push(logEntryFilteredArray);
              }
      
              // Further process and finally set states with our results.
              processLogArrayAndSetStates(logArrayToProcessFiltered);
      
          }
      
          // Finally, set updatestate with current date/time
          setState(FINAL_STATE_PATH + '.All.lastTimeUpdated', Date.now());    
      
      }
      
      
      
      /*************************************************************************************************************************
       * Filtering
       *************************************************************************************************************************/
      
      /**
       * This function applies the filters as set in LOG_FILTER.
       * Also, it splits up the log levels into elements of an array we return by this function.
       * @param {string} strLogEntry
       * @return {array}  split up log levels as elements within this array, like: ['info':'logtext', 'error':'logtext'] etc.
       */
      function applyFilter(strLogEntry) {
          // We add one element per each filter to the Array ('all', 'error', etc.)
          let logArrayProcessed = [];
          for (let j = 0; j < LOG_FILTER.length; j++) {
              logArrayProcessed[LOG_FILTER[j].id] = '';
          }
      
          // We apply regex here. This will also eliminate all log lines without proper info
          // like date/time, log level, and entry.
          let arrSplitLogLine = logLineSplit(strLogEntry);
          if (arrSplitLogLine !== false) {
      
              if (isLikeEmpty(LOG_FILTER) === false) {
                          
                  // Now let's iterate 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 (let k = 0; k < LOG_FILTER.length; k++) {
                      if ( (strMatchesTerms(strLogEntry, LOG_FILTER[k].filter_all, 'every') === true)
                      && (strMatchesTerms(strLogEntry, LOG_FILTER[k].filter_any, 'some') === true)
                      && (strMatchesTerms(strLogEntry, LOG_FILTER[k].blacklist, 'blacklist') === false) ) {
                          logArrayProcessed[LOG_FILTER[k].id] = logArrayProcessed[LOG_FILTER[k].id] + strLogEntry + "\n";
                      }
                      
                      // Now we remove terms if desired
                      if (isLikeEmpty(LOG_FILTER[k].clean) === false) {
                          for (let lpTerm of LOG_FILTER[k].clean) {
                              if (lpTerm !== '') {
                                  logArrayProcessed[LOG_FILTER[k].id] = logArrayProcessed[LOG_FILTER[k].id].replace(lpTerm, '');
                              }
                          }
                      }
                  }
              }
          }
          return logArrayProcessed;
      }
      
      /*************************************************************************************************************************
       * Further processing
       *************************************************************************************************************************/
      
      /**
       * Further processes the log array and set states accordingly.
       * 
       * @param  arrayLogInput             The Array of the log input.
       *                                   Array is like: 
       *                                   [
       *                                      ['info':'15.08.2019 09:27:55.476 info adapt.0 some log', 'error':''],
       *                                      ['info':'15.08.2019 09:33:58.522 info adapt.0 some more log', 'error':''],
       *                                      ['info':'', 'error':'15.08.2019 09:37:55.807 error adapt.0 some error log'],
       *                                   ]
       **/
      function processLogArrayAndSetStates(arrayLogInput) {
      
          /*****************
           * [1] Build array from LOG_FILTER. Looks like: arrayFilterIds = ['info', 'error', 'warn'].
           * Also, build result array to keep our results. Lools like resultArr = [info: '', error: '', warn: '']
           *****************/
          let arrayFilterIds = [];
          let resultArr = [];
          for (let i = 0; i < LOG_FILTER.length; i++) {
              arrayFilterIds.push(LOG_FILTER[i].id); // each LOG_FILTER id into array
              resultArr[LOG_FILTER[i].id] = '';
          }
          /*****************
           * [2] Process element by element, so ['info':'log test', 'error':'log test'] of given array.
           * We fill the result array accordingly.
           *****************/
          for (let lpElement of arrayLogInput) {
      
              // Loop thru our new array arrayFilterIds and fill result array
              for (let k = 0; k < arrayFilterIds.length; k++) {
      
                  // some variables
                  let lpFilterId = arrayFilterIds[k]; // Filter ID from LOG_FILTER, like 'error', 'info', 'custom', etc.
                  let lpNewLogLine = lpElement[lpFilterId]; // Current log line of provided array element of 'error', 'info', 'custom' etc.
      
                  if (isLikeEmpty(lpNewLogLine)) {
                      // No log content for the given filter id.
                      if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Filter  [' + lpFilterId + ']: No match.');
                  } else {
      
                      if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Filter [' + lpFilterId + ']: Match! New Log Line length: (' + lpNewLogLine.length + ')');
      
                      // Append new log line to result array
                      if (isLikeEmpty(resultArr[lpFilterId])) {
                          resultArr[lpFilterId] = lpNewLogLine; 
                      } else {
                          resultArr[lpFilterId] = lpNewLogLine + resultArr[lpFilterId]; // "\n" not needed, always added above
                      }
                  }
              }
          }
      
          /*****************
           * [3] We merge with the current state.
           *****************/
          for (let k = 0; k < arrayFilterIds.length; k++) {
              let lpFilterId = arrayFilterIds[k]; // Filter ID from LOG_FILTER, like 'error', 'info', 'custom', etc.
              let lpStatePath1stPart = FINAL_STATE_PATH + '.log' + cleanseStatePath(lpFilterId); // Get Path to state
              let lpNewFinalLog = resultArr[lpFilterId];
      
              if (! isLikeEmpty(lpNewFinalLog) )  {
      
                  // Get state value
      			let strCurrentStateLog = getState(lpStatePath1stPart + '.log').val; // Get state contents of loop item
                  
                  // Add state log lines to our final log
                  if (! isLikeEmpty(strCurrentStateLog)) {
                      lpNewFinalLog = lpNewFinalLog + strCurrentStateLog; // "\n" not needed, always added above
                  }            
      
                  // Convert to array for easier handling
                  let lpNewFinalLogArray = lpNewFinalLog.split(/\r?\n/);
      
                  // Remove duplicates
                  lpNewFinalLogArray = arrayRemoveDublicates(lpNewFinalLogArray);
      
                  // Remove empty values
                  lpNewFinalLogArray = cleanArray(lpNewFinalLogArray);
      
                  // Sort array descending
                  lpNewFinalLogArray = sortLogArrayByDate(lpNewFinalLogArray, 'desc');
      
                  // Merge Loglines if multiple values and add leading '[123 entries]' to log message
                  let doMerge = getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'merge');
                  if (doMerge || doMerge === 'true') {    // also check for string 'true' in case user used string
                      lpNewFinalLogArray = mergeLogLines(lpNewFinalLogArray);
                  }
      
                  // We need a separate array for JSON
                  let lpNewFinalLogArrayJSON = lpNewFinalLogArray;
      
                  // Let's remove elements if time of when button '.clearJSON' was pressed is greater than log date.
                  lpNewFinalLogArrayJSON = clearJsonByDate(lpNewFinalLogArrayJSON, lpStatePath1stPart + '.clearJSON');              
      
                  // Just keep the first x elements of the log. JSON log length is being set individually.
                  lpNewFinalLogArray = lpNewFinalLogArray.slice(0, MAX_LOG_LINES);
                  lpNewFinalLogArrayJSON = lpNewFinalLogArrayJSON.slice(0, getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'jsonMaxLines'));
      
                  // Get just the most recent log entry into string
                  let lpMostRecent = lpNewFinalLogArray[0];
      
                  // Sort ascending if desired
                  if (!getConfigValuePerKey(LOG_FILTER, 'id', lpFilterId, 'sortDescending')) {
                      lpNewFinalLogArray = lpNewFinalLogArray.reverse();
                      lpNewFinalLogArrayJSON = lpNewFinalLogArrayJSON.reverse();
                  }
      
                  // ** Finally set the states
      
                  ///////////////////////////////
                  // -1- Full Log, String, separated by "\n"
                  ///////////////////////////////
                  let strResult = lpNewFinalLogArray.join("\n");
                  if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'New length to be set into state: (' + strResult.length + '), state: [' + lpStatePath1stPart + '.log' + ']');
      
                  setState(lpStatePath1stPart + '.log', strResult);
      
                  ///////////////////////////////
                  // -2- JSON, with elements date and msg
                  ///////////////////////////////
                  // Let's put together the JSON
                  let jsonArr = [];
                  for (let j = 0; j < lpNewFinalLogArrayJSON.length; j++) {
                      // Get 4 elements in array: datetime, level, source, message
                      let arrSplitLogLine = logLineSplit(lpNewFinalLogArrayJSON[j]);
                      if (arrSplitLogLine !== false) {
                          let strLogMsg = arrSplitLogLine.message;
                          // Reduce the length for each log message per "jsonLogLength"
                          strLogMsg = strLogMsg.substr(0, LOG_FILTER[k].jsonLogLength);
                          // ++++++
                          // Build the final Array
                          // ++++++
                          // We need this section to generate the JSON with the columns (which ones, and order) as specified in LOG_FILTER
                          let objectJSONentry = {}; // object (https://stackoverflow.com/a/13488998)
                          if (isLikeEmpty(LOG_FILTER[k].jsonColumns)) log('Columns not specified in "jsonColumns".', 'warn');
                          // Prepare CSS
                          let strCSS1, strCSS2;
                          let strCSS1_level, strCSS2_level;
                          if (JSON_APPLY_CSS) {
                              strCSS1 = "<span class='log-" + arrSplitLogLine.level + "'>";
                              strCSS2 = '</span>';
                              strCSS1_level = strCSS1;
                              strCSS2_level = strCSS2;
                              if (LOG_FILTER[k].jsonCssToLevel) {
                                  strCSS1 = '';
                                  strCSS2 = '';
                              }
                          }
      
                          for (let lpCol of LOG_FILTER[k].jsonColumns) {
                              switch (lpCol) {
                                  case 'date' :
                                      objectJSONentry.date = strCSS1 + formatLogDateStr(arrSplitLogLine.datetime, LOG_FILTER[k].jsonDateFormat) + 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 (! isLikeEmpty(lpNewFinalLogArrayJSON)) {
                      setState(lpStatePath1stPart + '.logJSON', JSON.stringify(jsonArr));
                      setState(lpStatePath1stPart + '.logJSONcount', lpNewFinalLogArrayJSON.length);
                  } 
              }
          }
      
          /*****************
           * [4] Finally done. Now we set states for the VIS views.
           *****************/
          if(FINAL_NUM_OF_VIS_VIEWS > 0) {
              
              setTimeout(function() { // Apply timeout, to ensure all states are set.
      
                  for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                      let lpStateViewSelFilter = FINAL_STATE_PATH + '.All.visView' + (i+1); // like: 0_userdata.0.Log-Script.All.visView1.whichFilter
                      let currentVisFilter = getState(lpStateViewSelFilter + '.whichFilter').val;
                      if(! isLikeEmpty(currentVisFilter)) {
                          let jsonStateToGet = LOG_STATE_PATH + '.log' + currentVisFilter + '.logJSON';
                          if( isState(jsonStateToGet) ) {
                              setState(lpStateViewSelFilter + '.outputJSON', getState(jsonStateToGet).val); // Finally: set state
                              setState(lpStateViewSelFilter + '.outputJSONcount', getState(jsonStateToGet + 'count').val); // Finally: set state
                          } else {
                              log('Log-Script-Fehler: Gewählter Filter ' +  currentVisFilter + ', aber Datenpunkt [' + jsonStateToGet + '] ist nicht vorhanden.', 'error');
                          }
                      }
                  }
              }, 2000);
          }
      }
      
      /*************************************************************************************************************************
       * Script specific supporting functions
       *************************************************************************************************************************/
      
      /**
       * Subscribe to all Log-Script.logXXXX.clearJSON
       * This will allow to clear JSON log if button is pressed.
       */
      function subscribeClearJson() {
          // Set current date to state if button is pressed
          let logSubscribe = '';
          for (let i = 0; i < LOG_FILTER.length; i++) {
              let lpFilterId = cleanseStatePath(LOG_FILTER[i].id);
              let lpStateFirstPart = FINAL_STATE_PATH + '.log' + lpFilterId;
              logSubscribe += ( (logSubscribe === '') ? '' : ', ') + lpFilterId;
              on({id: lpStateFirstPart + '.clearJSON', change: 'any', val: true}, function(obj) {
                  let stateBtnPth = obj.id // e.g. [javascript.0.Log-Script.logInfo.clearJSON]
                  let firstPart = stateBtnPth.substring(0, stateBtnPth.length-10); // get first part of obj.id, like "javascript.0.Log-Script.logInfo"
                  let filterID = firstPart.slice(firstPart.lastIndexOf('.') + 1); // gets the filter id, like "logInfo"
                  if (SCRIPT_DEBUG) log(DEBUG_IGNORE_STR + 'Clear JSON states for [' + filterID + '].');
                  // We clear the according JSON states
                  setState(firstPart + '.logJSON', '[]');
                  setState(firstPart + '.logJSONcount', 0);
      
              });
          }
          if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Subscribing to Clear JSON Buttons: ' + logSubscribe)
      }
      
      /**
       * Subscribe to all .All.clearAllJSON
       */
      function stateSubscribeClearAllJSON() {
      
          on({id: FINAL_STATE_PATH + '.All.clearAllJSON', change: 'any', val: true}, function (obj) {
      
              // 1. All fitered log states
              const ALL_FILTER_IDS = getAllFilterIds();
              for (let loopFilterId of ALL_FILTER_IDS) {
                  // Version 4.5.6
                  // See https://forum.iobroker.net/post/375932
                  // We do no longer set the target states directly, but use the according clearJSON instead.
                  // This is to ensure that user can use the clearJSON timestamp for the time of last clearJSON.
                  setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.clearJSON', true);
                  // setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.logJSON', '[]');
                  // setState(FINAL_STATE_PATH + '.log' + loopFilterId + '.logJSONcount', 0);
      
              }
      
              // 2. Vis add-ons
              if(FINAL_NUM_OF_VIS_VIEWS > 0) {
                  for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                      let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                      setState(lpState + '.outputJSON', '[]');
                      setState(lpState + '.outputJSONcount', 0);
                  }
              }
          });
      
      }
      
      
      /**
       * Subscribe to .All.visViewX.whichFilter
       */
      function stateSubscribeVisWhichFilter() {
          if(FINAL_NUM_OF_VIS_VIEWS > 0) {
              for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                  let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                  on({id: lpState + '.whichFilter', change: 'ne'}, function (obj) {
                      if(!isLikeEmpty(obj.state.val)) {
                          let firstStatePart = obj.id.substr(0, (obj.id.length - obj.id.split('.').pop().length - 1)); // Gibt von '0_userdata.0.System.Log-Script.All.visView1.whichFilter' nur den Hauptteil ohne String nach letztem Punkt zurück, also "0_userdata.0.System.Log-Script.All.visView1"
                          setState(firstStatePart + '.' + 'outputJSON', getState(LOG_STATE_PATH + '.log' + obj.state.val + '.logJSON').val);
                          setState(firstStatePart + '.' + 'outputJSONcount', getState(LOG_STATE_PATH + '.log' + obj.state.val + '.logJSONcount').val);
                      }
                  });
              }
          }
      }
      
      /**
       * Subscribe to all .All.visViewX.clearJSON
       */
      function stateSubscribeVisClearJson() {
          if(FINAL_NUM_OF_VIS_VIEWS > 0) {
              for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                  let lpState = FINAL_STATE_PATH + '.All.visView' + (i+1);
                  on({id: lpState + '.clearJSON', change: 'any', val: true}, function (obj) {
                      let firstStatePart = obj.id.substr(0, (obj.id.length - obj.id.split('.').pop().length - 1)); // Gibt von '0_userdata.0.System.Log-Script.All.visView1.clearJSON' nur den Hauptteil ohne String nach letztem Punkt zurück, also "0_userdata.0.System.Log-Script.All.visView1"
                      let selectedFilter = getState(firstStatePart + '.whichFilter').val;
                      if(!isLikeEmpty(selectedFilter)) {    
                          setState(firstStatePart + '.outputJSON', '[]'); // .All.visViewX.outputJSON leeren
                          setState(firstStatePart + '.outputJSONcount', 0); // .All.visViewX.outputJSONcount leeren
                          setState(FINAL_STATE_PATH + '.log' + selectedFilter + '.clearJSON', true); // Log-Script.logXXXX.clearJSON leeren
                      }             
                  });
              }
          }
      }
      
      /**
       * Get all filter ids into an array (Homematic, Warnanderror, etc.)
       * @return [array] Filter ids in Array
       */
      function getAllFilterIds() {
          let allFilterIds = [];
          for(let i = 0; i < LOG_FILTER.length; i++) {
              let lpIDClean = cleanseStatePath(LOG_FILTER[i].id)
              if (LOG_FILTER[i].id !== '') allFilterIds.push(lpIDClean); // für VIS add-on states
          };
          return allFilterIds;
      }
      
      
      /**
       * Reformats a log date string accordingly
       * @param {string}    strDate   The date to convert
       * @param {string}    format    e.g. 'yyyy-mm-dd HH:MM:SS'. Both upper case and lower case letters are allowed.
       * @return {string}             Returns the resulting date string
       */
      function formatLogDateStr(strDate, format) {
      
          let strResult = format.toLowerCase();
          strResult = strResult.replace('yyyy', strDate.substr(0,4));
          strResult = strResult.replace('yy', strDate.substr(2,2));
          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;
      
      }
      
      /**
       * Cleanse the log line
       * @param {string}   logLine    The log line to be cleansed.
       * @return {string}             The cleaned log line
       */
      function cleanseLogLine(logLine) {
          // Remove color escapes - https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings
          let logLineResult = logLine.replace(/\u001b\[.*?m/g, ''); 
          // Sometimes, a log line starts with the term "undefined", so we remove it.
          if (logLineResult.substr(0,9) === 'undefined') logLineResult = logLineResult.substr(9,99999);
          // Remove white space, tab stops, new line
          logLineResult = logLineResult.replace(/\s\s+/g, ' ');
          // Check against global blacklist
          if(strMatchesTerms(logLineResult, BLACKLIST_GLOBAL, 'blacklist')) logLineResult = '';
      
      
          return logLineResult;
      }
      
      
      
      /**
       * Sorts the log array by date. We expect the first 23 chars of each element being a date in string format.
       * @param {array} inputArray       Array to process
       * @param {string}  order          'asc' or 'desc' for ascending or descending order
       */
      function sortLogArrayByDate(inputArray, order) {
          var result = inputArray.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;
      }
      
      
      /**
       * Splits a given log entry into an array with 4 elements.
       * @param {string}  inputValue  Log line like '2018-07-22 11:47:53.019  - info: javascript.0 script.js ...'
       * @return {object}   Array with 4 elements: 
       *                     0. datetime (e.g. 2018-07-22 11:47:53.019),
       *                     1. level (e.g. info)
       *                     2. source (e.g. javascript.0)
       *                     3. message (e.g. script.js....)
       *                     Returns FALSE if no match or input value not valid
       */
      function logLineSplit(inputValue) {
      
          // Get RegEx ready
          let mRegEx = new RegExp(LOG_PATT, 'g');
      
          // Split
          let returnObj = {}
          let m;
          do {
              m = mRegEx.exec(inputValue);
              if (m) {
                  returnObj.datetime = m[1];
                  returnObj.spaceAt2 = m[2];
                  returnObj.level = m[3];
                  returnObj.spaceAt4 = m[4];
                  returnObj.source = m[5];
                  returnObj.spaceAt6 = m[6];
                  returnObj.message = m[7];
              } 
          } while (m);
      
          // Now we check if we have valid entries we want
          if ((returnObj.datetime === undefined)
              || (returnObj.level === undefined)
              || (returnObj.source === undefined)
              || (returnObj.message === undefined)
          ) {
             return false; // no valid hits
          }
          // We can return the array now, since it meets all requirements
          return returnObj;
      
      }
      
      /**
       * Merges date/time, level, source and message to a logline
       * @param  {array}    inputValue   Array with 4 elements: date/time, level, source, message
       * @return {string}   Merged log line as string. Empty string '', if input value not valid.
      																			  
       */
      function logLineMerge(inputValue) {
      
          if (inputValue.length === 4) {
              let mergedLine = inputValue[0] + ' - ' + inputValue[1] + ': ' + inputValue[2] + ' ' + inputValue[3];
              return mergedLine;
          } else {
              // We expect a size of 4, so go out
              return '';
          }
      
      }
      
      
      /**
       * Merge Loglines if multiple values and add leading '[123 entries]' to log message
       * @param {array}  logArray        array of log entries
       * @return {array} the new merged log array
       */
      function mergeLogLines(logArray) {
      
          // We use array spreads '...' to copy array. If not, array is changed by reference and not value.
          // That means, if we change the target array, it will also change the source array.
          // See https://stackoverflow.com/questions/7486085/copy-array-by-value
          let arrCopy = [...logArray];
          let arrNew = [];
      
          for (let i = 0; i < arrCopy.length; i++) {
      
              if (! isLikeEmpty(arrCopy[i])) {
      
                  let lpEntry = arrCopy[i];
                  let lineWithoutDate = lpEntry.substring(23);
                  let lpLineSplit = logLineSplit(lpEntry);
      
                  // Get multiple values
                  let lpMulti = arrayGetElements(arrCopy, removeLeading123entries(lpLineSplit.message), false);
                  let result = lpEntry;
                  let lineCounter = 0;
                  if (lpMulti.length > 1) { // Treffer - die aktuelle Zeile zählt ja auch mit.
                      lineCounter = lpMulti.length;
                      let hitLeadingNumber = -1;
                      for (let hitLine of lpMulti) {
                          let hitLineSplit = logLineSplit(hitLine);
                          // Check if hit contains '[123 entries]'. If yes, get the number out of it into lineCounter.
                          // If not, we just count with 1.
                          hitLeadingNumber = checkForMultiEntry(hitLineSplit.message);
                          if (hitLeadingNumber > 1) {
                              lineCounter = hitLeadingNumber + lpMulti.length - 1;
                          }
                      }
                  } else {
                      lineCounter = 1;
                  }
      
                  if (lineCounter > 1) {
      
                          // remove from array by filling empty value
                          arrCopy = arrayReplaceElementsByValue(arrCopy, removeLeading123entries(lpLineSplit.message), '', false);
                          // new result
                          result = logLineMerge([lpLineSplit.datetime, lpLineSplit.level, lpLineSplit.source, '[' + lineCounter + ' ' + MERGE_LOGLINES_TXT + '] ' + removeLeading123entries(lpLineSplit.message)]);
                  }
                  arrNew.push(result);
              } 
          }
      
          return arrNew;
      
          /**
           * @param  {string}   strInput    A log message with potential leading '[123 entries]'
           * @return {string}   string without leading '[123 entries]', if it is there
           */
          function removeLeading123entries(strInput) {
      
              let mRegEx = new RegExp(MERGE_REGEX_PATT);
              let matches = mRegEx.exec(strInput);
              if (matches === null) {
                  return strInput;
              } else {
                  return matches[2];
              }
          }
      
          /**
           * @param  {string}   strInput    A log message checking for leading '[123 entries]'
           * @return {number}   returns the number 123 from '[123 entries]' if any match, or -1 if not found
           */
          function checkForMultiEntry(strInput) {
      
              // Get RegEx ready
              let mRegEx = new RegExp(MERGE_REGEX_PATT);
              let matches = mRegEx.exec(strInput);
              if (matches === null) {
                  return -1;
              } else {
                  return parseInt(matches[1]);
              }
          }
      
      }
      
      
      
      /*************
       * Get the file system path and filename of the current log file.
       * 
       * ioBroker creates a log file every midnight at 0:00 under '/opt/iobroker/log/'
       * Syntax of the log file is: iobroker.YYYY-MM-DD.log
       * This function returns the full path to the log file, considering the current date/time when this function is called.
       * @return {string}      Path and file name to log file.
       */
      function getCurrentFullFsLogPath() {
          let strLogPathFinal = LOG_FS_PATH;
          if (strLogPathFinal.slice(-1) !== '/') strLogPathFinal = strLogPathFinal + '/';
          let strFullLogPath = strLogPathFinal + DEBUG_CUSTOM_FILENAME;
          if (DEBUG_CUSTOM_FILENAME === '') strFullLogPath = strLogPathFinal + 'iobroker.' + getCurrentISODate() + '.log';
          return strFullLogPath;
      }
      
      /**
       * Clear array: if stateForTimeStamp is greater or equal than log date, we remove the entire log entry
       * @param {array} inputArray     Array of log entries
       * @param {string} stateForTimeStamp     state of which we need the time stamp
       * @return {array} cleaned log
       */
      function clearJsonByDate(inputArray, stateForTimeStamp) {
          let dtState = new Date(getState(stateForTimeStamp).ts);
          if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'Time of last change of state [' + stateForTimeStamp + ']: ' + dtState);
          let newArray = [];
          for (let lpLog of inputArray) {
              let dtLog = new Date(lpLog.substr(0,23));
              if (dtLog.getTime() >= dtState.getTime()) {
                  newArray.push(lpLog);            
              }
        }
        return newArray;
      }
      
      /**
       * Build an array of states we need to create.
       * @return {array} 2 array of states to be created. First is for force=false, second is for force=true.
       */
      function buildNeededStates() {
      
          let debugCleanIDs = '';  // für Debug-Ausgabe
          let statesArray = [];
      
          for(let i = 0; i < LOG_FILTER.length; i++) {
              if (LOG_FILTER[i].id !== '') {
                  let lpIDClean = cleanseStatePath(LOG_FILTER[i].id);
                  debugCleanIDs += ((debugCleanIDs === '') ? '' : '; ') + lpIDClean; // für Debug-Ausgabe
      
                  statesArray.push({ id:'log' + lpIDClean + '.log', name:'Filtered Log - ' + lpIDClean, type:"string", role: "state", def: ""});
                  statesArray.push({ id:'log' + lpIDClean + '.logJSON', name:'Filtered Log - ' + lpIDClean + ' - JSON', type:"string", role: "state", def: ""});
                  statesArray.push({ id:'log' + lpIDClean + '.logJSONcount', name:'Filtered Log - Count of JSON ' + lpIDClean, role: "state", type:"number", def: 0});
                  statesArray.push({ id:'log' + lpIDClean + '.clearJSON', name:'Clear JSON log ' + lpIDClean, role: "button", type:"boolean", def: false});
      
              }
          }
          if (SCRIPT_DEBUG) log (DEBUG_IGNORE_STR + 'createLogStates(): Clean IDs: ' + debugCleanIDs);
      
      
          let finalStatesForceFalse = [];
          let finalStatesForceTrue = [];
          for (let s=0; s < statesArray.length; s++) {
              finalStatesForceFalse.push([FINAL_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,
              }]);
          }
      
          // Jetzt noch die VIS Add-on states (All.visView1, All.visView2, etc.)
          if(FINAL_NUM_OF_VIS_VIEWS > 0) {
              let dropdown = '';
              const ALL_FILTER_IDS = getAllFilterIds();
              for (let lpEntry of ALL_FILTER_IDS) {
                  dropdown += '"' + lpEntry + '":"' + lpEntry + '",'; // fill JSON string
              }
              dropdown = dropdown.substr(0, dropdown.length-1); // remove last comma ","
              dropdown = '{' + dropdown + '}'; // finalize JSON string
              let dropdownJSON = JSON.parse(dropdown); // convert to JSON
              for(let i = 0; i < FINAL_NUM_OF_VIS_VIEWS; i++) {
                  let lpStateVisViews = FINAL_STATE_PATH + '.All.visView' + (i+1);
                  finalStatesForceTrue.push ([lpStateVisViews + '.whichFilter', {'name':'Für VIS-Button/Auswahlmenü des anzuzeigenden Filter in JSON-Tabelle', 'type':'string', 'read':false, 'write':true, 'role':'value', 'states': dropdownJSON, 'def': ALL_FILTER_IDS[0]}]); // force true to rebuild state drop-down
                  finalStatesForceTrue.push ([lpStateVisViews + '.outputJSON',  {'name': 'JSON-Ausgabe des in whichFilter gewählten Filters', 'type': 'string', 'read': true, 'write': false, 'role': 'state', 'def': '' }]); // force true to empty on re-start of script.
                  finalStatesForceFalse.push ([lpStateVisViews + '.outputJSONcount',  {'name': 'Anzahl Zeilen in JSON-Ausgabe des in whichFilter gewählten Filters', 'type': 'number', 'read': true, 'write': false, 'role': 'state', 'def': 0 }]);
                  finalStatesForceFalse.push([lpStateVisViews + '.clearJSON',   {'name':'Clear currently selected JSON Log', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def': false }]);
              }
          }
      
          // Nun noch Button zum leeren ALLER JSONs
          finalStatesForceFalse.push([FINAL_STATE_PATH + '.All.clearAllJSON',   {'name':'Clear ALL JSON logs', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def': false }]);
      
          // Zeit letztes Update
          finalStatesForceFalse.push([FINAL_STATE_PATH + '.All.lastTimeUpdated',   {'name':'Date/Time of last update', 'type':'number', 'read':true, 'write':false, 'role':'value.time'}]);
      
          // Done.
          return [finalStatesForceFalse, finalStatesForceTrue];
      }
      
      /**
       * Converts a timestamp to log date format, like 2019-10-15 16:38:00.260.
       * @param {object}  timeStamp   The date/time timestamp to convert.
       * @return {string} The resulting log date format as string.
       */
      function timestampToLogDate(timeStamp) {
      
          let date = new Date(timeStamp);
          // Need to convert to local time as this time provided from onLog() is UTC
          // https://stackoverflow.com/questions/6525538/convert-utc-date-time-to-local-date-time/18330682
          let localDate = new Date(date.getTime() - date.getTimezoneOffset()*60*1000);
      
          // Convert to ISO string, so like 2019-10-15T16:38:00.260Z
          let strResult = localDate.toISOString();
      
          // date.toISOString() adds T and Z, so we remove these letters, as the log do not show these.
          strResult = strResult.replace('T', ' ');  // remove T
          strResult = strResult.replace('Z', '');  // remove Z at the end
          return strResult;
      
      }
      
      /**
       * Remove PID in log message 
       * The js-controller version 2.0+ adds the PID number inside brackets to the beginning of the message. We remove it here.
       * @param {string} msg   The log message, like: 'javascript.0 (123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
       */
      function removePID(msg) {
      
          // First: Split source and message text. 
          // Input is like: 'javascript.0 (123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
          let regexp = /^(\S+)\s(.*)/;
          let matches_array = msg.match(regexp);
          let strFirst = matches_array[1];    // like 'javascript.0'
          let strRest = matches_array[2];     // like '(123) Logtext 123 Logtext 123 Logtext 123 Logtext 123'
          
          // Next, we remove the PID
          strRest = strRest.replace(/^\([0-9]{1,9}\)\s/, '');
      
          // Last, we put the two strings together again
          return strFirst + ' ' + strRest;
      
      }
      
      
      
      
      /*************************************************************************************************************************
       * onStop - Being executed once this ioBroker Script stops. 
       *************************************************************************************************************************/
      // This is to end the Tale. Not sure, if we indeed need it, but just in case...
      onStop(function myScriptStop () {
      
          // Unsubscribe log handler
          onLogUnregister(G_LogHandler);
          if (LOG_INFO) log('Unsubscribed to Log Handler.', 'info');
      
      }, 0);
      
      
      
      /*************************************************************************************************************************
       * General supporting functions
       *************************************************************************************************************************/
      
      /**
       * Remove Duplicates from Array
       * Source - https://stackoverflow.com/questions/23237704/nodejs-how-to-remove-duplicates-from-array
       * @param {array} inputArray       Array to process
       * @return {array}  Array without duplicates.
       */
      function arrayRemoveDublicates(inputArray) {
          let uniqueArray;
          uniqueArray = inputArray.filter(function(elem, pos) {
              return inputArray.indexOf(elem) == pos;
          });
          return uniqueArray;
      }
      
      /**
       * Clean Array: Removes 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
       * @param {array} inputArray       Array to process
       * @return {array}  Cleaned array
       */
      function cleanArray(inputArray) {
        var newArray = [];
        for (let i = 0; i < inputArray.length; i++) {
          if (inputArray[i]) {
            newArray.push(inputArray[i]);
          }
        }
        return newArray;
      }
      
      
      /**
       * Checks if Array or String is not undefined, null or empty.
       * 08-Sep-2019: added check for [ and ] to also catch arrays with empty strings.
       * @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 >"< or >[< or >]< is considered empty
       */
      function isLikeEmpty(inputVar) {
          if (typeof inputVar !== 'undefined' && inputVar !== null) {
              let strTemp = JSON.stringify(inputVar);
              strTemp = strTemp.replace(/\s+/g, ''); // remove all whitespaces
              strTemp = strTemp.replace(/\"+/g, "");  // remove all >"<
              strTemp = strTemp.replace(/\'+/g, "");  // remove all >'<
              strTemp = strTemp.replace(/\[+/g, "");  // remove all >[<
              strTemp = strTemp.replace(/\]+/g, "");  // remove all >]<
              if (strTemp !== '') {
                  return false;
              } else {
                  return true;
              }
          } else {
              return true;
          }
      }
      
      /**
       * Returns the current date in ISO format "YYYY-MM-DD".
       * @return  {string}    Date in ISO format
       */
      function getCurrentISODate() {
          let currDate = new Date();
          return currDate.getFullYear() + '-' + zeroPad((currDate.getMonth() + 1), 2) + '-' + 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 zeroPad(num, places) {
          let zero = places - num.toString().length + 1;
          return Array(+(zero > 0 && zero)).join("0") + num;        
      } 
      
      
      /**
       * Will just keep lower case letters, numbers, '-' and '_' and removes the rest
       * Also, capitalize first Letter.
       */
      function cleanseStatePath(stringInput) {
          let strProcess = stringInput;
          strProcess = strProcess.replace(/([^a-z0-9_\-]+)/gi, '');
          strProcess = strProcess.toLowerCase();
          strProcess = strProcess.charAt(0).toUpperCase() + strProcess.slice(1);
          return strProcess;
      
      }
      
      
      /**
       * Checks if the string provided contains either every or some terms.
       * Source: https://stackoverflow.com/questions/36283767/javascript-select-the-string-if-it-matches-multiple-words-in-array
       * @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 {boolean}       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
       */
      function strMatchesTerms(strInput, arrayTerms, type) {
          if(type === 'blacklist') {
              if (Array.isArray(arrayTerms)) {
                  let arrayTermsNew = [];
                  for (let lpTerm of arrayTerms) {
                      if (lpTerm.length >= 3) {
                          arrayTermsNew.push(lpTerm);
                      }
                  }
                  if(isLikeEmpty(arrayTermsNew) === false) {
                      let 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') {
                      let bResultEvery = arrayTerms.every(function(word) {
                          return strInput.indexOf(word) > -1;
                      });
                      return bResultEvery;
                  } else if(type === 'some') {
                      let 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
              }
          }
      }
      
      /**
       * Checks if a a given state or part of state is existing.
       * This is a workaround, as getObject() or getState() throw warnings in the log.
       * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
       * See: https://forum.iobroker.net/topic/11354/
       * @param {string}    strStatePath     Input string of state, like 'javas-cript.0.switches.Osram.Bedroom'
       * @param {boolean}   [strict=true]    Optional: Default is true. If true, it will work strict, if false, it will add a wildcard * to the end of the string
       * @return {boolean}                   true if state exists, false if not
       */
      function isState(strStatePath, strict) {
      
          if(strict === undefined) strict = true;
      
          let mSelector;
          if (strict) {
              mSelector = $('state[id=' + strStatePath + '$]');
          } else {
              mSelector = $('state[id=' + strStatePath + ']');
          }
          if (mSelector.length > 0) {
              return true;
          } else {
              return false;
          }
      }
      
      
      /**
       * Removing Array element(s) by input value. 
       * @param {array}   arr             the input array
       * @param {string}  valRemove       the value to be removed
       * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
       * @return {array}  the array without the element(s)
       */
      function arrayRemoveElementsByValue(arr, valRemove, exact) {
      
          for ( let i = 0; i < arr.length; i++){ 
              if (exact) {
                  if ( arr[i] === valRemove) {
                      arr.splice(i, 1);
                      i--; // required, see https://love2dev.com/blog/javascript-remove-from-array/
                  }
              } else {
                  if (arr[i].indexOf(valRemove) != -1) {
                      arr.splice(i, 1);
                      i--; // see above
                  }
              }
          }
          return arr;
      }
      
      /**
       * Escapes a string for use in RegEx as (part of) pattern
       * Source: https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
       * @param {string} inputStr  The input string to be escaped
       * @return {string}  The escaped string
       */
      function escapeRegExp(inputStr) {
          return inputStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
      }
      
      
      /**
       * Get all elements of an array if found
       * @param {array}   arr             the input array
       * @param {string}  valFind         the value to find
       * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
       * @return {array}  an array with all hits or empty array if no hits.
       */
      function arrayGetElements(arr, valFind, exact) {
          let resultArr = [];
          for ( let i = 0; i < arr.length; i++){ 
              if (exact) {
                  if ( arr[i] === valFind) {
                      resultArr.push(arr[i]);
                  }
              } else {
                  if (arr[i].indexOf(valFind) != -1) {
                      resultArr.push(arr[i]);
                  }
              }
          }
          return resultArr;
      }
      
      /**
       * Replace Array element(s) by input value. 
       * @param {array}   arr             the input array
       * @param {string}  valReplace      the value to search for
       * @param {string}  newValue        the new value
       * @param {boolean} [exact=true]    OPTIONAL: default is true. if true, it must fully match. if false, it matches also if valRemove is part of element string
       * @return {array}  the array with replaced the element(s)
       */
      function arrayReplaceElementsByValue(arr, valReplace, newValue, exact) {
      
          for ( let i = 0; i < arr.length; i++){ 
              if (exact) {
                  if ( arr[i] === valReplace) {
                      arr[i] = newValue;
                  }
              } else {
                  if (arr[i].indexOf(valReplace) != -1) {
                      arr[i] = newValue;
                  }
              }
          }
          return arr;
      }
      
      /**
       * Retrieve values from a CONFIG variable, example:
       * const CONF = [{car: 'bmw', color: 'black', hp: '250'}, {car: 'audi', color: 'blue', hp: '190'}]
       * To get the color of the Audi, use: getConfigValuePerKey(CONF, 'car', 'audi', 'color')
       * To find out which car has 190 hp, use: getConfigValuePerKey(CONF, 'hp', '190', 'car')
       * @param {object}  config     The configuration variable/constant
       * @param {string}  key1       Key to look for.
       * @param {string}  key1Value  The value the key should have
       * @param {string}  key2       The key which value we return
       * @returns {any}    Returns the element's value, or number -1 of nothing found.
       */
      function getConfigValuePerKey(config, key1, key1Value, key2) {
          for (let lpConfDevice of config) {
              if ( lpConfDevice[key1] === key1Value ) {
                  if (lpConfDevice[key2] === undefined) {
                      return -1;
                  } else {
                      return lpConfDevice[key2];
                  }
              }
          }
          return -1;
      }
      
      
      /**
       * For a given state path, we extract the location '0_userdata.0' or 'javascript.0' or add '0_userdata.0', if missing.
       * @param {string}  path            Like: 'Computer.Control-PC', 'javascript.0.Computer.Control-PC', '0_userdata.0.Computer.Control-PC'
       * @param {boolean} returnFullPath  If true: full path like '0_userdata.0.Computer.Control-PC', if false: just location like '0_userdata.0' or 'javascript.0'
       * @return {string}                 Path
       */
      function validateStatePath(path, returnFullPath) {
          if (path.startsWith('.')) path = path.substr(1);    // Remove first dot
          if (path.endsWith('.'))   path = path.slice(0, -1); // Remove trailing dot
          if (path.length < 1) log('Provided state path is not valid / too short.', 'error')
          let match = path.match(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/);
          let location = (match == null) ? '0_userdata.0' : match[0].slice(0, -1); // default is '0_userdata.0'.
          if(returnFullPath) {
              return (path.indexOf(location) == 0) ? path : (location + '.' + path);
          } else {
              return location;
          }
      }
      
      
      /**
       * Create states under 0_userdata.0 or javascript.x
       * Current Version:     https://github.com/Mic-M/iobroker.createUserStates
       * Support:             https://forum.iobroker.net/topic/26839/
       * Autor:               Mic (ioBroker) | Mic-M (github)
       * Version:             1.1 (26 January 2020)
       * Example:             see https://github.com/Mic-M/iobroker.createUserStates#beispiel
       * -----------------------------------------------
       * PLEASE NOTE: Per https://github.com/ioBroker/ioBroker.javascript/issues/474, the used function setObject() 
       *              executes the callback PRIOR to completing the state creation. Therefore, we use a setTimeout and counter. 
       * -----------------------------------------------
       * @param {string} where          Where to create the state: '0_userdata.0' or 'javascript.x'.
       * @param {boolean} force         Force state creation (overwrite), if state is existing.
       * @param {array} statesToCreate  State(s) to create. single array or array of arrays
       * @param {object} [callback]     Optional: a callback function -- This provided function will be executed after all states are created.
       */
      function createUserStates(where, force, statesToCreate, callback = undefined) {
       
          const WARN = false; // Only for 0_userdata.0: Throws warning in log, if state is already existing and force=false. Default is false, so no warning in log, if state exists.
          const LOG_DEBUG = false; // To debug this function, set to true
          // Per issue #474 (https://github.com/ioBroker/ioBroker.javascript/issues/474), the used function setObject() executes the callback 
          // before the state is actual created. Therefore, we use a setTimeout and counter as a workaround.
          const DELAY = 50; // Delay in milliseconds (ms). Increase this to 100, if it is not working.
      
          // Validate "where"
          if (where.endsWith('.')) where = where.slice(0, -1); // Remove trailing dot
          if ( (where.match(/^((javascript\.([1-9][0-9]|[0-9]))$|0_userdata\.0$)/) == null) ) {
              log('This script does not support to create states under [' + where + ']', 'error');
              return;
          }
      
          // Prepare "statesToCreate" since we also allow a single state to create
          if(!Array.isArray(statesToCreate[0])) statesToCreate = [statesToCreate]; // wrap into array, if just one array and not inside an array
      
          // Add "where" to STATES_TO_CREATE
          for (let i = 0; i < statesToCreate.length; i++) {
              let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
              lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/,'') // remove any javascript.x. / 0_userdata.0. from beginning
              lpPath = where + '.' + lpPath; // add where to beginning of string
              statesToCreate[i][0] = lpPath;
          }
      
          if (where != '0_userdata.0') {
              // Create States under javascript.x
              let numStates = statesToCreate.length;
              statesToCreate.forEach(function(loopParam) {
                  if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']');
                  let loopInit = (loopParam[1]['def'] == undefined) ? null : loopParam[1]['def']; // mimic same behavior as createState if no init value is provided
                  createState(loopParam[0], loopInit, force, loopParam[1], function() {
                      numStates--;
                      if (numStates === 0) {
                          if (LOG_DEBUG) log('[Debug] All states processed.');
                          if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                              if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                              return callback();
                          } else {
                              return;
                          }
                      }
                  });
              });
          } else {
              // Create States under 0_userdata.0
              let numStates = statesToCreate.length;
              let counter = -1;
              statesToCreate.forEach(function(loopParam) {
                  counter += 1;
                  if (LOG_DEBUG) log ('[Debug] Currently processing following state: [' + loopParam[0] + ']');
                  if( ($(loopParam[0]).length > 0) && (existsState(loopParam[0])) ) { // Workaround due to https://github.com/ioBroker/ioBroker.javascript/issues/478
                      // State is existing.
                      if (WARN && !force) log('State [' + loopParam[0] + '] is already existing and will no longer be created.', 'warn');
                      if (!WARN && LOG_DEBUG) log('[Debug] State [' + loopParam[0] + '] is already existing. Option force (=overwrite) is set to [' + force + '].');
                      if(!force) {
                          // State exists and shall not be overwritten since force=false
                          // So, we do not proceed.
                          numStates--;
                          if (numStates === 0) {
                              if (LOG_DEBUG) log('[Debug] All states successfully processed!');
                              if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                  if (LOG_DEBUG) log('[Debug] An optional callback function was provided, which we are going to execute now.');
                                  return callback();
                              }
                          } else {
                              // We need to go out and continue with next element in loop.
                              return; // https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach
                          }
                      } // if(!force)
                  }
      
                  // State is not existing or force = true, so we are continuing to create the state through setObject().
                  let obj = {};
                  obj.type = 'state';
                  obj.native = {};
                  obj.common = loopParam[1];
                  setObject(loopParam[0], obj, function (err) {
                      if (err) {
                          log('Cannot write object for state [' + loopParam[0] + ']: ' + err);
                      } else {
                          if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']')
                          let init = null;
                          if(loopParam[1].def === undefined) {
                              if(loopParam[1].type === 'number') init = 0;
                              if(loopParam[1].type === 'boolean') init = false;
                              if(loopParam[1].type === 'string') init = '';
                          } else {
                              init = loopParam[1].def;
                          }
                          setTimeout(function() {
                              setState(loopParam[0], init, true, function() {
                                  if (LOG_DEBUG) log('[Debug] setState durchgeführt: ' + loopParam[0]);
                                  numStates--;
                                  if (numStates === 0) {
                                      if (LOG_DEBUG) log('[Debug] All states processed.');
                                      if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                                          if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                                          return callback();
                                      }
                                  }
                              });
                          }, DELAY + (20 * counter) );
                      }
                  });
              });
          }
      }
      

      MicM Offline
      MicM Offline
      Mic
      Developer
      wrote on last edited by
      #453

      @Negalein
      Das obige Script von dir wirft im JS-Adapter zahlreiche Fehler, siehe rote Markierungen rechts.
      xxxxx.png

      So sieht das von Github aus:
      xxxgit.png

      Am besten startest du noch mal mit dem aktuellen Github-Script.

      NegaleinN 1 Reply Last reply
      0
      • MicM Mic

        @Negalein
        Das obige Script von dir wirft im JS-Adapter zahlreiche Fehler, siehe rote Markierungen rechts.
        xxxxx.png

        So sieht das von Github aus:
        xxxgit.png

        Am besten startest du noch mal mit dem aktuellen Github-Script.

        NegaleinN Offline
        NegaleinN Offline
        Negalein
        Global Moderator
        wrote on last edited by
        #454

        @Mic sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

        Am besten startest du noch mal mit dem aktuellen Github-Script.

        jetzt passt es.
        DP wurden sofort angelegt.

        ° Node.js: 20.17.0 NPM: 10.8.2
        ° Proxmox, Ubuntu 22.04.3 LTS
        ° Fixer ---> iob fix

        1 Reply Last reply
        0
        • MicM Mic

          Version 4.5 Alpha

          Habe nun alles eingebaut.

          Neue Datenpunkte:

          • All.visView1. Enthält alle Datenpunkte wie oben versprochen, und noch ein paar mehr 🙂 Für mehrere Views/Tablets usw. könnt ihr weitere (visView2, visView3, usw.) anlegen. Dazu im Script NUMBER_OF_VIS_VIEWS anpassen. *
          • All.clearAllJSON Löscht alle JSON-Logs auf einmal
          • All.lastTimeUpdated Voreingestellt aktualisiert das Script alle 20 Sekunden. Bei jedem Update wird dieser Datenpunkt aktualisiert. Kann in VIS zur Darstellung verwendet werden.

          Ich habe es getestet und es läuft sehr zuverlässig bei mir. Würde mich über Tester freuen.

          Hier Version 4.5 Alpha zum testen:
          Ich werde das Script offiziell in Github übernehmen, sobald von euch erfolgreich getestet wurde.
          Link: Version 4.5 Alpha


          *) Hier noch weitere Erläuterung zu All.visView1.:

          /*******************************************************************************
           * Optionale Konfiguration: Auswahl Log-Filter für VIS unterhalb ".All"
           ******************************************************************************/
          // Wenn man in VIS eine Tabelle der Logs ausgibt, kann man hiermit mit Buttons
          // ('Homematic', 'Warnungen', 'Fehler' usw. zwischen den einzelnen Filtern umschalten,
          // die dann dynamisch jeweils in dieser einen Tabelle ausgegeben werden.
          // Hier die Anzahl der unterschiedlichen VIS-Views angeben, in denen du das brauchst.
          // Diese werden angelegt unter '.All.visView1', '.All.visView2', usw.
          // Falls auf 0 gestellt, dann werden gar keine Datenpunkte ausgegeben.
          const NUMBER_OF_VIS_VIEWS = 1;
          
          MartinK.M Offline
          MartinK.M Offline
          MartinK.
          wrote on last edited by
          #455

          @Mic Absolut genial gemacht! Sogar für Anfänger wie mich alles zu verstehen 🙂 ... läuft auf anhieb!!! 😀 👍 👍

          ...konnte ich helfen ? Dann würde ich mich über ein Voting freuen ! (rechts unten)...

          1 Reply Last reply
          1
          • B Offline
            B Offline
            blackeagle998
            wrote on last edited by blackeagle998
            #456

            @Mic
            Das Skript läuft wie immer super, solange ich mindestens eine VIS View angebe.
            Ändere ich den Wert "const NUMBER_OF_VIS_VIEWS = 1;" auf 0, erhalte ich im LOG folgende Fehler:

            javascript.0	2020-02-13 12:02:44.106	error	(10714) at process._tickCallback (internal/process/next_tick.js:68:7)
            javascript.0	2020-02-13 12:02:44.106	error	(10714) at promise.then (/opt/iobroker/node_modules/standard-as-callback/built/index.js:19:49)
            javascript.0	2020-02-13 12:02:44.105	error	(10714) at tryCatcher (/opt/iobroker/node_modules/standard-as-callback/built/utils.js:11:23)
            javascript.0	2020-02-13 12:02:44.105	error	(10714) at client.set.err (/opt/iobroker/node_modules/iobroker.js-controller/lib/states/statesInRedis.js:534:55)
            javascript.0	2020-02-13 12:02:44.105	error	(10714) at err (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:318:38)
            javascript.0	2020-02-13 12:02:44.105	error	(10714) at Object.<anonymous> (script.js.common.02_IO-Broker.Logfile:1708:44)
            javascript.0	2020-02-13 12:02:44.105	error	(10714) at script.js.common.02_IO-Broker.Logfile:421:9
            javascript.0	2020-02-13 12:02:44.104	error	(10714) at createUserStates (script.js.common.02_IO-Broker.Logfile:1629:43)
            javascript.0	2020-02-13 12:02:44.103	error	(10714) Error in callback: TypeError: Cannot read property 'replace' of undefined
            

            Die Zeilen 1627 bis 1633 enthalten folgenden Code:

             // Add "where" to STATES_TO_CREATE
                for (let i = 0; i < statesToCreate.length; i++) {
                    let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
                    lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/,'') // remove any javascript.x. / 0_userdata.0. from beginning
                    lpPath = where + '.' + lpPath; // add where to beginning of string
                    statesToCreate[i][0] = lpPath;
                }
            

            Das soll nur als Hinweis dienen, da das Skript ja für die VIS Aufbereitung gedacht ist und somit mindestens eine VIS View vorhanden sein sollte.

            MicM 1 Reply Last reply
            0
            • B blackeagle998

              @Mic
              Das Skript läuft wie immer super, solange ich mindestens eine VIS View angebe.
              Ändere ich den Wert "const NUMBER_OF_VIS_VIEWS = 1;" auf 0, erhalte ich im LOG folgende Fehler:

              javascript.0	2020-02-13 12:02:44.106	error	(10714) at process._tickCallback (internal/process/next_tick.js:68:7)
              javascript.0	2020-02-13 12:02:44.106	error	(10714) at promise.then (/opt/iobroker/node_modules/standard-as-callback/built/index.js:19:49)
              javascript.0	2020-02-13 12:02:44.105	error	(10714) at tryCatcher (/opt/iobroker/node_modules/standard-as-callback/built/utils.js:11:23)
              javascript.0	2020-02-13 12:02:44.105	error	(10714) at client.set.err (/opt/iobroker/node_modules/iobroker.js-controller/lib/states/statesInRedis.js:534:55)
              javascript.0	2020-02-13 12:02:44.105	error	(10714) at err (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:318:38)
              javascript.0	2020-02-13 12:02:44.105	error	(10714) at Object.<anonymous> (script.js.common.02_IO-Broker.Logfile:1708:44)
              javascript.0	2020-02-13 12:02:44.105	error	(10714) at script.js.common.02_IO-Broker.Logfile:421:9
              javascript.0	2020-02-13 12:02:44.104	error	(10714) at createUserStates (script.js.common.02_IO-Broker.Logfile:1629:43)
              javascript.0	2020-02-13 12:02:44.103	error	(10714) Error in callback: TypeError: Cannot read property 'replace' of undefined
              

              Die Zeilen 1627 bis 1633 enthalten folgenden Code:

               // Add "where" to STATES_TO_CREATE
                  for (let i = 0; i < statesToCreate.length; i++) {
                      let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
                      lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/,'') // remove any javascript.x. / 0_userdata.0. from beginning
                      lpPath = where + '.' + lpPath; // add where to beginning of string
                      statesToCreate[i][0] = lpPath;
                  }
              

              Das soll nur als Hinweis dienen, da das Skript ja für die VIS Aufbereitung gedacht ist und somit mindestens eine VIS View vorhanden sein sollte.

              MicM Offline
              MicM Offline
              Mic
              Developer
              wrote on last edited by
              #457

              @blackeagle998 sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

              Das soll nur als Hinweis dienen

              Vielen Dank für den Test. Habe ich in Version 4.7 korrigiert. Für ein Update reicht es, alles unterhalb des folgenden Kommentars zu ersetzen:

              /*************************************************************************************************************************
               * Ab hier nichts mehr ändern / Stop editing here!
               *************************************************************************************************************************/
              
              1 Reply Last reply
              0
              • LatziL Offline
                LatziL Offline
                Latzi
                wrote on last edited by
                #458

                @Mic

                ich hab im Log folgende Fehlermeldungen:

                2020-02-14 20:06:16.407 - error: rtspStream.0 (14568) uncaught exception: i is not defined
                2020-02-14 20:06:16.408 - error: rtspStream.0 (14568) ReferenceError: i is not defined
                2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[43]: frame=95046 fps= 15 q=6.1 size= 315696kB time=01:45:32.56 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[44]: frame=95054 fps= 15 q=3.9 size= 315727kB time=01:45:33.06 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[45]: frame=95061 fps= 15 q=4.7 size= 315740kB time=01:45:33.64 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[46]: frame=95069 fps= 15 q=5.0 size= 315772kB time=01:45:34.07 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[47]: frame=95076 fps= 15 q=4.7 size= 315785kB time=01:45:34.65 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[48]: frame=95084 fps= 15 q=4.6 size= 315819kB time=01:45:35.15 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[49]: frame=95092 fps= 15 q=5.7 size= 315848kB time=01:45:35.66 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[50]: frame=95100 fps= 15 q=4.7 size= 315864kB time=01:45:36.16 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[51]: frame=95108 fps= 15 q=4.6 size= 315900kB time=01:45:36.66 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[52]: frame=95115 fps= 15 q=5.0 size= 315932kB time=01:45:37.24 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[53]: frame=95123 fps= 15 q=4.2 size= 315949kB time=01:45:37.74 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[54]: frame=95131 fps= 15 q=4.7 size= 315975kB time=01:45:38.25 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[55]: frame=95139 fps= 15 q=4.6 size= 316009kB time=01:45:38.75 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[56]: frame=95146 fps= 15 q=6.0 size= 316021kB time=01:45:39.26 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[57]: frame=95154 fps= 15 q=4.8 size= 316056kB time=01:45:39.76 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[58]: frame=95162 fps= 15 q=3.6 size= 316087kB time=01:45:40.26 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: ReferenceError: i is not defined
                2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: at WebSocketServer.wsServer.broadcast (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:103:54)
                2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:109:26)
                2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.emit (events.js:198:13)
                2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.mpeg1Muxer.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:47:17)
                2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.emit (events.js:198:13)
                2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Socket.Mpeg1Muxer.stream.stdout.on (/opt/iobroker/node_modules/node-rtsp-stream/mpeg1muxer.js:39:17)
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.emit (events.js:198:13)
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at addChunk (_stream_readable.js:288:12)
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at readableAddChunk (_stream_readable.js:269:11)
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.Readable.push (_stream_readable.js:224:10)
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: frame=95169 fps= 15 q=4.4 size= 316101kB time=01:45:40.84 bitrate= 408.4kbits/s speed= 1x
                2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[60]: frame=95177 fps= 15 q=5.3 size= 316135kB time=01:45:41.34 bitrate= 408.4kbits/s speed= 1x 
                

                Es wird mir im DP error aber nichts angezeigt. Ich habe auch nichts deratiges in der blacklist. Hast du eine Erklärung dafür?

                GlasfaserG MicM 2 Replies Last reply
                0
                • LatziL Latzi

                  @Mic

                  ich hab im Log folgende Fehlermeldungen:

                  2020-02-14 20:06:16.407 - error: rtspStream.0 (14568) uncaught exception: i is not defined
                  2020-02-14 20:06:16.408 - error: rtspStream.0 (14568) ReferenceError: i is not defined
                  2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[43]: frame=95046 fps= 15 q=6.1 size= 315696kB time=01:45:32.56 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[44]: frame=95054 fps= 15 q=3.9 size= 315727kB time=01:45:33.06 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[45]: frame=95061 fps= 15 q=4.7 size= 315740kB time=01:45:33.64 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[46]: frame=95069 fps= 15 q=5.0 size= 315772kB time=01:45:34.07 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[47]: frame=95076 fps= 15 q=4.7 size= 315785kB time=01:45:34.65 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[48]: frame=95084 fps= 15 q=4.6 size= 315819kB time=01:45:35.15 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[49]: frame=95092 fps= 15 q=5.7 size= 315848kB time=01:45:35.66 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[50]: frame=95100 fps= 15 q=4.7 size= 315864kB time=01:45:36.16 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[51]: frame=95108 fps= 15 q=4.6 size= 315900kB time=01:45:36.66 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[52]: frame=95115 fps= 15 q=5.0 size= 315932kB time=01:45:37.24 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[53]: frame=95123 fps= 15 q=4.2 size= 315949kB time=01:45:37.74 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[54]: frame=95131 fps= 15 q=4.7 size= 315975kB time=01:45:38.25 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[55]: frame=95139 fps= 15 q=4.6 size= 316009kB time=01:45:38.75 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[56]: frame=95146 fps= 15 q=6.0 size= 316021kB time=01:45:39.26 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[57]: frame=95154 fps= 15 q=4.8 size= 316056kB time=01:45:39.76 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[58]: frame=95162 fps= 15 q=3.6 size= 316087kB time=01:45:40.26 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: ReferenceError: i is not defined
                  2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: at WebSocketServer.wsServer.broadcast (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:103:54)
                  2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:109:26)
                  2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.emit (events.js:198:13)
                  2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.mpeg1Muxer.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:47:17)
                  2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.emit (events.js:198:13)
                  2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Socket.Mpeg1Muxer.stream.stdout.on (/opt/iobroker/node_modules/node-rtsp-stream/mpeg1muxer.js:39:17)
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.emit (events.js:198:13)
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at addChunk (_stream_readable.js:288:12)
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at readableAddChunk (_stream_readable.js:269:11)
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.Readable.push (_stream_readable.js:224:10)
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: frame=95169 fps= 15 q=4.4 size= 316101kB time=01:45:40.84 bitrate= 408.4kbits/s speed= 1x
                  2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[60]: frame=95177 fps= 15 q=5.3 size= 316135kB time=01:45:41.34 bitrate= 408.4kbits/s speed= 1x 
                  

                  Es wird mir im DP error aber nichts angezeigt. Ich habe auch nichts deratiges in der blacklist. Hast du eine Erklärung dafür?

                  GlasfaserG Offline
                  GlasfaserG Offline
                  Glasfaser
                  wrote on last edited by Glasfaser
                  #459

                  @Latzi

                  Teste mal mit dem Skript von Mic , hast du dort eine Log Ausgabe ?

                  log('Error-Meldung', 'error');
                  
                  log('Warn-Meldung', 'warn');
                  
                  // Nochmal nach 2 Sekunden.
                  
                  setTimeout(function() {
                  
                      log('Error-Meldung', 'error');
                  
                      log('Warn-Meldung', 'warn');
                  
                  }, 2*1000);
                  
                  // Nach 30 Sekunden noch eine Warnmeldung.
                  
                  setTimeout(function() {
                  
                      log('Neue Warn-Meldung nach 30 Sekunden', 'warn');
                  
                  }, 30*1000);
                  
                  

                  Synology 918+ 16GB - ioBroker in Docker v9 , VISO auf Trekstor Primebook C13 13,3" , Hikvision Domkameras mit Surveillance Station .. CCU RaspberryMatic in Synology VM .. Zigbee CC2538+CC2592 .. Sonoff .. KNX .. Modbus ..

                  LatziL 1 Reply Last reply
                  0
                  • GlasfaserG Glasfaser

                    @Latzi

                    Teste mal mit dem Skript von Mic , hast du dort eine Log Ausgabe ?

                    log('Error-Meldung', 'error');
                    
                    log('Warn-Meldung', 'warn');
                    
                    // Nochmal nach 2 Sekunden.
                    
                    setTimeout(function() {
                    
                        log('Error-Meldung', 'error');
                    
                        log('Warn-Meldung', 'warn');
                    
                    }, 2*1000);
                    
                    // Nach 30 Sekunden noch eine Warnmeldung.
                    
                    setTimeout(function() {
                    
                        log('Neue Warn-Meldung nach 30 Sekunden', 'warn');
                    
                    }, 30*1000);
                    
                    
                    LatziL Offline
                    LatziL Offline
                    Latzi
                    wrote on last edited by
                    #460

                    @Glasfaser ja, die Meldungen kommen. Liegt wohl an der Art und Weise wie rtspstream die Fehler generiert.
                    Danke dir für die Unterstützung!

                    GlasfaserG 1 Reply Last reply
                    0
                    • LatziL Latzi

                      @Glasfaser ja, die Meldungen kommen. Liegt wohl an der Art und Weise wie rtspstream die Fehler generiert.
                      Danke dir für die Unterstützung!

                      GlasfaserG Offline
                      GlasfaserG Offline
                      Glasfaser
                      wrote on last edited by Glasfaser
                      #461

                      @Latzi

                      Habe mal ein Fehler simuliert .... rtspstream wird im LOG angezeigt

                      1.JPG

                      Zeig doch mal deine Usereinstellung , für die ERROR Filterung.

                      Synology 918+ 16GB - ioBroker in Docker v9 , VISO auf Trekstor Primebook C13 13,3" , Hikvision Domkameras mit Surveillance Station .. CCU RaspberryMatic in Synology VM .. Zigbee CC2538+CC2592 .. Sonoff .. KNX .. Modbus ..

                      LatziL 1 Reply Last reply
                      0
                      • GlasfaserG Glasfaser

                        @Latzi

                        Habe mal ein Fehler simuliert .... rtspstream wird im LOG angezeigt

                        1.JPG

                        Zeig doch mal deine Usereinstellung , für die ERROR Filterung.

                        LatziL Offline
                        LatziL Offline
                        Latzi
                        wrote on last edited by
                        #462

                        @Glasfaser
                        mein Block error sieht folgendermaßen aus:

                         {
                            id:             'error',
                            filter_all:     [' - error:', ''],  // nur Logeinträge mit Level 'error'
                            filter_any:     [''],
                            blacklist:      ['', '', ''],
                            clean:          ['', '', ''],
                            merge:          true,
                            sortDescending: true,
                            jsonColumns:    ['date','level','source','msg'],
                            jsonDateFormat: 'dd.mm. hh:mm',
                            jsonLogLength:  200,
                            jsonMaxLines:   60,
                            jsonCssToLevel: true,
                          }
                        

                        Kannst du das bitte mal mit deinem Block vergleichen?

                        GlasfaserG 1 Reply Last reply
                        0
                        • LatziL Latzi

                          @Glasfaser
                          mein Block error sieht folgendermaßen aus:

                           {
                              id:             'error',
                              filter_all:     [' - error:', ''],  // nur Logeinträge mit Level 'error'
                              filter_any:     [''],
                              blacklist:      ['', '', ''],
                              clean:          ['', '', ''],
                              merge:          true,
                              sortDescending: true,
                              jsonColumns:    ['date','level','source','msg'],
                              jsonDateFormat: 'dd.mm. hh:mm',
                              jsonLogLength:  200,
                              jsonMaxLines:   60,
                              jsonCssToLevel: true,
                            }
                          

                          Kannst du das bitte mal mit deinem Block vergleichen?

                          GlasfaserG Offline
                          GlasfaserG Offline
                          Glasfaser
                          wrote on last edited by
                          #463

                          @Latzi sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                          Füge mal das ein :

                           {
                          
                              id:             'error',
                          
                              filter_all:     [' - error: ', ''],  // nur Logeinträge mit Level 'error'
                          
                              filter_any:     [''],
                          
                              blacklist:      ['', '', ''],
                          
                              clean:          ['', '', ''],
                          
                              merge:          true,
                          
                              sortDescending: true,
                          
                              jsonColumns:    ['date','level','source','msg'],
                          
                              jsonDateFormat: 'dd.mm. hh:mm',
                          
                              jsonLogLength:  200,
                          
                              jsonMaxLines:   60,
                          
                              jsonCssToLevel: true,
                          
                            }
                          

                          Synology 918+ 16GB - ioBroker in Docker v9 , VISO auf Trekstor Primebook C13 13,3" , Hikvision Domkameras mit Surveillance Station .. CCU RaspberryMatic in Synology VM .. Zigbee CC2538+CC2592 .. Sonoff .. KNX .. Modbus ..

                          LatziL 1 Reply Last reply
                          0
                          • GlasfaserG Glasfaser

                            @Latzi sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                            Füge mal das ein :

                             {
                            
                                id:             'error',
                            
                                filter_all:     [' - error: ', ''],  // nur Logeinträge mit Level 'error'
                            
                                filter_any:     [''],
                            
                                blacklist:      ['', '', ''],
                            
                                clean:          ['', '', ''],
                            
                                merge:          true,
                            
                                sortDescending: true,
                            
                                jsonColumns:    ['date','level','source','msg'],
                            
                                jsonDateFormat: 'dd.mm. hh:mm',
                            
                                jsonLogLength:  200,
                            
                                jsonMaxLines:   60,
                            
                                jsonCssToLevel: true,
                            
                              }
                            
                            LatziL Offline
                            LatziL Offline
                            Latzi
                            wrote on last edited by
                            #464

                            @Glasfaser ändert leider nichts 😞

                            GlasfaserG 1 Reply Last reply
                            0
                            • LatziL Latzi

                              @Glasfaser ändert leider nichts 😞

                              GlasfaserG Offline
                              GlasfaserG Offline
                              Glasfaser
                              wrote on last edited by
                              #465

                              @Latzi

                              Mhhh , habe gerade nochmal verglichen , dachte es hatte einfluss das nach dem - error: ein Leerzeichen fehlte ...

                              Kann du den Error im Datenpunkt log sehen ?

                              1.JPG

                              2.JPG

                              Synology 918+ 16GB - ioBroker in Docker v9 , VISO auf Trekstor Primebook C13 13,3" , Hikvision Domkameras mit Surveillance Station .. CCU RaspberryMatic in Synology VM .. Zigbee CC2538+CC2592 .. Sonoff .. KNX .. Modbus ..

                              LatziL 1 Reply Last reply
                              0
                              • GlasfaserG Glasfaser

                                @Latzi

                                Mhhh , habe gerade nochmal verglichen , dachte es hatte einfluss das nach dem - error: ein Leerzeichen fehlte ...

                                Kann du den Error im Datenpunkt log sehen ?

                                1.JPG

                                2.JPG

                                LatziL Offline
                                LatziL Offline
                                Latzi
                                wrote on last edited by
                                #466

                                @Glasfaser nein, ist darin nicht enthalten

                                GlasfaserG 1 Reply Last reply
                                0
                                • LatziL Latzi

                                  @Glasfaser nein, ist darin nicht enthalten

                                  GlasfaserG Offline
                                  GlasfaserG Offline
                                  Glasfaser
                                  wrote on last edited by
                                  #467

                                  @Latzi

                                  Da bin gerade überfragt ….. mehr kann ich dir im Moment leider nicht weiterhelfen , da ich gerade ein großes ? dazu habe …🤔

                                  Warte auf Mic

                                  Synology 918+ 16GB - ioBroker in Docker v9 , VISO auf Trekstor Primebook C13 13,3" , Hikvision Domkameras mit Surveillance Station .. CCU RaspberryMatic in Synology VM .. Zigbee CC2538+CC2592 .. Sonoff .. KNX .. Modbus ..

                                  LatziL 1 Reply Last reply
                                  0
                                  • GlasfaserG Glasfaser

                                    @Latzi

                                    Da bin gerade überfragt ….. mehr kann ich dir im Moment leider nicht weiterhelfen , da ich gerade ein großes ? dazu habe …🤔

                                    Warte auf Mic

                                    LatziL Offline
                                    LatziL Offline
                                    Latzi
                                    wrote on last edited by
                                    #468

                                    @Glasfaser vielen Dank für deine Mühe 👍 👍 👍

                                    1 Reply Last reply
                                    0
                                    • LatziL Latzi

                                      @Mic

                                      ich hab im Log folgende Fehlermeldungen:

                                      2020-02-14 20:06:16.407 - error: rtspStream.0 (14568) uncaught exception: i is not defined
                                      2020-02-14 20:06:16.408 - error: rtspStream.0 (14568) ReferenceError: i is not defined
                                      2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[43]: frame=95046 fps= 15 q=6.1 size= 315696kB time=01:45:32.56 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[44]: frame=95054 fps= 15 q=3.9 size= 315727kB time=01:45:33.06 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[45]: frame=95061 fps= 15 q=4.7 size= 315740kB time=01:45:33.64 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[46]: frame=95069 fps= 15 q=5.0 size= 315772kB time=01:45:34.07 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[47]: frame=95076 fps= 15 q=4.7 size= 315785kB time=01:45:34.65 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[48]: frame=95084 fps= 15 q=4.6 size= 315819kB time=01:45:35.15 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[49]: frame=95092 fps= 15 q=5.7 size= 315848kB time=01:45:35.66 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[50]: frame=95100 fps= 15 q=4.7 size= 315864kB time=01:45:36.16 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[51]: frame=95108 fps= 15 q=4.6 size= 315900kB time=01:45:36.66 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.453 - error: host.Pi4-Server Caught by controller[52]: frame=95115 fps= 15 q=5.0 size= 315932kB time=01:45:37.24 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[53]: frame=95123 fps= 15 q=4.2 size= 315949kB time=01:45:37.74 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[54]: frame=95131 fps= 15 q=4.7 size= 315975kB time=01:45:38.25 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.454 - error: host.Pi4-Server Caught by controller[55]: frame=95139 fps= 15 q=4.6 size= 316009kB time=01:45:38.75 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[56]: frame=95146 fps= 15 q=6.0 size= 316021kB time=01:45:39.26 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[57]: frame=95154 fps= 15 q=4.8 size= 316056kB time=01:45:39.76 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[58]: frame=95162 fps= 15 q=3.6 size= 316087kB time=01:45:40.26 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: ReferenceError: i is not defined
                                      2020-02-14 20:06:17.465 - error: host.Pi4-Server Caught by controller[59]: at WebSocketServer.wsServer.broadcast (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:103:54)
                                      2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:109:26)
                                      2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at VideoStream.emit (events.js:198:13)
                                      2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.mpeg1Muxer.on (/opt/iobroker/node_modules/node-rtsp-stream/videoStream.js:47:17)
                                      2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Mpeg1Muxer.emit (events.js:198:13)
                                      2020-02-14 20:06:17.466 - error: host.Pi4-Server Caught by controller[59]: at Socket.Mpeg1Muxer.stream.stdout.on (/opt/iobroker/node_modules/node-rtsp-stream/mpeg1muxer.js:39:17)
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.emit (events.js:198:13)
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at addChunk (_stream_readable.js:288:12)
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at readableAddChunk (_stream_readable.js:269:11)
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: at Socket.Readable.push (_stream_readable.js:224:10)
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[59]: frame=95169 fps= 15 q=4.4 size= 316101kB time=01:45:40.84 bitrate= 408.4kbits/s speed= 1x
                                      2020-02-14 20:06:17.467 - error: host.Pi4-Server Caught by controller[60]: frame=95177 fps= 15 q=5.3 size= 316135kB time=01:45:41.34 bitrate= 408.4kbits/s speed= 1x 
                                      

                                      Es wird mir im DP error aber nichts angezeigt. Ich habe auch nichts deratiges in der blacklist. Hast du eine Erklärung dafür?

                                      MicM Offline
                                      MicM Offline
                                      Mic
                                      Developer
                                      wrote on last edited by
                                      #469

                                      @Latzi sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                                      ich hab im Log folgende Fehlermeldungen:

                                      2020-02-14 20:06:16.407 - error: rtspStream.0 (14568) uncaught exception: i is not defined
                                      2020-02-14 20:06:16.408 - error: rtspStream.0 (14568) ReferenceError: i is not defined
                                      2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[43]: frame=95046 fps= 15 q=6.1 size= 315696kB time=01:45:32.56 bitrate= 408.4kbits/s speed= 1x
                                      (...)
                                      

                                      Es wird mir im DP error aber nichts angezeigt. Ich habe auch nichts deratiges in der blacklist. Hast du eine Erklärung dafür?

                                      Bitte ersetze mal folgende Zeile im Script:

                                      const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
                                      

                                      Durch:

                                      const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([^\\s]*)(\\s)(.*)';
                                      
                                      LatziL 1 Reply Last reply
                                      0
                                      • MicM Mic

                                        @Latzi sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                                        ich hab im Log folgende Fehlermeldungen:

                                        2020-02-14 20:06:16.407 - error: rtspStream.0 (14568) uncaught exception: i is not defined
                                        2020-02-14 20:06:16.408 - error: rtspStream.0 (14568) ReferenceError: i is not defined
                                        2020-02-14 20:06:17.452 - error: host.Pi4-Server Caught by controller[43]: frame=95046 fps= 15 q=6.1 size= 315696kB time=01:45:32.56 bitrate= 408.4kbits/s speed= 1x
                                        (...)
                                        

                                        Es wird mir im DP error aber nichts angezeigt. Ich habe auch nichts deratiges in der blacklist. Hast du eine Erklärung dafür?

                                        Bitte ersetze mal folgende Zeile im Script:

                                        const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([a-z0-9.\\-]*)(\\s)(.*)';
                                        

                                        Durch:

                                        const LOG_PATT =  '([0-9_.\\-:\\s]*)(\\s+\\- )(silly|debug|info|warn|error|)(: )([^\\s]*)(\\s)(.*)';
                                        
                                        LatziL Offline
                                        LatziL Offline
                                        Latzi
                                        wrote on last edited by
                                        #470

                                        @Mic sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                                        const LOG_PATT = '([0-9_.\-:\s])(\s+\- )(silly|debug|info|warn|error|)(: )([^\s])(\s)(.*)';

                                        @Mic Applaus, Applaus 👏 👏 👏
                                        Jetzt kommen auch die Meldungen vom rtspStream durch (es waren nicht nur die error-Meldungen, eigentlich waren alle Typen betroffen).
                                        Muss ich diese Zeile für spätere Versionen sichern oder baust du es allgemein im Script ein?

                                        MicM 1 Reply Last reply
                                        0
                                        • LatziL Latzi

                                          @Mic sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                                          const LOG_PATT = '([0-9_.\-:\s])(\s+\- )(silly|debug|info|warn|error|)(: )([^\s])(\s)(.*)';

                                          @Mic Applaus, Applaus 👏 👏 👏
                                          Jetzt kommen auch die Meldungen vom rtspStream durch (es waren nicht nur die error-Meldungen, eigentlich waren alle Typen betroffen).
                                          Muss ich diese Zeile für spätere Versionen sichern oder baust du es allgemein im Script ein?

                                          MicM Offline
                                          MicM Offline
                                          Mic
                                          Developer
                                          wrote on last edited by
                                          #471

                                          @Latzi
                                          Gern geschehen 🙂

                                          @Latzi sagte in [Vorlage] JS: Log-Datei aufbereiten für VIS:

                                          Muss ich diese Zeile für spätere Versionen sichern oder baust du es allgemein im Script ein?

                                          Baue ich ins Script ein, Update folgt sehr bald.

                                          1 Reply Last reply
                                          0
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          230

                                          Online

                                          32.4k

                                          Users

                                          81.4k

                                          Topics

                                          1.3m

                                          Posts
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Login

                                          • Don't have an account? Register

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Recent
                                          • Tags
                                          • Unread 0
                                          • Categories
                                          • Unreplied
                                          • Popular
                                          • GitHub
                                          • Docu
                                          • Hilfe