Skip to content

Technik Blog

Programmieren | Arduino | ESP32 | MicroPython | Python | Raspberry Pi | Raspberry Pi Pico

Menu
  • Smarthome
  • Gartenautomation
  • Mikrocontroller
    • Arduino
    • ESP32 & Co.
    • Calliope Mini
    • Raspberry Pi & Pico
  • Solo Mining
  • Über mich
Menu

Node-RED Fehler loggen: Mit Bordmitteln und eigener Logger-Node

Veröffentlicht am 21. Juni 202621. Juni 2026 von Stefan Draeger

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.

Node-RED Fehler loggen: Mit Bordmitteln und eigener Logger-Node
Dieses Video auf YouTube ansehen.

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.

Inhaltsverzeichnis

  • Praxisbeispiel: Ausfälle des MQTT-Brokers protokollieren
  • MQTT-Status mit Node-RED-Bordmitteln protokollieren
  • Logdateien automatisch nach Datum aufteilen
  • Logging zentral auslagern oder eigene Logger-Node verwenden
  • Eigene Logger-Node für Node-RED
  • Installation der Logger-Node
  • Bordmittel, Link-Nodes oder eigene Logger-Node?
  • Vorteile der eigenen Logger-Node
  • Nachteile und Einschränkungen
  • Mein Fazit zur Logger-Node

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 mit Status-Node, Switch-Node, Function-Nodes und Debug-Ausgabe zur Überwachung einer MQTT-Verbindung
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": "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 mit Status-, Switch-, Function- und File-Node sowie Logausgabe für unterbrochene und wiederhergestellte MQTT-Verbindungen
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.

Konfiguration der Node-RED Write-File-Node mit dem Dateipfad /data/sample.log zum fortlaufenden Speichern von Logeinträgen
Logdatei mit Zeitstempeln und den Log-Leveln ERROR und INFO für unterbrochene, hergestellte und wiederhergestellte MQTT-Verbindungen

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 zur Überwachung einer MQTT-Verbindung mit Status-Node, Switch-Node, Function-Nodes und täglicher Speicherung der Logeinträge in einer Datei.
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.

Einstellungen einer Node-RED Write-File-Node, die den Dateipfad aus msg.filename übernimmt und Logeinträge an eine tägliche Logdatei anhängt.

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 mit Link-Out- und Link-In-Node, bei dem die zentrale Logging-Logik auf einen separaten Flow ausgelagert wurde.
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.

Node-RED Flow mit eigener Logger-Node zur Verarbeitung und Speicherung von Logmeldungen mit Zeitstempel und Log-Level.
Der Flow übergibt strukturierte Logmeldungen an die eigene Logger-Node, die Zeitstempel, Log-Level und Dateiname automatisch verarbeitet.

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-LevelVerwendung
INFOallgemeine Informationen und Statusmeldungen
WARNWarnungen, bei denen der Flow grundsätzlich weiterarbeiten kann
ERRORFehler, 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.

Node-RED - eigene Logger Node - Datum-Zeit-Format_komprimiert
Node-RED – eigene Logger Node – Datum-Zeit-Format_komprimiert

Zur Verfügung stehen beispielsweise:

FormatBeispiel
deutsches Format mit Millisekunden18.06.2026 10:15:42.317
deutsches Format ohne Millisekunden18.06.2026 10:15:42
ISO-ähnliches Format2026-06-18T10:15:42.317
Unix-Zeit in Millisekunden1781770542317

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.

Node-RED - eigene Logger Node - Zeitzone_komprimiert
Node-RED – eigene Logger Node – Zeitzone_komprimiert

Zur Auswahl stehen unter anderem:

  • Systemzeitzone
  • Europe/Berlin
  • UTC
  • Europe/London
  • America/New_York
  • America/Los_Angeles
  • Asia/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.

Node-RED - eigene Logger Node - Dateiname als Pfad oder msg Objekt_komprimiert
Node-RED – eigene Logger Node – Dateiname als Pfad oder msg Objekt_komprimiert

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"
}
Debugausgabe einer Node-RED Logger-Node mit der erzeugten Logzeile und dem vollständigen Dateipfad der verwendeten Logdatei.
Die Debugausgabe zeigt den vollständig formatierten Logeintrag sowie den Dateipfad der von der Logger-Node verwendeten Logdatei.

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.

Checkboxmsg.loggerDebugVerhalten
deaktiviertnicht gesetztkeine Debug-Ausgabe
aktiviertnicht gesetztDebug-Ausgabe
deaktivierttrueDebug-Ausgabe
aktiviertfalsekeine 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.
Logausgabe einer Node-RED Logger-Node mit Zeitstempel, Log-Level und Meldungen zur MQTT-Verbindung in einer täglichen Logdatei.
Die Logger-Node speichert Statusmeldungen mit Zeitstempel und Log-Level in einer täglich neu angelegten Logdatei.

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.payload kein Objekt ist,
  • message fehlt oder leer ist,
  • ein ungültiges Log-Level übergeben wird,
  • der konfigurierte Dateipfad fehlt,
  • msg.filename nicht gesetzt wurde,
  • eine ungültige Zeitzone verwendet wird,
  • das Verzeichnis nicht erstellt werden kann,
  • oder die Datei nicht geschrieben werden kann.
Fehlermeldung der Node-RED Logger-Node, weil msg.payload kein Objekt mit den Eigenschaften message und level enthält.

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ösungGeeignet für
Function- und Write-File-Nodeseinzelne, kleine Logging-Aufgaben
zentrale Logging-Strecke mit Link-Nodesmehrere Flows innerhalb derselben Node-RED-Installation
eigene Logger-Nodewiederverwendbares 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.logLine und msg.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, WARN und ERROR
  • 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

Foto von Stefan Draeger
Über den Autor

Stefan Draeger — Entwickler & Tech-Blogger

Ich zeige praxisnah, wie du Projekte mit Arduino, ESP32 und Smarthome-Komponenten umsetzt – Schritt für Schritt, mit Code und Schaltplänen.

Mehr Artikel von Stefan →

Schreibe einen Kommentar Antwort abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden?
Support-Ticket erstellen

Newsletter abonnieren

Bleib auf dem Laufenden: Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32 und mehr – direkt in dein Postfach.

Jetzt Newsletter abonnieren

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee.
So hilfst du mit, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

draeger-it.blog auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kategorien

Tools

  • QR-Code Generator
  • Passwort Generator: Sichere Passwörter & Passphrasen erstellen
  • PNG zu WebP konvertieren – kostenlos, schnell & ohne Upload
  • Bilder online komprimieren – PNG, JPG & JPEG ohne Upload
  • Code online formatieren: JSON, HTML, XML, CSS & JavaScript
  • Unix-Zeitstempel-Rechner
  • ASCII Tabelle
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner
  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix – Generator

Links

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com

Stefan Draeger
Königsberger Str. 13
38364 Schöningen
Tel.: 015565432686
E-Mail: info@draeger-it.blog

Folge mir auf

link zu Fabook
link zu LinkedIn
link zu YouTube
link zu TikTok
link zu Pinterest
link zu Instagram
  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
©2026 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
  • Optionen verwalten
  • Dienste verwalten
  • Verwalten von {vendor_count}-Lieferanten
  • Lese mehr über diese Zwecke
Einstellungen anzeigen
  • {title}
  • {title}
  • {title}