Skip to content
System types · Built-in All levels

Node-RED

The built-in Node-RED service type — why it is recognised from span attributes rather than metric names, the four runtime-level checks including the incomplete-span one, and why per-flow alerting belongs to integrations.

SLSluicio team 6 min read Updated Sep 2026

Node-RED is a flow runtime: one process hosting many flows, each of which is an integration to anyone watching it. It is also the one built-in type that has no metrics of its own, which changes both how it is recognised and what the starter checks can be.

Key: node-red · applied automatically to services emitting node_red.-prefixed span attributes.

Every other type is recognised from metric names. node-red-contrib-opentelemetry is a tracing integration: the only metrics it exports are HTTP request metrics from http in nodes, under generic semantic-convention names that every HTTP service in your estate emits. There is no Node-RED metric name to match on, and there is no reason to expect one.

What every Node-RED span does carry is node_red.* attributes - the flow id, the flow name, the node type, the trigger. So this type is detected from span attribute keys instead. Nothing is different from your side; it is the only built-in that works this way.

Install node-red-contrib-opentelemetry and configure one OTEL config node pointing at your cell’s OTLP endpoint.

Three things are worth knowing before you spend an evening on it:

  • Only one OTEL node can own the hooks. The hook label is global, so a second config node is inert and its settings - including attribute mappings - are silently ignored. Keep exactly one.
  • Span attribute mappings live on the config node, not in the flow, so no flow file can carry them. They have to be typed in and deployed.
  • Exporter credentials belong in Node-RED’s credentials store, never in node config: node config is written into flows.json and travels with every flow export.
CheckConditionSeverityWhy it matters
Flow faileda failed flow execution in 15 minutesWarningSomething raised.
Flow execution did not completea span left incomplete in 15 minutesWarningThe run neither finished nor raised. See below - this one catches what the first cannot.
Slow flowsp95 flow duration > 30s over 15 minutesWarningAcross the runtime; scope it to an integration to tune per flow.
Runtime went quietfewer than one flow execution in an hourWarningNode-RED fails silently well: the editor stays green while nothing runs.

The incomplete-span check is the important one

Section titled “The incomplete-span check is the important one”

Calling msg.error(...) in a flow routes the message to a catch node without firing the completion hook, so the run is never marked as failed. The failed-flow check above genuinely cannot see it. Only throw or node.done(err) marks a trace.

What does survive is a span that was opened and never closed, and the instrumentation flags it with node_red.span.incomplete. On a runtime where flows handle their own errors - which is most of them - this check sees failures that nothing else does.

A dead-man check is the right instrument for a runtime that should always be doing something, and pure noise for one that runs a nightly export and sleeps. If your flows are scheduled, disable it here and put a multi-stage completion check on the integration instead, where “did last night’s run happen” is a question with a real answer.

The checks above are deliberately runtime-level. A Node-RED process hosting twenty flows will tell you that something failed, which is not what you want to be paged with.

Per-flow alerting is what integrations are for. Make each flow an integration, matched on its node_red.flow.id - see attribute matching:

service.name = my-nodered AND node_red.flow.id = tab_paperless

Every check on that integration is then per flow by construction, its health is that flow’s health, and its messages are that flow’s runs. Sluicio proposes exactly this grouping once the telemetry is flowing, so you are usually accepting a suggestion rather than writing matchers by hand.

This is also why the facet and dependency work matters on a Node-RED runtime specifically: several integrations share one service, so anything derived per service rather than per integration would give all of them the same answer.

  • Slow flows across a runtime is a blunt instrument - an HTTP call and an hourly file poll do not belong to the same threshold. It is worth keeping as a coarse signal while you split the flows into integrations, and worth disabling afterwards.
  • A catch, delay or trigger node starts a linked second trace, following the OpenTelemetry messaging conventions, rather than extending the first. That is deliberate: the hand-off is asynchronous. Sluicio follows those links, so topology and integration candidates see across them, and the trace view draws the continuation.