Skip to content

Commit 0443714

Browse files
BobdenOssjvans
andauthored
fix: Keep SAP Passport up-to-date (#283)
Currently `SAP Passport` is only updated when `prepare` and `exec` are called, but it is required to update every time the database is called. Therefor this PR shifts the `SAP Passport` criteria to using `prom`. Which is the re use function `@cap-js/hana` uses inside its generic driver wrappers to promisify the native driver functions. Therefor by tracing the `prom` function it traces _all_* driver calls and ensures that the `SAP Passport` contains an unique trace ID which open telemetry can anchor itself onto. As without the round trip specific parent trace ID the open telemetry service provider will see the child trace timestamps outside of the parent trace window and try to adjust the timestamp into the parent trace time frame. Which gets especially confusing when the parent time frame is smaller then the child time frame. ![image](https://github.com/user-attachments/assets/e8a92aa3-7748-4089-a33e-eb2e99f05af6) It still has to be investigated how well the `prom` wrapping approach works for the `resultset streaming` (cap-js/cds-dbs#702) feature. --------- Co-authored-by: D050513 <[email protected]>
1 parent 6e96ea7 commit 0443714

File tree

6 files changed

+61
-34
lines changed

6 files changed

+61
-34
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1111
- Improved support for tracing messaging services and `cds.spawn`
1212
- Support for adding custom spans to trace hierarchy via `tracer.startActiveSpan()` (beta)
1313
- Trace attribute `db.client.response.returned_rows` for queries via `cds.ql`
14+
- Experimental!: Trace HANA interaction via `@cap-js/hana`'s promisification of the driver API for increased accuracy
15+
- Enable via config `cds.env.requires.telemetry.tracing._hana_prom`
16+
- Requires `@cap-js/hana^1.7.0`
1417

1518
### Changed
1619

lib/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ function _getInstrumentations() {
2424
module: '@opentelemetry/instrumentation-runtime-node'
2525
}
2626
}
27-
} catch (e) {
28-
LOG._debug && LOG.debug('Failed to automatically add @opentelemetry/instrumentation-runtime-node:', e)
27+
} catch (err) {
28+
LOG._debug && LOG.debug('Failed to automatically add @opentelemetry/instrumentation-runtime-node:', err)
2929
}
3030
}
3131

lib/tracing/cds.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const cds = require('@sap/cds')
2+
const LOG = cds.log('telemetry')
23

34
const { SpanKind } = require('@opentelemetry/api')
45

@@ -79,8 +80,32 @@ module.exports = () => {
7980

8081
const impl = cds.env.requires.db?.impl
8182
if (impl?.match(/^@cap-js\//)) {
82-
const dbService = require(impl)
8383
cds.once('served', () => {
84+
// HANA
85+
if (impl === '@cap-js/hana' && cds.env.requires.telemetry.tracing._hana_prom) {
86+
try {
87+
const [major, minor] = require('@cap-js/hana/package.json').version.split('.')
88+
if (major > 1 || (major === 1 && minor >= 7)) {
89+
// REVISIT: when telemetry and hana are loaded from differen places this doesn't work
90+
const hanaDriver = require('@cap-js/hana/lib/drivers/base.js')
91+
const _prom = hanaDriver.prom
92+
hanaDriver.prom = function (dbc, func) {
93+
const fn = _prom(dbc, func)
94+
dbc = dbc._parentConnection || dbc
95+
const driver = dbc.constructor.name === 'Client' ? 'hdb' : '@sap/hana-client'
96+
return function prom() {
97+
return trace(`${driver} - ${func}`, fn, this, arguments, { dbc, fn: func, kind: SpanKind.CLIENT })
98+
}
99+
}
100+
return
101+
}
102+
} catch (err) {
103+
LOG._warning && LOG.warning('Failed to wrap hana driver due to error:', err)
104+
}
105+
}
106+
107+
// other DBs
108+
const dbService = cds.db.constructor
84109
const { prepare: _prepare, exec: _exec } = dbService.prototype
85110
dbService.prototype.prepare = wrap(_prepare, {
86111
wrapper: function prepare(sql) {

lib/tracing/okra.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ module.exports = () => {
88
let OKRAService
99
try {
1010
OKRAService = require('@sap/cds/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/Service')
11-
} catch (e) {
12-
LOG._warn && LOG.warn('Unable to instrument Okra due to error:', e)
11+
} catch (err) {
12+
LOG._warn && LOG.warn('Unable to instrument Okra due to error:', err)
1313
return
1414
}
1515

lib/tracing/trace.js

+25-26
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ function _getDynamicDBAttributes(options, args, parent) {
163163
// the second time, args is the string query -> we need to get event and target from the previous invocation (i.e., parentSpan).
164164
const dbAttributes = new Map()
165165
const db_statement =
166-
options.sql || (typeof args[0].query === 'string' && args[0].query) || (typeof args[0] === 'string' && args[0])
166+
options.sql || (typeof args[0]?.query === 'string' && args[0].query) || (typeof args[0] === 'string' && args[0])
167167
if (db_statement) dbAttributes.set(ATTR_DB_STATEMENT, db_statement)
168-
const db_operation = args[0].event || parent?.attributes[ATTR_DB_OPERATION]
168+
const db_operation = args[0]?.event || parent?.attributes[ATTR_DB_OPERATION]
169169
if (db_operation) dbAttributes.set(ATTR_DB_OPERATION, db_operation)
170-
const db_sql_table = args[0].target?.name || parent?.attributes[ATTR_DB_SQL_TABLE]
170+
const db_sql_table = args[0]?.target?.name || parent?.attributes[ATTR_DB_SQL_TABLE]
171171
if (db_sql_table) dbAttributes.set(ATTR_DB_SQL_TABLE, db_sql_table)
172172
return dbAttributes
173173
}
@@ -200,31 +200,30 @@ function _addAttributes(span, fn, that, options, args, parent) {
200200
_setAttributes(span, _getDynamicDBAttributes(options, args, parent))
201201

202202
// SAP Passport
203-
if (process.env.SAP_PASSPORT && that.dbc?.constructor.name in { HDBDriver: 1, HANAClientDriver: 1 }) {
203+
if (process.env.SAP_PASSPORT && that.dbc?.set) {
204204
const { spanId, traceId } = span.spanContext()
205+
// REVISIT: @sap/dsrpassport uses '0226' for VARPARTOFFSET
205206
// prettier-ignore
206-
let passport = [
207-
/* EYECATCHER */ '2A54482A',
208-
/* VERSION */ '03',
209-
/* LENGTH */ '00E6',
210-
/* TRACELEVEL */ '0000',
211-
/* COMPONENTID */ '2020202020202020202020202020202020202020202020202020202020202020',
212-
/* SERVICE */ '0000',
213-
/* USER */ '2020202020202020202020202020202020202020202020202020202020202020',
214-
/* ACTION */ '20202020202020202020202020202020202020202020202020202020202020202020202020202020',
215-
/* ACTIONTYPE */ '0000',
216-
/* PREVCOMPONENTID */ Buffer.from(span.resource.attributes['service.name'].substr(0, 32).padEnd(32, ' ')).toString('hex'),
217-
/* TRANSACTIONID */ Buffer.from(traceId.toUpperCase()).toString('hex'),
218-
/* CLIENT */ '202020',
219-
/* COMPONENTTYPE */ '0000',
220-
/* ROOTCONTEXTID */ traceId.toUpperCase(),
221-
/* CONNECTIONID */ '0000000000000001' + spanId.toUpperCase(),
222-
/* CONNECTIONCNT */ '00000001',
223-
/* VARPARTCOUNT */ '0000',
224-
/* VARPARTOFFSET */ '0000', // REVISIT: @sap/dsrpassport uses '0226'
225-
/* EYECATCHER */ '2A54482A'
226-
]
227-
passport = passport.join('')
207+
const passport = `${
208+
/* EYECATCHER */ '2A54482A'}${
209+
/* VERSION */ '03'}${
210+
/* LENGTH */ '00E6'}${
211+
/* TRACELEVEL */ '0000'}${
212+
/* COMPONENTID */ '2020202020202020202020202020202020202020202020202020202020202020'}${
213+
/* SERVICE */ '0000'}${
214+
/* USER */ '2020202020202020202020202020202020202020202020202020202020202020'}${
215+
/* ACTION */ '20202020202020202020202020202020202020202020202020202020202020202020202020202020'}${
216+
/* ACTIONTYPE */ '0000'}${
217+
/* PREVCOMPONENTID */ Buffer.from(span.resource.attributes['service.name'].substr(0, 32).padEnd(32, ' ')).toString('hex')}${
218+
/* TRANSACTIONID */ Buffer.from(traceId.toUpperCase()).toString('hex')}${
219+
/* CLIENT */ '202020'}${
220+
/* COMPONENTTYPE */ '0000'}${
221+
/* ROOTCONTEXTID */ traceId.toUpperCase()}${
222+
/* CONNECTIONID */ '0000000000000001' + spanId.toUpperCase()}${
223+
/* CONNECTIONCNT */ '00000001'}${
224+
/* VARPARTCOUNT */ '0000'}${
225+
/* VARPARTOFFSET */ '0000' }${
226+
/* EYECATCHER */ '2A54482A'}`
228227
LOG._debug && LOG.debug('Setting SAP Passport:', passport)
229228
that.dbc.set({ SAP_PASSPORT: passport })
230229
}

lib/utils.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ function _require(name) {
162162
name = Array.isArray(name) ? name[0] : name
163163
try {
164164
return require(name.startsWith('./') ? cds.utils.path.join(cds.root, name) : name)
165-
} catch (e) {
166-
e.message = `Cannot find module '${name}'. Make sure to install it with 'npm i ${name}'\n` + e.message
167-
throw e
165+
} catch (err) {
166+
err.message = `Cannot find module '${name}'. Make sure to install it with 'npm i ${name}'\n` + err.message
167+
throw err
168168
}
169169
}
170170

0 commit comments

Comments
 (0)