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.
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.

Node-RED Flow zum Importieren
[
{
"id": "8c105c2e4a45c10e",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "7d1c53815cc56777",
"type": "inject",
"z": "8c105c2e4a45c10e",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 120,
"wires": [
[
"2e69599beb37b62d"
]
]
},
{
"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": [
"2e69599beb37b62d"
],
"x": 100,
"y": 260,
"wires": [
[
"895156e15c61ffb6",
"0ffb9997f320e647"
]
]
},
{
"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": [
[
"5edda4976ee422cb"
]
]
},
{
"id": "895156e15c61ffb6",
"type": "switch",
"z": "8c105c2e4a45c10e",
"name": "",
"property": "status.text",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "node-red:common.status.disconnected",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connected",
"vt": "str"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 250,
"y": 260,
"wires": [
[
"99ee96e58880f9fd"
],
[
"03e0329660e4c26b"
]
]
},
{
"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": [
[
"5edda4976ee422cb"
]
]
},
{
"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": "8c105c2e4a45c10e",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "7d1c53815cc56777",
"type": "inject",
"z": "8c105c2e4a45c10e",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 40,
"wires": [
[
"2e69599beb37b62d"
]
]
},
{
"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": [
"2e69599beb37b62d"
],
"x": 100,
"y": 140,
"wires": [
[
"895156e15c61ffb6"
]
]
},
{
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "895156e15c61ffb6",
"type": "switch",
"z": "8c105c2e4a45c10e",
"name": "",
"property": "status.text",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "node-red:common.status.disconnected",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connecting",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connected",
"vt": "str"
}
],
"checkall": "false",
"repair": false,
"outputs": 3,
"x": 250,
"y": 140,
"wires": [
[
"99ee96e58880f9fd"
],
[
"da8de8fd48fad8c1"
],
[
"03e0329660e4c26b"
]
]
},
{
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "ae0e3cd59331728d",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "LogMessage erstellen",
"func": "function createGermanTimestamp() {\n const now = new Date();\n\n const day = String(now.getDate()).padStart(2, \"0\");\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n const year = now.getFullYear();\n\n 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\");\n\n return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}.${milliseconds}`;\n}\n\nlet timestamp = createGermanTimestamp();\nmsg.payload = timestamp + \" [\" + msg.logLevel + \"] \" + msg.payload;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 980,
"y": 160,
"wires": [
[
"5edda4976ee422cb",
"e42770381c324907"
]
]
},
{
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"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.logLevel}] ${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. Eine neue Datei wird dabei nur dann angelegt, wenn an diesem Tag tatsächlich mindestens ein Logeintrag entsteht.
Node-RED Flow zum Importieren
[
{
"id": "8c105c2e4a45c10e",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "7d1c53815cc56777",
"type": "inject",
"z": "8c105c2e4a45c10e",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 40,
"wires": [
[
"2e69599beb37b62d"
]
]
},
{
"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": [
"2e69599beb37b62d"
],
"x": 100,
"y": 140,
"wires": [
[
"895156e15c61ffb6"
]
]
},
{
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "895156e15c61ffb6",
"type": "switch",
"z": "8c105c2e4a45c10e",
"name": "",
"property": "status.text",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "node-red:common.status.disconnected",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connecting",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connected",
"vt": "str"
}
],
"checkall": "false",
"repair": false,
"outputs": 3,
"x": 250,
"y": 140,
"wires": [
[
"99ee96e58880f9fd"
],
[
"da8de8fd48fad8c1"
],
[
"03e0329660e4c26b"
]
]
},
{
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "ae0e3cd59331728d",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "LogMessage erstellen",
"func": "function createGermanTimestamp() {\n const now = new Date();\n\n const day = String(now.getDate()).padStart(2, \"0\");\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n const year = now.getFullYear();\n\n 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\");\n\n return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}.${milliseconds}`;\n}\n\nconst timestamp = createGermanTimestamp();\n\nmsg.logMessage = `${timestamp} [${msg.logLevel}] ${msg.payload}`;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 980,
"y": 160,
"wires": [
[
"5edda4976ee422cb",
"410148176f491ba1"
]
]
},
{
"id": "e42770381c324907",
"type": "file",
"z": "8c105c2e4a45c10e",
"name": "",
"filename": "filename",
"filenameType": "msg",
"appendNewline": true,
"createDir": true,
"overwriteFile": "false",
"encoding": "none",
"x": 1660,
"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": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "410148176f491ba1",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "LogMessage to Payoad",
"func": "msg.payload = msg.logMessage;\ndelete msg.logMessage;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1250,
"y": 180,
"wires": [
[
"17a16dfce086f3c2"
]
]
},
{
"id": "17a16dfce086f3c2",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "Dateinamen erstellen",
"func": "const now = new Date();\n\nconst year = now.getFullYear();\nconst month = String(now.getMonth() + 1).padStart(2, \"0\");\nconst day = String(now.getDate()).padStart(2, \"0\");\n\nmsg.filename = `/data/logs/sample-${year}-${month}-${day}.log`;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1480,
"y": 180,
"wires": [
[
"e42770381c324907"
]
]
},
{
"id": "fd32b1d5d1c7e01b",
"type": "catch",
"z": "8c105c2e4a45c10e",
"name": "",
"scope": null,
"uncaught": false,
"x": 140,
"y": 300,
"wires": [
[
"253582447c85d726"
]
]
},
{
"id": "253582447c85d726",
"type": "debug",
"z": "8c105c2e4a45c10e",
"name": "debug 2",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 280,
"y": 300,
"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": ""
}
]
Beispielsweise können so folgende Dateien entstehen:
sample-2026-06-17.log sample-2026-06-18.log sample-2026-06-19.log
Dafür erzeugen wir den Dateinamen dynamisch in der Function-Node. Neben dem eigentlichen Logeintrag setzen wir zusätzlich die Eigenschaft msg.filename.
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
msg.filename = `/data/logs/sample-${year}-${month}-${day}.log`;
return msg;
Die Eigenschaft msg.filename enthält damit bei jeder Nachricht den vollständigen Pfad der aktuell benötigten Logdatei.
In der Write-File-Node wird anschließend kein fest eingetragener Dateiname mehr verwendet. Stattdessen wählen wir als Quelle: msg.filename
Beim ersten Logeintrag eines neuen Tages verweist msg.filename automatisch auf einen neuen Dateinamen. Existiert diese Datei noch nicht, wird sie von der Write-File-Node angelegt. Weitere Meldungen desselben Tages werden an diese Datei angehängt.
Damit haben wir mit den Bordmitteln von Node-RED eine einfache tägliche Log-Rotation umgesetzt.
Streng genommen handelt es sich zunächst um eine datumsbasierte Aufteilung der Logdateien. Alte Dateien werden noch nicht automatisch gelöscht oder komprimiert. Für viele kleinere Node-RED-Projekte reicht diese einfache Lösung jedoch bereits aus.
Logging zentral auslagern oder eigene Logger-Node verwenden
Die bisherige Lösung funktioniert zuverlässig und kommt vollständig mit den Bordmitteln von Node-RED aus. Allerdings besteht der Flow inzwischen aus mehreren Function-Nodes, einer Write-File-Node und zusätzlicher Logik für Zeitstempel, Log-Level und Dateinamen.
Für einen einzelnen Flow ist dieser Aufbau noch überschaubar. Sobald jedoch mehrere Flows Meldungen protokollieren sollen, müssten dieselben Schritte immer wieder nachgebaut oder kopiert werden:
- Log-Level festlegen
- Zeitstempel erzeugen
- Meldung einheitlich formatieren
- Dateinamen aus dem aktuellen Datum erstellen
- Zielverzeichnis festlegen
- Logeintrag an eine Datei anhängen
Das führt schnell zu doppeltem Code und macht spätere Änderungen unnötig aufwendig.
Eine Möglichkeit besteht darin, die komplette Logging-Logik mit einer Link-Out-Node auf einen separaten Tab auszulagern.
Node-RED Flow zum Importieren
[
{
"id": "8c105c2e4a45c10e",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "7d1c53815cc56777",
"type": "inject",
"z": "8c105c2e4a45c10e",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 40,
"wires": [
[
"2e69599beb37b62d"
]
]
},
{
"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": [
"2e69599beb37b62d"
],
"x": 100,
"y": 140,
"wires": [
[
"895156e15c61ffb6"
]
]
},
{
"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": 500,
"y": 340,
"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": [
[
"115c9585ed755bc6"
]
]
},
{
"id": "895156e15c61ffb6",
"type": "switch",
"z": "8c105c2e4a45c10e",
"name": "",
"property": "status.text",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "node-red:common.status.disconnected",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connecting",
"vt": "str"
},
{
"t": "eq",
"v": "node-red:common.status.connected",
"vt": "str"
}
],
"checkall": "false",
"repair": false,
"outputs": 3,
"x": 250,
"y": 140,
"wires": [
[
"99ee96e58880f9fd"
],
[
"da8de8fd48fad8c1"
],
[
"03e0329660e4c26b"
]
]
},
{
"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": [
[
"115c9585ed755bc6"
]
]
},
{
"id": "ae0e3cd59331728d",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "LogMessage erstellen",
"func": "function createGermanTimestamp() {\n const now = new Date();\n\n const day = String(now.getDate()).padStart(2, \"0\");\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n const year = now.getFullYear();\n\n 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\");\n\n return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}.${milliseconds}`;\n}\n\nconst timestamp = createGermanTimestamp();\n\nmsg.logMessage = `${timestamp} [${msg.logLevel}] ${msg.payload}`;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 220,
"y": 380,
"wires": [
[
"5edda4976ee422cb",
"410148176f491ba1"
]
]
},
{
"id": "e42770381c324907",
"type": "file",
"z": "8c105c2e4a45c10e",
"name": "",
"filename": "filename",
"filenameType": "msg",
"appendNewline": true,
"createDir": true,
"overwriteFile": "false",
"encoding": "none",
"x": 900,
"y": 400,
"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": [
[
"115c9585ed755bc6"
]
]
},
{
"id": "410148176f491ba1",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "LogMessage to Payoad",
"func": "msg.payload = msg.logMessage;\ndelete msg.logMessage;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 400,
"wires": [
[
"17a16dfce086f3c2"
]
]
},
{
"id": "17a16dfce086f3c2",
"type": "function",
"z": "8c105c2e4a45c10e",
"name": "Dateinamen erstellen",
"func": "const now = new Date();\n\nconst year = now.getFullYear();\nconst month = String(now.getMonth() + 1).padStart(2, \"0\");\nconst day = String(now.getDate()).padStart(2, \"0\");\n\nmsg.filename = `/data/logs/sample-${year}-${month}-${day}.log`;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 400,
"wires": [
[
"e42770381c324907"
]
]
},
{
"id": "dd5643ec406fd4d8",
"type": "link in",
"z": "8c105c2e4a45c10e",
"name": "LogMessage schreiben",
"links": [],
"x": 65,
"y": 380,
"wires": [
[
"ae0e3cd59331728d"
]
]
},
{
"id": "115c9585ed755bc6",
"type": "link call",
"z": "8c105c2e4a45c10e",
"name": "",
"links": [
"dd5643ec406fd4d8"
],
"linkType": "static",
"timeout": "30",
"x": 1010,
"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": ""
}
]
Alle Flows, die eine Meldung protokollieren möchten, können ihre Nachricht an dieselbe zentrale Logging-Strecke übergeben.
Dazu sollte die Nachricht mindestens die eigentliche Meldung und das gewünschte Log-Level enthalten:
msg.payload = "Die Verbindung zum MQTT-Broker ist unterbrochen!"; msg.logLevel = "ERROR"; return msg;
Diese Lösung hat mehrere Vorteile:
- Die Logging-Logik wird nur einmal gepflegt.
- Änderungen am Dateinamen oder Logformat erfolgen zentral.
- Mehrere Flows können dieselbe Protokollierung verwenden.
- Die eigentlichen Anwendungsflows bleiben übersichtlich.
- Es werden keine zusätzlichen Nodes benötigt.
Für viele Projekte ist diese Lösung bereits vollkommen ausreichend.
Eine eigene Logger-Node geht jedoch noch einen Schritt weiter. Sie bündelt Zeitstempel, Dateiname, Zielverzeichnis, Log-Level und das Schreiben der Datei in einer einzigen Node mit eigener Konfigurationsoberfläche.
Damit wird aus mehreren einzelnen Nodes eine wiederverwendbare Komponente, die sich direkt aus der Node-RED-Palette in einen Flow ziehen lässt.
Die eigene Logger-Node ist also nicht zwingend notwendig. Sie sorgt vor allem für mehr Komfort, eine einheitliche Konfiguration und eine einfachere Wiederverwendbarkeit über mehrere Projekte hinweg.
Eigene Logger-Node für Node-RED
Mit den bisherigen Bordmitteln können wir bereits eine funktionierende und zentrale Logging-Lösung aufbauen. Über Link-Out- und Link-In-Nodes lässt sich die komplette Verarbeitung auf einen separaten Tab auslagern und von mehreren Flows gemeinsam verwenden.
Diese Lösung funktioniert gut, besteht intern aber weiterhin aus mehreren einzelnen Nodes:
Link In → Zeitstempel erzeugen → Logeintrag formatieren → Dateinamen erstellen → Write File
Möchte man diese Funktion in einer anderen Node-RED-Installation oder in einem neuen Projekt verwenden, muss der komplette Logging-Flow erneut importiert werden.
Aus diesem Grund habe ich die wiederkehrenden Funktionen in einer eigenen Node-RED-Node zusammengefasst:
node-red-contrib-draeger-file-logger
Das erste offizielle Release trägt die Versionsnummer 1.1.0 und steht auf GitHub zur Verfügung: https://github.com/StefanDraeger/node-red-contrib-draeger-file-logger
Die Logger-Node übernimmt die Formatierung, Validierung und Speicherung der Logmeldung. Dadurch kann die bisherige Logging-Strecke auf eine einzelne Node reduziert werden.

Welche Daten erwartet die Logger-Node?
Die Logger-Node erwartet in msg.payload ein JSON-Objekt mit den beiden Eigenschaften message und level.
{
"message": "Die Verbindung zum MQTT-Broker wurde hergestellt!",
"level": "INFO"
}
Die Eigenschaft message enthält den eigentlichen Text der Logmeldung.
Über level wird festgelegt, um welche Art von Meldung es sich handelt. Unterstützt werden aktuell folgende Log-Level:
| Log-Level | Verwendung |
|---|---|
INFO | allgemeine Informationen und Statusmeldungen |
WARN | Warnungen, bei denen der Flow grundsätzlich weiterarbeiten kann |
ERROR | Fehler, die später geprüft werden sollten |
Eine Function-Node vor dem Logger kann beispielsweise folgende Nachricht erzeugen:
msg.payload = {
message: "Die Verbindung zum MQTT-Broker ist unterbrochen!",
level: "ERROR"
};
return msg;
Die Logger-Node erzeugt daraus automatisch einen vollständigen Eintrag:
18.06.2026 10:15:42.317 [ERROR] Die Verbindung zum MQTT-Broker ist unterbrochen!
Damit müssen Zeitstempel, Log-Level und Meldung nicht mehr manuell in einer zusätzlichen Function-Node zusammengesetzt werden.
Einstellungen der Logger-Node
Die Logger-Node besitzt eine eigene Konfigurationsoberfläche. Darüber lassen sich die wichtigsten Einstellungen direkt innerhalb der Node vornehmen.




Zu den verfügbaren Einstellungen gehören:
- Name der Node
- Format des Zeitstempels
- verwendete Zeitzone
- fester Dateipfad oder dynamischer Pfad
- automatisches Anlegen fehlender Verzeichnisse
- tägliche Aufteilung der Logdateien
- optionale Debug-Ausgabe
Dadurch lässt sich die Node ohne Änderungen am JavaScript-Code für unterschiedliche Projekte verwenden.
Format des Zeitstempels auswählen
Das gewünschte Zeitformat wird über eine Auswahlliste festgelegt.
Zur Verfügung stehen beispielsweise:
| Format | Beispiel |
|---|---|
| deutsches Format mit Millisekunden | 18.06.2026 10:15:42.317 |
| deutsches Format ohne Millisekunden | 18.06.2026 10:15:42 |
| ISO-ähnliches Format | 2026-06-18T10:15:42.317 |
| Unix-Zeit in Millisekunden | 1781770542317 |
Für meine Logdateien verwende ich das deutsche Format mit Millisekunden. Dadurch lassen sich auch Statusänderungen unterscheiden, die innerhalb derselben Sekunde auftreten.
Zeitzone festlegen
Zusätzlich zum Zeitformat kann die Zeitzone ausgewählt werden.
Zur Auswahl stehen unter anderem:
- Systemzeitzone
Europe/BerlinUTCEurope/LondonAmerica/New_YorkAmerica/Los_AngelesAsia/Tokyo
Alternativ kann eine eigene IANA-Zeitzone eingetragen werden.
Das ist besonders bei Docker-Installationen hilfreich. Die Zeitzone des Containers muss nicht zwangsläufig mit der Zeitzone des Hostsystems übereinstimmen. Durch die explizite Auswahl kann die Logger-Node trotzdem den gewünschten lokalen Zeitstempel erzeugen.
Die gewählte Zeitzone wird auch für das Datum im Namen einer täglichen Logdatei verwendet.
Dateipfad fest konfigurieren
Der einfachste Weg ist ein fester Dateipfad innerhalb der Node.
Beispiel:
/data/logs/mqtt-status.log
Alle eingehenden Meldungen werden anschließend in diese Datei geschrieben.
Diese Variante eignet sich, wenn eine Logger-Node dauerhaft für einen bestimmten Anwendungsfall verwendet wird.
Dateipfad dynamisch übergeben
Alternativ kann der Dateipfad über msg.filename festgelegt werden.
Dazu wird in der Logger-Node die entsprechende Option ausgewählt. Der vollständige Pfad muss anschließend mit jeder Nachricht übergeben werden:
msg.filename = "/data/logs/mqtt-status.log";
msg.payload = {
message: "Die Verbindung wurde hergestellt.",
level: "INFO"
};
return msg;
Damit kann dieselbe Logger-Node abhängig von der eingehenden Nachricht in unterschiedliche Dateien schreiben.
Beispielsweise könnten Meldungen auf mehrere Dateien verteilt werden:
/data/logs/mqtt.log /data/logs/shelly.log /data/logs/system.log
Der dynamische Dateipfad bietet mehr Flexibilität. Gleichzeitig muss sichergestellt werden, dass msg.filename vor jedem Aufruf gesetzt wurde.
Fehlende Verzeichnisse automatisch erstellen
Über eine Checkbox kann festgelegt werden, ob die Logger-Node fehlende Verzeichnisse automatisch anlegen soll.
Ist beispielsweise folgender Pfad konfiguriert:
/data/logs/mqtt/status.log
und das Verzeichnis /data/logs/mqtt/ existiert noch nicht, kann es von der Node automatisch erstellt werden.
Voraussetzung ist, dass der Benutzer, unter dem Node-RED läuft, die notwendigen Schreibrechte besitzt.
Kann das Verzeichnis nicht erstellt oder die Datei nicht geschrieben werden, erzeugt die Logger-Node einen Fehler. Dieser lässt sich wiederum mit einer Catch-Node überwachen.
Täglich eine neue Logdatei verwenden
Über die Option für tägliche Logdateien wird das aktuelle Datum automatisch an den Dateinamen angehängt.
Aus /data/logs/mqtt-status.log wird beispielsweise /data/logs/mqtt-status-2026-06-18.log. Am nächsten Tag verwendet die Node automatisch /data/logs/mqtt-status-2026-06-19.log.
Eine neue Datei wird nur dann angelegt, wenn an diesem Tag tatsächlich eine Logmeldung eintrifft. Für Tage ohne Ereignisse entstehen keine leeren Dateien.
Damit ist die zuvor manuell aufgebaute datumsbasierte Log-Rotation bereits direkt in der Logger-Node enthalten.
Wichtig ist jedoch: Alte Dateien werden aktuell nicht automatisch gelöscht oder komprimiert. Die Node teilt die Meldungen lediglich nach Tagen auf.
Debug-Ausgabe aktivieren
Während der Entwicklung kann es hilfreich sein, erfolgreiche Logeinträge zusätzlich im Debug-Bereich von Node-RED anzuzeigen.
Dafür besitzt die Logger-Node eine eigene Checkbox.
Ist sie aktiviert, erscheint nach dem erfolgreichen Schreibvorgang eine Meldung mit der erzeugten Logzeile und dem tatsächlich verwendeten Dateipfad.
Beispiel:
{
"loggerDebug": true,
"logLine": "18.06.2026 10:15:42.317 [INFO] Die Verbindung wurde hergestellt.",
"logFile": "/data/logs/mqtt-status-2026-06-18.log"
}

Zusätzlich kann die Debug-Ausgabe für jede einzelne Nachricht über msg.loggerDebug gesteuert werden.
Debug-Ausgabe aktivieren:
msg.loggerDebug = true;
Debug-Ausgabe deaktivieren:
msg.loggerDebug = false;
Der Wert aus msg.loggerDebug hat Vorrang vor der Checkbox in der Konfiguration.
| Checkbox | msg.loggerDebug | Verhalten |
|---|---|---|
| deaktiviert | nicht gesetzt | keine Debug-Ausgabe |
| aktiviert | nicht gesetzt | Debug-Ausgabe |
| deaktiviert | true | Debug-Ausgabe |
| aktiviert | false | keine Debug-Ausgabe |
Dadurch kann die Debug-Ausgabe beispielsweise normalerweise deaktiviert bleiben und nur bei bestimmten Fehlern oder während einer Testphase eingeschaltet werden.
Ausgabe am Ausgang der Logger-Node
Nach dem erfolgreichen Schreiben gibt die Logger-Node die ursprüngliche Nachricht über ihren Ausgang weiter.
Zusätzlich werden zwei neue Eigenschaften ergänzt:
msg.logLine msg.logFile
msg.logLine enthält den vollständig formatierten Logeintrag:
18.06.2026 10:15:42.317 [INFO] Die Verbindung wurde hergestellt.

msg.logFile enthält den tatsächlich verwendeten Dateipfad:
/data/logs/mqtt-status-2026-06-18.log
Damit kann der Flow nach dem Speichern weiterarbeiten. So wäre es beispielsweise möglich, den Pfad der Datei anzuzeigen, eine Statistik zu erhöhen oder eine zusätzliche Benachrichtigung auszulösen.
Fehlerhafte Eingaben erkennen
Die Logger-Node überprüft die eingehenden Daten, bevor ein Eintrag geschrieben wird.
Ein Fehler wird unter anderem erzeugt, wenn:
msg.payloadkein Objekt ist,messagefehlt oder leer ist,- ein ungültiges Log-Level übergeben wird,
- der konfigurierte Dateipfad fehlt,
msg.filenamenicht gesetzt wurde,- eine ungültige Zeitzone verwendet wird,
- das Verzeichnis nicht erstellt werden kann,
- oder die Datei nicht geschrieben werden kann.
Reihenfolge der Logeinträge
Schnell aufeinanderfolgende Ereignisse können innerhalb weniger Millisekunden auftreten. Bei der MQTT-Verbindung kann beispielsweise zuerst der Status „Verbindung wird hergestellt“ und direkt danach „Verbindung hergestellt“ gemeldet werden.
Die Logger-Node verarbeitet ihre Schreibvorgänge deshalb innerhalb einer Node-Instanz nacheinander.
Dadurch bleibt die Reihenfolge der eingehenden Meldungen erhalten.
Werden mehrere verschiedene Logger-Nodes verwendet, die gleichzeitig in dieselbe Datei schreiben, besitzt jedoch jede Instanz eine eigene Warteschlange. In diesem Fall kann die Reihenfolge zwischen den einzelnen Instanzen nicht garantiert werden.
Für ein zentrales Logging empfiehlt es sich daher, eine gemeinsame Logger-Node zu verwenden und die Nachrichten beispielsweise über Link-Out- und Link-In-Nodes an diese weiterzuleiten.
Installation der Logger-Node
Die Logger-Node steht aktuell über mein GitHub-Repository zur Verfügung: https://github.com/StefanDraeger/node-red-contrib-draeger-file-logger
Das Repository kann zunächst geklont werden:
git clone https://github.com/StefanDraeger/node-red-contrib-draeger-file-logger.git
Anschließend wird die Node im Benutzerverzeichnis von Node-RED installiert:
cd ~/.node-red npm install /vollständiger/pfad/node-red-contrib-draeger-file-logger
Nach der Installation muss Node-RED neu gestartet werden.
Bei einer Docker-Installation befindet sich das persistente Node-RED-Verzeichnis häufig unter /data. Die genaue Installation hängt dabei vom verwendeten Docker-Setup und den eingebundenen Volumes ab.
Wichtig ist außerdem, dass Node-RED Schreibrechte für das konfigurierte Logverzeichnis besitzt.
Bordmittel, Link-Nodes oder eigene Logger-Node?
Alle drei gezeigten Lösungen haben ihre Berechtigung.
| Lösung | Geeignet für |
|---|---|
| Function- und Write-File-Nodes | einzelne, kleine Logging-Aufgaben |
| zentrale Logging-Strecke mit Link-Nodes | mehrere Flows innerhalb derselben Node-RED-Installation |
| eigene Logger-Node | wiederverwendbares und komfortabel konfigurierbares Logging |
Die eigene Logger-Node ist nicht zwingend notwendig. Alle Grundfunktionen lassen sich auch mit den vorhandenen Node-RED-Nodes umsetzen.
Ihr Vorteil liegt vor allem darin, dass die wiederkehrende Logik in einer einzigen Node gebündelt wird.
Vorteile der eigenen Logger-Node
- deutlich kompaktere Flows
- zentrale und einheitliche Formatierung der Logeinträge
- kein wiederholter Code für Zeitstempel und Dateinamen
- auswählbares Zeitformat
- konfigurierbare Zeitzone
- fester oder dynamischer Dateipfad
- fehlende Verzeichnisse können automatisch erstellt werden
- tägliche Logdateien ohne zusätzliche Function-Node
- optionale Debug-Ausgabe
- dynamische Steuerung über
msg.loggerDebug - Validierung der eingehenden Daten
- Fehler können mit einer Catch-Node verarbeitet werden
- Weitergabe von
msg.logLineundmsg.logFile - Wiederverwendung in mehreren Projekten möglich
Nachteile und Einschränkungen
- zusätzliche Node muss installiert und gepflegt werden
- Abhängigkeit von einem eigenen Community-Paket
- bei Updates muss die neue Version erneut installiert werden
- alte Logdateien werden noch nicht automatisch gelöscht
- keine automatische Komprimierung
- keine Rotation anhand der Dateigröße
- aktuell nur die Log-Level
INFO,WARNundERROR - keine direkte Ausgabe an Syslog, MQTT oder eine Datenbank
- mehrere Logger-Instanzen sollten nicht gleichzeitig dieselbe Datei beschreiben
- Schreibrechte des Zielverzeichnisses müssen korrekt eingerichtet sein
Mein Fazit zur Logger-Node
Für einen einzelnen kleinen Flow reicht die Lösung aus Function- und Write-File-Node vollkommen aus.
Sobald jedoch mehrere Flows protokolliert werden sollen, lohnt sich eine zentrale Logging-Strecke. Diese kann bereits mit Link-Nodes sehr sauber umgesetzt werden.
Die eigene Logger-Node erhöht vor allem den Komfort. Sie reduziert die Zahl der benötigten Nodes, sorgt für ein einheitliches Format und bündelt alle wichtigen Einstellungen in einer Konfigurationsoberfläche.
Da der vollständige Quellcode auf GitHub verfügbar ist, kann die Node nachvollzogen, erweitert oder an eigene Anforderungen angepasst werden. Fehlerberichte und Verbesserungsvorschläge können direkt über das GitHub-Repository eingereicht werden.
Letzte Aktualisierung am: 21. Juni 2026






