Skip to content

[Playground] OTLP-Mapper - instana to otlp span mapper#2534

Draft
abhilash-sivan wants to merge 42 commits into
mainfrom
chore-otel-span-mapper
Draft

[Playground] OTLP-Mapper - instana to otlp span mapper#2534
abhilash-sivan wants to merge 42 commits into
mainfrom
chore-otel-span-mapper

Conversation

@abhilash-sivan
Copy link
Copy Markdown
Contributor

@abhilash-sivan abhilash-sivan commented May 4, 2026

UI: https://instana.io/s/B2R0pfHbSpaV5jXmQNDd7w

This is just a playground where we convert Instana spans to Otel spans and send them to the Otel backend.

INSTANA_OTLP_FORMAT=true SERVICE_NAME=test-nodejs-service INSTANA_DEBUG=true node index.js

  • handle multiple keys in span.data
  • approve mapping design
  • approve port problem
  • approve agent announcement cycle disable use case
  • test: send traces via otlp port and metrics via standard port - Idenfies 2 diff apps 1. Otel and 2. Node.js. We have to add metrics mapping. Discussed/Agreed with community.
  • figure out: node.http.server - what is the official otel name/value so that the backend is labeling everything correctly in the UI? (prio 1)
  • Performance check classes vs functions

https://instana.io/s/Ehe2Zhb7QyKyxrmQg0fK8Q The BE uses path_tpl or url values ot show in the UI, so we mapped the otel span name to the same data from Instana span.

After conversion using the script:

  1. Instana kafka trace to otlp
{
  "attributes": [
    {
      "key": "messaging.system",
      "value": {
        "stringValue": "kafka"
      }
    },
    {
      "key": "messaging.destination.name",
      "value": {
        "stringValue": "order-events"
      }
    },
    {
      "key": "messaging.operation.type",
      "value": {
        "stringValue": "send"
      }
    },
    {
      "key": "messaging.kafka.destination.partition",
      "value": {
        "intValue": 0
      }
    },
    {
      "key": "messaging.kafka.message.offset",
      "value": {
        "intValue": 12345
      }
    },
    {
      "key": "messaging.kafka.message.key",
      "value": {
        "stringValue": "order-123"
      }
    },
    {
      "key": "service",
      "value": {
        "stringValue": "order-service"
      }
    }
  ],
  "events": [],
  "links": [],
  "resource": {
    "attributes": [
      {
        "key": "service.name",
        "value": {
          "stringValue": "order-service"
        }
      },
      {
        "key": "telemetry.sdk.language",
        "value": {
          "stringValue": "nodejs"
        }
      },
      {
        "key": "telemetry.sdk.name",
        "value": {
          "stringValue": "@instana/collector"
        }
      },
      {
        "key": "telemetry.sdk.version",
        "value": {
          "stringValue": "3.0.0"
        }
      }
    ]
  },
  "traceId": "00000000000000000000xyz789abc123",
  "spanId": "000000000span002",
  "parentSpanId": "0000000parent002",
  "startTimeUnixNano": "1716019300000000000",
  "endTimeUnixNano": "1716019300050000000",
  "status": {
    "code": 1
  },
  "name": "send order-events"
}

@abhilash-sivan abhilash-sivan force-pushed the chore-otel-span-mapper branch from 4490ab0 to f1c0d6f Compare May 4, 2026 18:07
@abhilash-sivan abhilash-sivan changed the base branch from main to chore-otlp-playground May 7, 2026 06:42
@abhilash-sivan abhilash-sivan force-pushed the chore-otel-span-mapper branch from 53e1fcc to d261bc5 Compare May 7, 2026 12:45
Comment thread packages/collector/src/agentConnection.js
Comment thread packages/core/src/tracing/otlp_mapper/mapper.js Outdated
Comment thread packages/core/src/tracing/otlpTransformer.js Outdated
Comment thread packages/core/src/tracing/otlpTransformer.js Outdated
Comment thread packages/core/src/tracing/otlpTransformer.js Outdated
Comment thread packages/core/src/tracing/spanBuffer.js Outdated
spans = [];
batchingBuckets.clear();

const processedSpans =
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is where the conversion is happening.

This seems to be the optimal place because if we convert early, then in some places we talk Instana format and it could raise unexpected issues. So before transmission seems simple and maintanable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we do this here or just after the current BE transformation,
https://github.com/instana/nodejs/pull/2534/changes?utm_source=chatgpt.com#diff-462ace70d7ac6eb4cd6f82756059c88629bfad1463fde0498d89106d7ccc8104L193

Right now we’re applying the transformation after collecting all spans. IMO it would be cleaner to do it before calling transmitSpans, so the transmit layer only deals with already-transformed spans.

TBD

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I agree. This would be a more cleaner way

// Transform internal span data format into external (backend) readable format.
span = applySpanTransformation(span);

Just after this transformation, we can call the Otel transformation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

One thing we have to consider is that currently all the spans intended for transmission — i.e., the array of spans — are converted together. If we do it in the above-mentioned area, we will have to process the spans individually.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note: TBD — Transform each span to OTLP format immediately in addSpan and store them in OTLP format in the buffer if batching is enabled. This would require changes to the batching logic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do you want to organize the agent ports etc?

OTLP mode is on -> port will change.
There is something missing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also:
How can we skip the agent announcement cycle when connecting to an otel collector later?
Did you draw that path as well to confirm the architecture?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, I haven’t worked much on the architecture yet. I can work on the dynamic port connections part, and I’ll also think through the agent announcement cycle and how we can skip it when connecting to an OTel collector

@abhilash-sivan abhilash-sivan changed the title Chore otel span mapper chore: instana-otlp span mapper May 7, 2026
@abhilash-sivan abhilash-sivan changed the title chore: instana-otlp span mapper [Playground] OTLP-Mapper - instana to otlp span mapper May 8, 2026
Comment thread packages/collector/src/agentConnection.js
Comment thread packages/core/src/tracing/backend_mappers/index.js Outdated
Comment thread packages/core/src/tracing/otlp_mapper/mapper.js Outdated
@kirrg001
Copy link
Copy Markdown
Contributor

test: send traces via otlp port and metrics via standard port - Idenfies 2 diff apps 1. Otel and 2. Node.js

This needs to be communicated in the next WG. Traces & Metrics mapping is required.

@abhilash-sivan abhilash-sivan force-pushed the chore-otel-span-mapper branch from 08a2757 to e827e9c Compare May 15, 2026 05:57
@abhilash-sivan abhilash-sivan force-pushed the chore-otel-span-mapper branch from e827e9c to d6c01a6 Compare May 15, 2026 05:58
Comment thread scripts/instana-to-otel-converter.js Outdated
Comment thread scripts/instana-to-otel-converter.js Outdated
* RabbitMQ-specific mappings
* Extends base messaging with RabbitMQ-specific fields
*/
rabbitmq: {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not verified rabbitmq per se. It's just to show how we differentiate children under messaging or any other group.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But for RabbitMQ, there’s nothing really common with BASE_MAPPINGS.messaging. I don’t think any of the others share much with it either, apart from Kafka.

Comment thread packages/core/src/tracing/converters/transformers.js Outdated
mappings: SPAN_ATTRIBUTE_MAPPINGS.kafka,
prefix: 'messaging.kafka',
additionalAttributes: {
'messaging.system': 'kafka'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need that here? If we have already a custom kafka mapping object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

messaging.system is an additional attribute that we append to the span. For Kafka, the system name also happens to be kafka, so it may seem redundant at first glance.

However, additionalAttributes is intended for constant or computed attributes that do not directly map from a single field inside span.data.kafka.

messaging.system falls into that category since it is effectively a constant defined per transformer, rather than something extracted from the incoming span payload itself.

I have changed this to: 'messaging.system': this.systemName

which will fetch the appropriate value in this case

Comment thread packages/core/src/tracing/converters/instana-to-otel-converter-utils.js Outdated
Comment thread packages/core/src/tracing/converters/instana-to-otel-converter.js Outdated
Comment thread packages/core/src/tracing/converters/instana-to-otel-converter.js
Comment thread packages/core/src/tracing/converters/transformers.js Outdated
scopeSpans: [
{
scope: {
name: '@instana/collector',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: we might need to update the package name later. Since this transformer lives in core, it could also be used by the serverless package in the future. It would be better to dynamically fetch the package name instead of hardcoding it.

*
* @see https://opentelemetry.io/docs/specs/semconv/database/
*/
database: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It should be databases since that is the naming pattern we already use. You can also see this in the IBM documentation and in the directory structure under core/src/tracing/instrumentation


'use strict';

const { getOtlpAttributeMappings } = require('./otlp_mapper/mapper');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this file still needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, metrics are handled here


attributes.push({
key: 'telemetry.sdk.version',
value: { stringValue: '3.0.0' }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit pick: This value is currently hard coded, can we source this from package.json? or add todo comment for fixing this later

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes


// Service name from span data
const serviceName =
instanaSpan.data?.service || process.env.OTEL_SERVICE_NAME || process.env.SERVICE_NAME || 'unknown-service';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is process.env.SERVICE_NAME? Did you mean process.env.INSTANA_SERVICE_NAME?
May be here, we can use config.serviceName as well.

instanaSpan.data?.service || process.env.OTEL_SERVICE_NAME || config.serviceName;

* RabbitMQ-specific mappings
* Extends base messaging with RabbitMQ-specific fields
*/
rabbitmq: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But for RabbitMQ, there’s nothing really common with BASE_MAPPINGS.messaging. I don’t think any of the others share much with it either, apart from Kafka.

* @see https://opentelemetry.io/docs/specs/semconv/general/attributes/#server-and-client-attributes
*/
peer: {
hostname: { key: 'net.peer.name' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

net.peer.name and net.peer.port are depreacted.

net.peer.name => server.address
net.peer.port => server.port

https://opentelemetry.io/blog/2023/http-conventions-declared-stable/#http-client-span-attributes

/**
* Protocol configuration for transformer instantiation and semantic system names
*/
const PROTOCOL_CONFIG = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PROTOCOL_CONFIG feels slightly misleading because this object is not really defining protocols, it’s mapping Instana span types to OTEL semantic convention system names.

We can reduce duplication in PROTOCOL_CONFIG by treating systemName as defaulting to spanType.

Maybe simplify to something like:

const OTEL_SYSTEM_NAMES = {
  http: 'http',
  kafka: 'kafka',
  rabbitmq: 'rabbitmq',
  mongo: 'mongodb'
};

Then:

function getProtocolConfig(spanType) {
  return {
    spanType: spanType,
    systemName: OTEL_SYSTEM_NAMES[spanType] || spanType
  };
}

*
* When a span contains multiple data keys (e.g., both 'mongo' and 'peer'),
* this array determines which transformer to use. Primary span types are
* checked first, auxiliary data types last.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why this required?

* @returns {string} OTLP trace ID (32-character hex)
*/
function convertTraceId(instanaTraceId) {
if (!instanaTraceId) return '00000000000000000000000000000000';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO this may not be a valid case, but if instanaTraceId is falsy we silently convert it to an all-zero trace ID. How is this expected to be handled? Do we have any known scenarios where this can happen? 🤔

http: HttpTransformer,

// Messaging protocols
kafka: KafkaTransformer,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is wrong IMO

Either we instantiate all of them here or none.
So we have to go with "all".

rabbitmq: span =>
new MessagingTransformer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants