diff --git a/lib/tracing/index.js b/lib/tracing/index.js index 7d735dd..02df258 100644 --- a/lib/tracing/index.js +++ b/lib/tracing/index.js @@ -1,8 +1,8 @@ const cds = require('@sap/cds') const LOG = cds.log('telemetry') -const { trace } = require('@opentelemetry/api') -const { getEnv, getEnvWithoutDefaults } = require('@opentelemetry/core') +const { trace, SpanKind } = require('@opentelemetry/api') +const { ExportResultCode, getEnv, getEnvWithoutDefaults } = require('@opentelemetry/core') const { Resource } = require('@opentelemetry/resources') const { BatchSpanProcessor, SimpleSpanProcessor, SamplingDecision } = require('@opentelemetry/sdk-trace-base') const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node') @@ -156,6 +156,25 @@ module.exports = resource => { LOG._info && LOG.info('Dynatrace OneAgent detected, disabling trace exporter') } else { const exporter = _getExporter() + + // unofficial hacks for running with xotel agent: + // - do not export @opentelemetry/instrumentation-hdb spans + // - remove parentSpanId from http spans if there is no traceparent header + // - switch SERVER spans back to INTERNAL spans + if (cds.env.requires.telemetry._with_xotel) { + LOG._info && LOG.info('Adding export mods for running with @sap/xotel-agent-ext-js') + const { export: _export } = exporter + exporter.export = function (spans, resultCallback) { + const _spans = spans.filter(s => s.instrumentationLibrary?.name !== '@opentelemetry/instrumentation-hdb') + if (!_spans.length) return resultCallback({ code: ExportResultCode.SUCCESS }) + for (const _span of _spans) { + if (_should_be_orphan(_span)) _make_orphan(_span) + if (_should_be_internal(_span)) _make_internal(_span) + } + return _export.call(this, _spans, resultCallback) + } + } + const processorConfig = cds.env.requires.telemetry.tracing.processor?.config || {} const processor = process.env.NODE_ENV === 'production' @@ -181,3 +200,19 @@ module.exports = resource => { return tracerProvider } + +const _should_be_orphan = span => + span.instrumentationLibrary?.name === '@opentelemetry/instrumentation-http' && + span.parentSpanId && + !span.attributes['http.request.header.traceparent'] +const _make_orphan = span => { + LOG._debug && LOG.debug('Removing parentSpanId from span:', span.spanContext().spanId) + span.parentSpanId = undefined +} + +const _should_be_internal = span => + span.kind === SpanKind.SERVER && span.instrumentationLibrary?.name !== '@opentelemetry/instrumentation-http' +const _make_internal = span => { + LOG._debug && LOG.debug('Switching span kind back to INTERNAL for span:', span.spanContext().spanId) + span.kind = SpanKind.INTERNAL +} diff --git a/lib/tracing/trace.js b/lib/tracing/trace.js index 9cc3b12..5347a30 100644 --- a/lib/tracing/trace.js +++ b/lib/tracing/trace.js @@ -253,6 +253,12 @@ function trace(req, fn, that, args, opts = {}) { if (kind == null) { kind = SpanKind.INTERNAL //> default + // unofficial hack for running with xotel agent: default to SERVER spans + if (cds.env.requires.telemetry._with_xotel) { + LOG._debug && LOG.debug('Temporaily switching span kind to SERVER to circumvent no sampling decision by xotel') + kind = SpanKind.SERVER + } + if (that instanceof cds.RemoteService) kind = SpanKind.CLIENT else if (that instanceof cds.MessagingService) { const msg = cds.env.requires[that.name]