Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/hana.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: HANA

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,4 @@ package-lock.json

test/msg-box
test/bookshop/gen
test/bookshop/.cdsrc.json
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down
8 changes: 4 additions & 4 deletions lib/tracing/cds.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}
})
}
Expand Down Expand Up @@ -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, {
Expand Down
6 changes: 4 additions & 2 deletions lib/tracing/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
49 changes: 33 additions & 16 deletions test/passport.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
})