Skip to content

Commit 8a9390e

Browse files
authored
feat: Support for adding @opentelemetry/instrumentation-runtime-node (#210)
closes #166
1 parent 7bc069b commit 8a9390e

File tree

7 files changed

+65
-32
lines changed

7 files changed

+65
-32
lines changed

CHANGELOG.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
99
### Added
1010

1111
- Support for SAP Cloud Logging credentials via user-provided service
12+
- Support for adding `@opentelemetry/instrumentation-runtime-node`
13+
- `npm add @opentelemetry/instrumentation-runtime-node`
14+
- to `cds.requires.telemetry.instrumentations`, add:
15+
```json
16+
"instrumentation-runtime-node": {
17+
"class": "RuntimeNodeInstrumentation",
18+
"module": "@opentelemetry/instrumentation-runtime-node"
19+
}
20+
```
1221

1322
### Changed
1423

24+
- Instrumentations are registered after tracing and metrics are set up
25+
1526
### Fixed
1627

1728
### Removed
@@ -52,8 +63,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
5263
### Added
5364

5465
- Support for own, high resolution timestamps
55-
+ Enable via `cds.env.requires.telemetry.tracing.hrtime = true`
56-
+ Enabled by default in development profile
66+
- Enable via `cds.env.requires.telemetry.tracing.hrtime = true`
67+
- Enabled by default in development profile
5768

5869
## Version 0.0.5 - 2024-03-11
5970

@@ -66,10 +77,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
6677
### Changed
6778

6879
- By default, all `system.*` metrics collected by `@opentelemetry/host-metrics` are ignored
69-
+ Disable change via environment variable `HOST_METRICS_RETAIN_SYSTEM=true`
80+
- Disable change via environment variable `HOST_METRICS_RETAIN_SYSTEM=true`
7081
- Metric exporter's property `temporalityPreference` always gets defaulted to `DELTA`
71-
+ Was previously only done for kind `telemetry-to-dynatrace`
72-
+ Set custom value via `cds.env.requires.telemetry.metrics.exporter.config.temporalityPreference`
82+
- Was previously only done for kind `telemetry-to-dynatrace`
83+
- Set custom value via `cds.env.requires.telemetry.metrics.exporter.config.temporalityPreference`
7384

7485
### Fixed
7586

lib/exporter/ConsoleSpanExporter.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ const LOG = cds.log('telemetry')
44
const path = require('path')
55

66
const { ExportResultCode, hrTimeToMilliseconds } = require('@opentelemetry/core')
7+
const {
8+
SEMATTRS_HTTP_TARGET,
9+
SEMATTRS_CODE_FILEPATH,
10+
SEMATTRS_CODE_LINENO
11+
// SEMATTRS_CODE_COLUMN
12+
} = require('@opentelemetry/semantic-conventions')
13+
// REVISIT: SEMATTRS_CODE_COLUMN doesn't yet exist in semantic conventions 1.25
14+
const SEMATTRS_CODE_COLUMN = 'code.column'
715

816
const _padded = v =>
917
`${`${v}`.split('.')[0].padStart(3, ' ')}.${(`${v}`.split('.')[1] || '0').padEnd(2, '0').substring(0, 2)}`
@@ -18,25 +26,25 @@ const _span2line = (span, parentStartTime = 0) => {
1826
let result = `\n ${_padded(start)}${_padded(end)} = ${_padded(duration)} ms`
1927

2028
let name = span.name
21-
if (name.match(/^[A-Z]+$/)) name = name + ' ' + span.attributes['http.target']
29+
if (name.match(/^[A-Z]+$/)) name = name + ' ' + span.attributes[SEMATTRS_HTTP_TARGET]
2230
if (name.length > 80) name = name.substring(0, 79) + '…'
2331

2432
result += ' ' + (span.___indent || '') + name
2533

2634
// REVISIT: what is this for?
27-
if (span.attributes['code.filepath'] !== undefined) {
35+
if (span.attributes[SEMATTRS_CODE_FILEPATH] !== undefined) {
2836
if (
2937
path
30-
.normalize(span.attributes['code.filepath'])
38+
.normalize(span.attributes[SEMATTRS_CODE_FILEPATH])
3139
.match(new RegExp(path.normalize(cds.env._home).replaceAll('\\', '\\\\'), 'g')) &&
32-
!path.normalize(span.attributes['code.filepath']).match(/node_modules/g)
40+
!path.normalize(span.attributes[SEMATTRS_CODE_FILEPATH]).match(/node_modules/g)
3341
) {
3442
result += `: .${path
35-
.normalize(span.attributes['code.filepath'])
43+
.normalize(span.attributes[SEMATTRS_CODE_FILEPATH])
3644
.substring(
3745
path.normalize(cds.env._home).length + 1,
38-
path.normalize(span.attributes['code.filepath']).length
39-
)}:${span.attributes['code.lineno']}:${span.attributes['code.column']}`
46+
path.normalize(span.attributes[SEMATTRS_CODE_FILEPATH]).length
47+
)}:${span.attributes[SEMATTRS_CODE_LINENO]}:${span.attributes[SEMATTRS_CODE_COLUMN]}`
4048
}
4149
}
4250

lib/index.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
const cds = require('@sap/cds')
22

33
const { diag } = require('@opentelemetry/api')
4+
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
5+
const { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions')
46

57
const tracing = require('./tracing')
68
const metrics = require('./metrics')
7-
const { getDiagLogLevel, getResource } = require('./utils')
9+
const { getDiagLogLevel, getResource, _require } = require('./utils')
10+
11+
function _getInstrumentations() {
12+
const instrumentations = []
13+
for (const each of Object.values(cds.env.requires.telemetry.instrumentations)) {
14+
const module = _require(each.module)
15+
if (!module[each.class]) throw new Error(`Unknown instrumentation "${each.class}" in module "${each.module}"`)
16+
instrumentations.push(new module[each.class]({ ...(each.config || {}) }))
17+
}
18+
return instrumentations
19+
}
820

921
module.exports = function () {
1022
// set logger and propagate log level
@@ -14,17 +26,22 @@ module.exports = function () {
1426

1527
// REVISIT: better way to make available?
1628
cds._telemetry = {
17-
name: resource.attributes['service.name'],
18-
version: resource.attributes['service.version']
29+
name: resource.attributes[SEMRESATTRS_SERVICE_NAME],
30+
version: resource.attributes[SEMRESATTRS_SERVICE_VERSION]
1931
}
2032

2133
/*
22-
* add tracing
34+
* setup tracing
2335
*/
2436
tracing(resource)
2537

2638
/*
27-
* add metrics
39+
* setup metrics
2840
*/
2941
metrics(resource)
42+
43+
/*
44+
* register instrumentations
45+
*/
46+
registerInstrumentations({ instrumentations: _getInstrumentations() })
3047
}

lib/tracing/index.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const LOG = cds.log('telemetry')
33

44
const { trace } = require('@opentelemetry/api')
55
const { Resource } = require('@opentelemetry/resources')
6-
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
76
const { BatchSpanProcessor, SimpleSpanProcessor, SamplingDecision } = require('@opentelemetry/sdk-trace-base')
87
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node')
98

@@ -63,16 +62,6 @@ function _getPropagator() {
6362
return new core.CompositePropagator({ propagators })
6463
}
6564

66-
function _getInstrumentations() {
67-
const instrumentations = []
68-
for (const each of Object.values(cds.env.requires.telemetry.instrumentations)) {
69-
const module = _require(each.module)
70-
if (!module[each.class]) throw new Error(`Unknown instrumentation "${each.class}" in module "${each.module}"`)
71-
instrumentations.push(new module[each.class]({ ...(each.config || {}) }))
72-
}
73-
return instrumentations
74-
}
75-
7665
function _getExporter() {
7766
let {
7867
kind,
@@ -125,8 +114,6 @@ module.exports = resource => {
125114
resource = new Resource({}).merge(resource).merge(dtmetadata)
126115
tracerProvider = new NodeTracerProvider({ resource, sampler: _getSampler() })
127116
tracerProvider.register({ propagator: _getPropagator() })
128-
const instrumentations = _getInstrumentations()
129-
registerInstrumentations({ tracerProvider, instrumentations })
130117
} else {
131118
LOG._warn && LOG.warn('TracerProvider already initialized by a different module. It will be used as is.')
132119
tracerProvider = tracerProvider.getDelegate()

lib/tracing/trace.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ const cds = require('@sap/cds')
33
const otel = require('@opentelemetry/api')
44
const { SpanKind, SpanStatusCode, ROOT_CONTEXT } = otel
55
const {
6+
SEMATTRS_HTTP_TARGET,
67
SEMATTRS_CODE_FUNCTION,
78
SEMATTRS_CODE_FILEPATH,
89
SEMATTRS_CODE_NAMESPACE,
910
SEMATTRS_CODE_LINENO,
10-
SEMATTRS_CODE_COLUMN,
11+
// SEMATTRS_CODE_COLUMN,
1112
SEMATTRS_DB_SYSTEM,
1213
SEMATTRS_DB_NAME,
1314
SEMATTRS_DB_USER,
@@ -18,6 +19,8 @@ const {
1819
SEMATTRS_DB_OPERATION,
1920
SEMATTRS_DB_SQL_TABLE
2021
} = require('@opentelemetry/semantic-conventions')
22+
// REVISIT: SEMATTRS_CODE_COLUMN doesn't yet exist in semantic conventions 1.25
23+
const SEMATTRS_CODE_COLUMN = 'code.column'
2124

2225
const { _hrnow } = require('../utils')
2326

@@ -49,7 +52,8 @@ function _getParentSpan() {
4952
// root span gets request attributes
5053
_setAttributes(parent, _getRequestAttributes())
5154
if (HRTIME) parent.startTime = cds.context.http?.req?.__hrnow || _hrnow()
52-
if (ADJUST_ROOT_NAME && parent.attributes['http.target']) parent.name += ' ' + parent.attributes['http.target']
55+
if (ADJUST_ROOT_NAME && parent.attributes[SEMATTRS_HTTP_TARGET])
56+
parent.name += ' ' + parent.attributes[SEMATTRS_HTTP_TARGET]
5357
}
5458
if (!parent?._is_async) cds.context._otelctx.setValue(cds.context._otelKey, parent)
5559
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@opentelemetry/exporter-trace-otlp-proto": "^0.52.1",
4040
"@opentelemetry/host-metrics": "^0.35.0",
4141
"@opentelemetry/instrumentation": "^0.52.1",
42+
"@opentelemetry/instrumentation-runtime-node": "^0.6.0",
4243
"@sap/cds-mtxs": "^2.0.5",
4344
"axios": "^1.6.7",
4445
"chai": "^4.4.1",

test/bookshop/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"@cap-js/telemetry": "*",
55
"@cap-js/sqlite": "*",
66
"@opentelemetry/host-metrics": "*",
7+
"@opentelemetry/instrumentation-runtime-node": "*",
78
"@sap/cds-mtxs": "*"
89
},
910
"cds": {
@@ -19,6 +20,10 @@
1920
"/odata/v4/admin/Authors"
2021
]
2122
}
23+
},
24+
"instrumentation-runtime-node": {
25+
"class": "RuntimeNodeInstrumentation",
26+
"module": "@opentelemetry/instrumentation-runtime-node"
2227
}
2328
},
2429
"_tracing": {

0 commit comments

Comments
 (0)