From 6001078abf5e754d8ccdea26d7869142ffcdb1c0 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:32:06 +0200 Subject: [PATCH] feat: propagate trace context to hana via sap passport session context (#230) --- CHANGELOG.md | 9 +++++++-- README.md | 1 + lib/tracing/index.js | 9 +++++++++ lib/tracing/trace.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865229e..4b559b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog](http://keepachangelog.com/). -## Version 1.0.2 - tbd +## Version 1.1.0 - tbd + +### Added + +- Experimental!: Propagate W3C trace context to SAP HANA via session context `SAP_PASSPORT` + - Enable via environment variable `SAP_PASSPORT` ### Fixed @@ -24,7 +29,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Support for SAP Cloud Logging credentials via user-provided service - Support for adding `@opentelemetry/instrumentation-runtime-node` - `npm add @opentelemetry/instrumentation-runtime-node` - - to `cds.requires.telemetry.instrumentations`, add: + - To `cds.requires.telemetry.instrumentations`, add: ```json "instrumentation-runtime-node": { "class": "RuntimeNodeInstrumentation", diff --git a/README.md b/README.md index de09af0..b3bb7f5 100644 --- a/README.md +++ b/README.md @@ -438,6 +438,7 @@ Hence, the `hrtime` mode is on by default in development but not in production. - `NO_TELEMETRY`: Disables the plugin - `NO_LOCATE`: Disables function location in tracing +- `SAP_PASSPORT`: Enables propagating W3C trace context to SAP HANA (experimental!) - `OTEL_LOG_LEVEL`: If not specified, the log level of cds logger `telemetry` is used - `OTEL_SERVICE_NAME`: If not specified, the name is determined from package.json (defaulting to "CAP Application") - `OTEL_SERVICE_VERSION`: If not specified, the version is determined from package.json (defaulting to "1.0.0") diff --git a/lib/tracing/index.js b/lib/tracing/index.js index 0a44b51..0b0367c 100644 --- a/lib/tracing/index.js +++ b/lib/tracing/index.js @@ -147,6 +147,15 @@ module.exports = resource => { cds._telemetry.tracer._active = true }) + // clear sap passport for new tx + if (process.env.SAP_PASSPORT) { + cds.on('served', () => { + cds.db?.before('BEGIN', async function () { + if (this.dbc?.constructor.name in { HDBDriver: 1, HANAClientDriver: 1 }) this.dbc.set({ SAP_PASSPORT: '' }) + }) + }) + } + /* * add tracing */ diff --git a/lib/tracing/trace.js b/lib/tracing/trace.js index 787e7e1..c76cbff 100644 --- a/lib/tracing/trace.js +++ b/lib/tracing/trace.js @@ -1,4 +1,5 @@ const cds = require('@sap/cds') +const LOG = cds.log('telemetry') const otel = require('@opentelemetry/api') const { SpanKind, SpanStatusCode, ROOT_CONTEXT } = otel @@ -244,6 +245,36 @@ function trace(name, fn, targetObj, args, options = {}) { _setAttributes(span, _getDynamicDBAttributes(options, args, parentSpan)) + // SAP Passport + if (process.env.SAP_PASSPORT && targetObj.dbc?.constructor.name in { HDBDriver: 1, HANAClientDriver: 1 }) { + const { spanId, traceId } = span.spanContext() + // prettier-ignore + let passport = [ + /* EYECATCHER */ '2A54482A', + /* VERSION */ '03', + /* LENGTH */ '00E6', + /* TRACELEVEL */ '0000', + /* COMPONENTID */ '2020202020202020202020202020202020202020202020202020202020202020', + /* SERVICE */ '0000', + /* USER */ '2020202020202020202020202020202020202020202020202020202020202020', + /* ACTION */ '20202020202020202020202020202020202020202020202020202020202020202020202020202020', + /* ACTIONTYPE */ '0000', + /* PREVCOMPONENTID */ Buffer.from(span.resource.attributes['service.name'].substr(0, 32).padEnd(32, ' ')).toString('hex'), + /* TRANSACTIONID */ Buffer.from(traceId.toUpperCase()).toString('hex'), + /* CLIENT */ '202020', + /* COMPONENTTYPE */ '0000', + /* ROOTCONTEXTID */ traceId.toUpperCase(), + /* CONNECTIONID */ '0000000000000001' + spanId.toUpperCase(), + /* CONNECTIONCNT */ '00000001', + /* VARPARTCOUNT */ '0000', + /* VARPARTOFFSET */ '0000', // REVISIT: @sap/dsrpassport uses '0226' + /* EYECATCHER */ '2A54482A' + ] + passport = passport.join('') + LOG._debug && LOG.debug('Setting SAP Passport:', passport) + targetObj.dbc.set({ SAP_PASSPORT: passport }) + } + // augment db.statement at parent, if necessary if ( span.attributes[SEMATTRS_DB_STATEMENT] &&