Node-RED Fehler loggen: Mit Bordmitteln und eigener Logger-Node
Im vorherigen Beitrag habe ich dir gezeigt, wie du Fehler und StatusĂ€nderungen in Node-RED mit einer Catch- beziehungsweise Status-Node erkennst und anschlieĂend ĂŒber eine Debug-Node ausgibst.
Doch was passiert, wenn ein Flow unbeaufsichtigt lÀuft und du einen Fehler erst Stunden oder Tage spÀter bemerkst? In diesem Fall reicht die Ausgabe im Debug-Bereich nicht aus. Damit sich die Ursache im Nachhinein nachvollziehen lÀsst, sollten wichtige Meldungen dauerhaft in einer Logdatei gespeichert werden.
https://youtu.be/U-w314Y3hc0
In diesem Beitrag zeige ich dir deshalb, wie du Fehler mit den Bordmitteln von Node-RED in eine Logdatei schreibst. AnschlieĂend stelle ich dir eine eigene Logger-Node vor, die dir viele wiederkehrende Arbeitsschritte abnimmt. Sie lĂ€sst sich flexibel konfigurieren und sorgt dafĂŒr, dass deine LogeintrĂ€ge einheitlich und ĂŒbersichtlich gespeichert werden.
Praxisbeispiel: AusfÀlle des MQTT-Brokers protokollieren
Als Beispiel verwenden wir erneut die Ăberwachung eines MQTT-Brokers. Dieser Anwendungsfall ist besonders praxisnah, da ein Broker in vielen IoT- und Smart-Home-Projekten eine zentrale Rolle ĂŒbernimmt.
Der Node-RED Flow erkennt StatusĂ€nderungen der MQTT-Out-Node und unterscheidet zwischen einer unterbrochenen und einer wiederhergestellten Verbindung. Node-RED Flow zum Importieren }, { "id": "7d1c53815cc56777", "type": "inject", "z": "8c105c2e4a45c10e", "name": "", "props": , "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 120, "y": 120, "wires": ] }, { "id": "2e69599beb37b62d", "type": "mqtt out", "z": "8c105c2e4a45c10e", "name": "", "topic": "/beispiel/text", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "2dc0a4b70d96ebb5", "x": 270, "y": 120, "wires": }, { "id": "7113958f4b37c1cb", "type": "status", "z": "8c105c2e4a45c10e", "name": "", "scope": , "x": 100, "y": 260, "wires": ] }, { "id": "5edda4976ee422cb", "type": "debug", "z": "8c105c2e4a45c10e", "name": "Ausgabe der StatusĂ€nderung", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 880, "y": 260, "wires": }, { "id": "99ee96e58880f9fd", "type": "function", "z": "8c105c2e4a45c10e", "name": "Statusmeldung fĂŒr "nicht verbunden" erstellen", "func": "msg.payload = "Die Verbindung zum MQTT Broker ist unterbrochen!";nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 540, "y": 220, "wires": ] }, { "id": "895156e15c61ffb6", "type": "switch", "z": "8c105c2e4a45c10e", "name": "", "property": "status.text", "propertyType": "msg", "rules": , "checkall": "false", "repair": false, "outputs": 2, "x": 250, "y": 260, "wires": , ] }, { "id": "03e0329660e4c26b", "type": "function", "z": "8c105c2e4a45c10e", "name": "Statusmeldung fĂŒr "verbunden" erstellen", "func": "msg.payload = "Die Verbindung zum MQTT Broker wurde hergestellt!";nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 540, "y": 300, "wires": ] }, { "id": "0ffb9997f320e647", "type": "debug", "z": "8c105c2e4a45c10e", "name": "debug 1", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 240, "y": 360, "wires": }, { "id": "2dc0a4b70d96ebb5", "type": "mqtt-broker", "name": "", "broker": "mosquitto", "port": 1883, "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": 4, "keepalive": 60, "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthRetain": "false", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closeRetain": "false", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willRetain": "false", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" } ]
FÀllt der MQTT-Broker aus, können angeschlossene GerÀte und Anwendungen keine Nachrichten mehr austauschen. LÀuft der Node-RED-Flow unbeaufsichtigt, bemerken wir die Unterbrechung möglicherweise erst deutlich spÀter. Eine dauerhaft gespeicherte Logdatei hilft uns dabei, nachzuvollziehen, wann die Verbindung unterbrochen und zu welchem Zeitpunkt sie wiederhergestellt wurde.
Der Ausgangspunkt ist der Flow aus dem vorherigen Beitrag. Eine Status-Node ĂŒberwacht den Verbindungszustand der MQTT-Out-Node. Die nachfolgende Switch-Node unterscheidet zwischen den beiden Statuswerten âverbundenâ und ânicht verbundenâ.
AbhÀngig vom erkannten Zustand erzeugen zwei Function-Nodes eine verstÀndliche Meldung:
Die Verbindung zum MQTT-Broker ist unterbrochen!
beziehungsweise:
Die Verbindung zum MQTT-Broker wurde hergestellt!
Bisher werden diese Meldungen lediglich ĂŒber eine Debug-Node ausgegeben. Im nĂ€chsten Schritt erweitern wir den Flow so, dass jede StatusĂ€nderung zusĂ€tzlich mit einem Zeitstempel in einer Logdatei gespeichert wird.
Damit können wir spÀter beispielsweise folgende Fragen beantworten:
- Wann ist der MQTT-Broker ausgefallen? - Wie lange war die Verbindung unterbrochen? - Wann konnte Node-RED die Verbindung wiederherstellen? - Tritt der Fehler regelmĂ€Ăig zu einer bestimmten Uhrzeit auf?
Gerade bei einem dauerhaft laufenden Node-RED-System ist diese Protokollierung deutlich hilfreicher als eine reine Ausgabe im Debug-Bereich.
MQTT-Status mit Node-RED-Bordmitteln protokollieren
FĂŒr die erste Lösung verwenden wir ausschlieĂlich Nodes, die bereits in Node-RED enthalten sind. ZusĂ€tzliche Erweiterungen mĂŒssen dafĂŒr nicht installiert werden.
Node-RED Flow zum Importieren }, { "id": "7d1c53815cc56777", "type": "inject", "z": "8c105c2e4a45c10e", "name": "", "props": , "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 120, "y": 40, "wires": ] }, { "id": "2e69599beb37b62d", "type": "mqtt out", "z": "8c105c2e4a45c10e", "name": "", "topic": "/beispiel/text", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "2dc0a4b70d96ebb5", "x": 270, "y": 40, "wires": }, { "id": "7113958f4b37c1cb", "type": "status", "z": "8c105c2e4a45c10e", "name": "", "scope": , "x": 100, "y": 140, "wires": ] }, { "id": "5edda4976ee422cb", "type": "debug", "z": "8c105c2e4a45c10e", "name": "Ausgabe der StatusĂ€nderung", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 1260, "y": 120, "wires": }, { "id": "99ee96e58880f9fd", "type": "function", "z": "8c105c2e4a45c10e", "name": "Statusmeldung fĂŒr "nicht verbunden" erstellen", "func": "msg.payload = "Die Verbindung zum MQTT Broker ist unterbrochen!";nmsg.logLevel = "ERROR";nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 540, "y": 100, "wires": ] }, { "id": "895156e15c61ffb6", "type": "switch", "z": "8c105c2e4a45c10e", "name": "", "property": "status.text", "propertyType": "msg", "rules": , "checkall": "false", "repair": false, "outputs": 3, "x": 250, "y": 140, "wires": , , ] }, { "id": "03e0329660e4c26b", "type": "function", "z": "8c105c2e4a45c10e", "name": "Statusmeldung fĂŒr "verbunden" erstellen", "func": "msg.payload = "Die Verbindung zum MQTT Broker wurde hergestellt!";nmsg.logLevel = "INFO";nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 540, "y": 220, "wires": ] }, { "id": "ae0e3cd59331728d", "type": "function", "z": "8c105c2e4a45c10e", "name": "LogMessage erstellen", "func": "function createGermanTimestamp() {n const now = new Date();nn const day = String(now.getDate()).padStart(2, "0");n const month = String(now.getMonth() + 1).padStart(2, "0");n const year = now.getFullYear();nn const hours = String(now.getHours()).padStart(2, "0");n const minutes = String(now.getMinutes()).padStart(2, "0");n const seconds = String(now.getSeconds()).padStart(2, "0");n const milliseconds = String(now.getMilliseconds()).padStart(3, "0");nn return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}.${milliseconds}`;n}nnlet timestamp = createGermanTimestamp();nmsg.payload = timestamp + " " + msg.payload;nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 980, "y": 160, "wires": ] }, { "id": "e42770381c324907", "type": "file", "z": "8c105c2e4a45c10e", "name": "", "filename": "/data/sample.log", "filenameType": "str", "appendNewline": true, "createDir": false, "overwriteFile": "false", "encoding": "none", "x": 1220, "y": 180, "wires": ] }, { "id": "da8de8fd48fad8c1", "type": "function", "z": "8c105c2e4a45c10e", "name": "Statusmeldung fĂŒr "Verbindung wird hergestellt" erstellen", "func": "msg.payload = "Die Verbindung zum MQTT Broker wird hergestellt!";nmsg.logLevel = "INFO";nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": , "x": 590, "y": 160, "wires": ] }, { "id": "2dc0a4b70d96ebb5", "type": "mqtt-broker", "name": "", "broker": "mosquitto", "port": 1883, "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": 4, "keepalive": 60, "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthRetain": "false", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closeRetain": "false", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willRetain": "false", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" } ]
Die Status-Node ĂŒberwacht den Verbindungszustand der MQTT-Out-Node. Sobald sich der Status Ă€ndert, wird eine Nachricht an die Switch-Node ĂŒbergeben. Diese unterscheidet in unserem Beispiel zwischen drei möglichen ZustĂ€nden:
- Verbindung unterbrochen - Verbindung wird hergestellt - Verbindung hergestellt
FĂŒr jeden Zustand wird eine eigene Function-Node verwendet. Dort erzeugen wir eine verstĂ€ndliche Meldung und ergĂ€nzen zusĂ€tzlich ein Log-Level.
Bei einer unterbrochenen Verbindung verwenden wir das Log-Level ERROR:
msg.payload = "Die Verbindung zum MQTT-Broker ist unterbrochen!"; msg.logLevel = "ERROR"; return msg;
WĂ€hrend Node-RED versucht, die Verbindung erneut herzustellen, verwenden wir das Log-Level INFO:
msg.payload = "Die Verbindung zum MQTT-Broker wird hergestellt!"; msg.logLevel = "INFO"; return msg;
Auch die erfolgreich wiederhergestellte Verbindung wird als Information protokolliert:
msg.payload = "Die Verbindung zum MQTT-Broker wurde hergestellt!"; msg.logLevel = "INFO"; return msg;
Alle drei Nachrichten werden anschlieĂend an dieselbe Function-Node weitergeleitet. Diese ergĂ€nzt einen Zeitstempel mit Millisekunden und erzeugt daraus einen einheitlichen Logeintrag.
function createGermanTimestamp() { const now = new Date(); const day = String(now.getDate()).padStart(2, "0"); const month = String(now.getMonth() + 1).padStart(2, "0"); const year = now.getFullYear(); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); const milliseconds = String(now.getMilliseconds()).padStart(3, "0"); return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}.${milliseconds}`; } const timestamp = createGermanTimestamp(); msg.payload = `${timestamp} ${msg.payload}`; return msg;
Die fertige Meldung wird anschlieĂend gleichzeitig an eine Debug-Node und eine File-Node ĂŒbergeben. Die Debug-Node hilft uns wĂ€hrend der Entwicklung, wĂ€hrend die File-Node den Eintrag dauerhaft in der Datei /data/sample.log speichert.
In der Write-File-Node geben wir den vollstÀndigen Pfad an, unter dem die Logdatei gespeichert werden soll. Dieser setzt sich aus dem Zielverzeichnis und dem Dateinamen zusammen.
Da ich Node-RED in einem Docker-Container betreibe, verwende ich das Verzeichnis /data/. Als Dateinamen wĂ€hle ich fĂŒr dieses Beispiel zunĂ€chst sample.log.
Der vollstÀndige Pfad lautet damit: /data/sample.log
Das Verzeichnis /data/ wird bei einer typischen Node-RED-Docker-Installation fĂŒr persistente Daten verwendet. Voraussetzung ist, dass es als Volume eingebunden wurde. Dadurch bleibt die Logdatei auch erhalten, wenn der Container neu gestartet oder neu erstellt wird.
Die Option zum AnhĂ€ngen an eine bestehende Datei muss aktiviert sein. Andernfalls wĂŒrde die Logdatei bei jeder neuen Meldung ĂŒberschrieben werden. AuĂerdem sollte nach jedem Eintrag automatisch ein Zeilenumbruch ergĂ€nzt werden.
Logdateien automatisch nach Datum aufteilen
Bisher werden alle Meldungen in dieselbe Datei geschrieben: /data/sample.log
Bei einem dauerhaft laufenden Node-RED-System kann diese Datei mit der Zeit jedoch sehr groĂ und unĂŒbersichtlich werden. AuĂerdem ist es schwieriger, gezielt nach Ereignissen eines bestimmten Tages zu suchen.
Deshalb erweitern wir den Flow so, dass fĂŒr jeden Tag automatisch eine eigene Logdatei verwendet wird.
















