Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ As with every [node installation](https://nodered.org/docs/user-guide/runtime/ad

- Add OTEL node **once** (to any flow),
- Setup the node:
- set OTEL [exporter](https://opentelemetry.io/docs/instrumentation/js/exporters/) url (example for Jaeger: `http://localhost:4318/v1/traces`),
- set OTEL [exporter](https://opentelemetry.io/docs/instrumentation/js/exporters/) url (example for Jaeger: `http://localhost:4318/v1/traces`). When left blank, the plugin will probe for [configuration via environment variables](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_endpoint),
- choose an OTLP transport protocol (`http/json` or `http/protobuf`),
- define a service name (will be displayed as span service),
- define a service name (will be displayed as span service; if the value of node is left blank, the plugin will probe for [configuration via environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/)),
- define an optional root span prefix (will be added in Node-RED root span name),
- define nodes that should not send traces (using comma-separated list like `debug,catch`),
- define nodes that should propagate [W3C trace context](https://www.w3.org/TR/trace-context/#design-overview) (in http request headers, using comma-separated list like `http request,my-custom-node`),
Expand Down
8 changes: 4 additions & 4 deletions lib/opentelemetry-node.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
value: '',
},
url: {
value: 'http://localhost:4318/v1/traces',
required: true,
value: '',
required: false,
validate: function (v) {
try {
// eslint-disable-next-line no-new
Expand All @@ -44,8 +44,8 @@
},
},
serviceName: {
value: 'Node-RED',
required: true,
value: '',
required: false,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also a source of errors (if it's left empty and there's no environment variable, traces come from unknown_service:/usr/local/bin/node)...
Ok not to be mandatory, but I prefer to keep the default value.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought since we have a placeholders, users might already have a clue on what's the expected value there. I can put the default there in place, also add a red badge when its missing (same as for URL)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can go checking in repo issues, sometimes users install without reading the doc and don't think about the configuration either and come telling "that's do not work" 😆

},
protocol: {
value: 'http',
Expand Down
33 changes: 21 additions & 12 deletions lib/opentelemetry-node.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const os = require('os')
const { name, version } = require('../package.json')
const { trace, context, propagation, SpanKind, SpanStatusCode } = require('@opentelemetry/api')
const { Resource } = require('@opentelemetry/resources')
const { Resource, detectResourcesSync, envDetectorSync } = require('@opentelemetry/resources')
const { ATTR_SERVICE_NAME, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_PATH, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_SCHEME, ATTR_HTTP_REQUEST_METHOD, ATTR_CLIENT_ADDRESS, ATTR_USER_AGENT_ORIGINAL, ATTR_HTTP_REQUEST_HEADER } = require('@opentelemetry/semantic-conventions')
// eslint-disable-next-line node/no-missing-require
const { ATTR_HOST_NAME, ATTR_CODE_FUNCTION } = require('@opentelemetry/semantic-conventions/incubating')
Expand All @@ -12,6 +12,7 @@ const {
CompositePropagator,
W3CBaggagePropagator,
W3CTraceContextPropagator,
merge,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used

} = require('@opentelemetry/core')
const { clearInterval } = require('timers')
const { defaultTextMapGetter } = require('@opentelemetry/api')
Expand Down Expand Up @@ -249,7 +250,6 @@ function createSpan (tracer, msg, nodeDefinition, node, isNotTraced) {
parentSpan = tracer.startSpan(_rootPrefix + spanName, {
attributes: {
[ATTR_IS_MESSAGE_CREATION]: true,
[ATTR_SERVICE_NAME]: nodeDefinition.type,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep the service.name attribute for the parent, as it describes the logical name of the service (cf. https://opentelemetry.io/docs/specs/semconv/attributes-registry/service/).
It is different from node_red.node.type which is used to describe a specific Node-RED span attribute and code.function which is used for child spans only.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tested it it was just set to http in, which confused me. Even if it contains the flow name itself, I don't want it to override service.name attribute on spans/traces, as service.name attribute for us is reserved for the name of internal microservice and is stattic across all application.

I can make it as another config option which is turned on by default, what do you think?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding, you have a service name for each Node-RED instance?
In my use, I want to have the "parent" span telling me "yeah this is a message inject" (coming from inside my Node-RED) or "this trace come from an external http request".

...commonAttributes,
},
kind,
Expand Down Expand Up @@ -478,26 +478,35 @@ module.exports = function (RED) {
_attributeMappings = attributeMappings

// check config
if (!url) {
this.status({ fill: 'red', shape: 'ring', text: 'invalid configuration' })
return
}
const node = this

// create tracer
let spanProcessor
const traceExporterProps = url ? { url } : {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's weird... exporting to nothing? 😆
Cf. previous comment about error prone.

Copy link
Author

@vitalii-diachkov-sumup vitalii-diachkov-sumup Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If URL is not explicitly provided, OTEL will derive the value from the environment. I can leave a code comment above this line

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, add an explicit comment, and may be this is the place to add the check from previous comment.

if (protocol === 'proto') {
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
spanProcessor = new BatchSpanProcessor(new OTLPTraceExporter({ url }))
spanProcessor = new BatchSpanProcessor(new OTLPTraceExporter(traceExporterProps))
} else {
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
spanProcessor = new BatchSpanProcessor(new OTLPTraceExporter({ url }))
spanProcessor = new BatchSpanProcessor(new OTLPTraceExporter(traceExporterProps))
}

// Create OTEL resource, combining props passed via environment variables
// and those explicitly given in OTEL node UI
const explicitResource = new Resource(
serviceName
? { [ATTR_SERVICE_NAME]: serviceName, [ATTR_HOST_NAME]: os.hostname() }
: { [ATTR_HOST_NAME]: os.hostname() },
)
const envResource = detectResourcesSync({ detectors: [envDetectorSync] })
const mergedResource = explicitResource.merge(envResource)

if (_isLogging) {
console.log('Initializing tracer provider with resource', mergedResource)
}

const provider = new BasicTracerProvider({
resource: new Resource({
[ATTR_SERVICE_NAME]: serviceName,
[ATTR_HOST_NAME]: os.hostname(),
}),
resource: mergedResource,
spanProcessors: [spanProcessor],
})
provider.register()
Expand Down
Loading