From 9507299716b736eb2498496b2a7a150f1a50143d Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:44:28 +0200 Subject: [PATCH 1/2] chore: no need to export CHANGELOG.md (#367) --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index de3e8e1..ed82113 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "license": "Apache-2.0", "main": "cds-plugin.js", "files": [ - "lib", - "CHANGELOG.md" + "lib" ], "scripts": { "lint": "npx eslint .", From d958804db17e67a24fef16eddcf504d0b0baefda Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:44:25 +0200 Subject: [PATCH 2/2] fix: SAP Passport with `_hana_prom = true` (#369) --- .github/workflows/hana.yml | 2 +- .gitignore | 1 + CHANGELOG.md | 6 +++++ jest.config.js | 2 +- lib/tracing/cds.js | 8 +++---- lib/tracing/trace.js | 6 +++-- package.json | 2 +- test/passport.test.js | 49 +++++++++++++++++++++++++------------- 8 files changed, 51 insertions(+), 25 deletions(-) diff --git a/.github/workflows/hana.yml b/.github/workflows/hana.yml index 4ab5195..4d43b9c 100644 --- a/.github/workflows/hana.yml +++ b/.github/workflows/hana.yml @@ -1,4 +1,4 @@ -name: CI +name: HANA permissions: contents: read diff --git a/.gitignore b/.gitignore index 0fd6948..99e365a 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,4 @@ package-lock.json test/msg-box test/bookshop/gen +test/bookshop/.cdsrc.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 64dd9b6..8bcb59c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +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.5.2 - 2025-08-18 + +### Fixed + +- SAP Passport propagation with `cds.requires.telemetry.tracing._hana_prom = true` + ## Version 1.5.1 - 2025-08-11 ### Fixed diff --git a/jest.config.js b/jest.config.js index 9ca04dd..70fb6cd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,7 @@ const config = { if (process.env.CI && process.env.HANA_DRIVER) { config.testTimeout *= 10 - config.testMatch = ['**/tracing-attributes.test.js'] + config.testMatch = ['**/tracing-attributes.test.js', '**/passport.test.js'] if (process.env.HANA_PROM) process.env.cds_requires_telemetry_tracing = JSON.stringify({ _hana_prom: process.env.HANA_PROM === 'true' }) diff --git a/lib/tracing/cds.js b/lib/tracing/cds.js index 934e833..c676627 100644 --- a/lib/tracing/cds.js +++ b/lib/tracing/cds.js @@ -14,14 +14,14 @@ function _wrapHandler(handler) { }) } -const _wrapStmt = (stmt, impl, sql) => { +const _wrapStmt = (stmt, impl, sql, dbc) => { for (const fn of ['run', 'get', 'all', 'stream', 'runBatch']) { if (!stmt[fn]) continue const it = stmt[fn] stmt[fn] = wrap(it, { no_locate: true, wrapper: function () { - return trace(`${impl} - stmt.${fn} ${sql}`, it, this, arguments, { sql, fn, kind: SpanKind.CLIENT }) + return trace(`${impl} - stmt.${fn} ${sql}`, it, this, arguments, { sql, dbc, fn, kind: SpanKind.CLIENT }) } }) } @@ -131,8 +131,8 @@ module.exports = () => { fn: 'prepare', kind: SpanKind.CLIENT }) - if (stmt instanceof Promise) return stmt.then(stmt => _wrapStmt(stmt, impl, sql)) - return _wrapStmt(stmt, impl, sql) + if (stmt instanceof Promise) return stmt.then(stmt => _wrapStmt(stmt, impl, sql, this.dbc)) + return _wrapStmt(stmt, impl, sql, this.dbc) } }) dbService.prototype.exec = wrap(_exec, { diff --git a/lib/tracing/trace.js b/lib/tracing/trace.js index fed83d5..48e6054 100644 --- a/lib/tracing/trace.js +++ b/lib/tracing/trace.js @@ -321,7 +321,9 @@ function trace(req, fn, that, args, opts = {}) { if (span.constructor.name === 'NonRecordingSpan') return fn.apply(that, args) // SAP Passport - if (process.env.SAP_PASSPORT && that.dbc?.set) { + // REVISIT: fallback for _hana_prom = false + const dbc = opts.dbc || name.startsWith('@cap-js/hana') && that.dbc + if (process.env.SAP_PASSPORT && dbc?.set) { const { spanId, traceId } = span.spanContext() // REVISIT: @sap/dsrpassport uses '0226' for VARPARTOFFSET // prettier-ignore @@ -346,7 +348,7 @@ function trace(req, fn, that, args, opts = {}) { /* VARPARTOFFSET */ '0000' }${ /* EYECATCHER */ '2A54482A'}` LOG._debug && LOG.debug('Setting SAP Passport:', passport) - that.dbc.set({ SAP_PASSPORT: passport }) + dbc.set({ SAP_PASSPORT: passport }) } const _on_success = res => { diff --git a/package.json b/package.json index ed82113..ef38eef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/telemetry", - "version": "1.5.1", + "version": "1.5.2", "description": "CDS plugin providing observability features, incl. automatic OpenTelemetry instrumentation.", "repository": { "type": "git", diff --git a/test/passport.test.js b/test/passport.test.js index c0a7873..652d564 100644 --- a/test/passport.test.js +++ b/test/passport.test.js @@ -2,37 +2,54 @@ process.env.SAP_PASSPORT = 'true' const cds = require('@sap/cds') const { expect, GET } = cds.test().in(__dirname + '/bookshop') -const log = cds.test.log() describe('SAP Passport', () => { - let _session_vars, _count + if (cds.env.requires.db.kind === 'sqlite') return test.skip('n/a for SQLite', () => {}) - cds.on('connect', srv => { - if (srv.options.kind === 'sqlite') { - const { acquire } = srv - srv.acquire = async function () { + const admin = { auth: { username: 'alice' } } + + cds.on('connect', async service => { + if (service.kind === 'hana') { + const { acquire } = service + service.acquire = async function () { const dbc = await acquire.apply(this, arguments) - dbc.set ??= o => { - _session_vars = Object.assign(_session_vars || {}, o) - _count++ + if (!dbc._native.set._patched) { + const { set } = dbc._native + dbc._native.set = function (obj) { + if ('SAP_PASSPORT' in obj) { + _passports.push(obj.SAP_PASSPORT) + _count++ + } + return set.apply(this, arguments) + } + dbc._native.set._patched = true } return dbc } } }) - const admin = { auth: { username: 'alice' } } - - beforeEach(log.clear) + let _passports, _count beforeEach(() => { - _session_vars = undefined + _passports = [] _count = 0 }) - test('gets set', async () => { - const { status } = await GET('/odata/v4/admin/Books', admin) + test('gets set once for simple queries', async () => { + const { status } = await GET('/odata/v4/admin/Books?$select=ID,title', admin) expect(status).to.equal(200) - expect(_session_vars).to.containSubset({ SAP_PASSPORT: s => s.match(/^2A54482A/) }) expect(_count).to.equal(2) + expect(_passports[0]).to.equal('') //> the reset + expect(_passports[1]).to.match(/^2A54482A/) + }) + + test('gets set twice for prepared statements', async () => { + const { status } = await GET("/odata/v4/admin/Books?$select=ID,title&$filter=title eq 'hurz'", admin) + expect(status).to.equal(200) + expect(_count).to.equal(3) + expect(_passports[0]).to.equal('') //> the reset + expect(_passports[1]).to.match(/^2A54482A/) + expect(_passports[2]).to.match(/^2A54482A/) + expect(_passports[1]).to.not.equal(_passports[2]) //> different for prepare and execute }) })