Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
18 changes: 9 additions & 9 deletions packages/collector/src/agentConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const pathUtil = require('path');
const circularReferenceRemover = require('./util/removeCircular');
const agentOpts = require('./agent/opts');
const cmdline = require('./cmdline');
const otlpTransformer = require('./otlpTransformer');
const otlpTransformer = require('@instana/core/src/tracing/otlpTransformer');
/** @typedef {import('@instana/core/src/core').InstanaBaseSpan} InstanaBaseSpan */

/** @type {import('@instana/core/src/core').GenericLogger} */
Expand Down Expand Up @@ -314,7 +314,7 @@ exports.sendMetrics = function sendMetrics(data, cb) {
firstTwoKeys[dataKeys[i]] = data[dataKeys[i]];
}

logger.debug(`sendMetrics called with data (first 2 keys): ${JSON.stringify(firstTwoKeys)}`);
// logger.debug(`sendMetrics called with data (first 2 keys): ${JSON.stringify(firstTwoKeys)}`);

// Transform Instana metrics to OTLP format
const otlpMetrics = otlpTransformer.transformMetrics(data);
Expand Down Expand Up @@ -344,15 +344,15 @@ exports.sendMetrics = function sendMetrics(data, cb) {
}
}

logger.debug(`Transformed to OTLP (first 2 metrics) ${JSON.stringify(otlpPreview)}`);
// logger.debug(`Transformed to OTLP (first 2 metrics) ${JSON.stringify(otlpPreview)}`);

// Send directly without using sendData (which would transform again)
sendOtlpData('/v1/metrics', otlpMetrics, err => {
if (err) {
logger.error('Error sending metrics:', err);
cb(err, null);
} else {
logger.debug('Metrics sent successfully');
// logger.debug('Metrics sent successfully');
// OTLP endpoints don't return requests like the old Instana endpoint
// Always return empty array for compatibility
cb(null, []);
Expand All @@ -370,10 +370,10 @@ exports.sendSpans = function sendSpans(spans, cb) {
if (err && !maxContentErrorHasBeenLogged && err instanceof PayloadTooLargeError) {
logLargeSpans(spans);
} else if (err) {
const spanInfo = getSpanLengthInfo(spans);
const spanInfo = spans;
logger.debug(`Failed to send: ${JSON.stringify(spanInfo)}`);
} else {
const spanInfo = getSpanLengthInfo(spans);
const spanInfo = spans;
logger.debug(`Successfully sent: ${JSON.stringify(spanInfo)}`);
}
cb(err);
Expand Down Expand Up @@ -475,11 +475,11 @@ function sendData(path, data, cb, ignore404 = false) {
cb = util.atMostOnce(`callback for sendData: ${path}`, cb);
console.log(JSON.stringify(data));
// Transform Instana format to OTLP format
const otlpFormat = otlpTransformer(data);
// const otlpFormat = otlpTransformer(data);

console.log(JSON.stringify(otlpFormat));
// console.log(JSON.stringify(otlpFormat));

const payloadAsString = JSON.stringify(otlpFormat, circularReferenceRemover());
const payloadAsString = JSON.stringify(data, circularReferenceRemover());
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.

Doing the conversion from spanBuffer

if (typeof logger.trace === 'function') {
logger.trace(`Sending data to ${path}.`);
} else {
Expand Down
26 changes: 19 additions & 7 deletions packages/core/src/tracing/backend_mappers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,26 @@
'use strict';

const mapper = require('./mapper');
const otlpMapper = require('./otlpMapper');

/**
* @param {(span: import('../../core').InstanaBaseSpan) => import('../../core').InstanaBaseSpan} transformer
*/
function createSafeTransform(transformer) {
return (/** @type {import('../../core').InstanaBaseSpan} */ span) => {
try {
return transformer(span);
} catch (error) {
return span;
}
};
}

module.exports = {
get transform() {
return (/** @type {import('../../core').InstanaBaseSpan} */ span) => {
try {
return mapper.transform(span);
} catch (error) {
return span;
}
};
return createSafeTransform(mapper.transform);
},
get otlpTransform() {
return createSafeTransform(otlpMapper.transform);
}
};
113 changes: 113 additions & 0 deletions packages/core/src/tracing/backend_mappers/otlpMapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

/**
* OTLP attribute mappings for different span types.
* Maps Instana span data fields to OTLP semantic convention attributes.
*
* Based on OpenTelemetry Semantic Conventions for HTTP:
* - Common HTTP exit (client) span mapping
* - Common HTTP entry (server) span mapping
*
* @type {Object<string, Object<string, string>>}
*/
const otlpAttributeMappings = {
http: {
// HTTP method mapping (both client and server)
// Instana: http.method -> OTel: http.request.method
method: 'http.request.method',

// HTTP status code mapping (both client and server)
// Instana: http.status -> OTel: http.response.status_code
status: 'http.response.status_code',

// HTTP URL mapping (client spans)
// Instana: http.url -> OTel: url.full
url: 'url.full',

// HTTP path mapping (server spans)
// Instana: http.path -> OTel: url.path
path: 'url.path',

// HTTP host mapping (both client and server)
// Instana: http.host -> OTel: server.address (simplified, may need port handling)
host: 'server.address',

// HTTP protocol mapping
// Instana: http.protocol -> OTel: network.protocol.name (may need version split)
protocol: 'network.protocol.name',

// HTTP query parameters mapping (both client and server)
// Instana: http.params -> OTel: url.query
params: 'url.query',

// HTTP path template mapping (both client and server)
// Instana: http.path_tpl -> OTel: url.template
path_tpl: 'url.template',

// HTTP error mapping (both client and server)
// Instana: http.error -> OTel: error.type
error: 'error.type',

// Note: http.context_root mapping is not included as it conflicts with http.path
// Both would map to url.path. Context root extraction requires special logic
// and should be implemented separately when needed.

// Legacy mappings for backward compatibility
status_text: 'http.status_text',

// HTTP route mapping (alternative to path_tpl)
route: 'http.route'
},

// resource but added for ui view, without this .. ?
service: {
name: 'service.name'
}
};

/**
* Transforms span data fields to OTLP attribute naming while keeping
* the mapper logic separate from the backend field mapping.
*
* @param {import('../../core').InstanaBaseSpan} span
* @returns {import('../../core').InstanaBaseSpan} The transformed span.
*/
module.exports.transform = span => {
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.

the same transform logic, like we already do for instana internal format to backend compatible conversion

if (!span || !span.data) {
return span;
}

Object.keys(span.data).forEach(key => {
const mappings = otlpAttributeMappings[key];
if (!mappings || typeof span.data[key] !== 'object' || span.data[key] === null) {
return;
}

applyMappings(span.data[key], mappings, key);
});

return span;
};

/**
* Applies OTLP field mappings to a specific data section.
*
* @param {Record<string, any>} dataSection
* @param {Object<string, string>} mappings
* @param {string} sectionKey
*/
function applyMappings(dataSection, mappings, sectionKey) {
Object.keys(dataSection).forEach(internalField => {
const mappedField = mappings[internalField] || `${sectionKey}.${internalField}`;
dataSection[mappedField] = dataSection[internalField];
delete dataSection[internalField];
});
}

module.exports.getOtlpAttributeMappings = function () {
return otlpAttributeMappings;
};
Loading