Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/chai-5.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sjvans authored Jan 4, 2024
2 parents 9f48fcd + 0574af7 commit b968a33
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 31 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 0.0.1-alpha.1 - 2023-12-18
## Version 0.0.1 - 2024-01-04

### Added

Expand Down
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ See [Getting Started](https://cap.cloud.sap/docs/get-started) on how to jumpstar

## Setup

Add `@cap-js/telemetry` to your dependencies via `npm add @cap-js/telemetry`. That's all.
Simply add `@cap-js/telemetry` to your dependencies via `npm add @cap-js/telemetry` and you will find telemetry output written to the console.
See [Predefined Kinds](#predefined-kinds) for additional dependencies you need to bring yourself when exporting to Dynatrace, Jaeger, etc.

The plugin can be disabled by setting environment variable `NO_TELEMETRY` to something truthy.
Additionally, tracing for individual services can be diabled by annotating the service with `@cds.tracing: false`.

Database tracing is currently limited to @cap-js/sqlite and @cap-js/hana.

See [Predefined Kinds](#predefined-kinds) for additional dependencies you need to bring yourself when exporting to Dynatrace or Jaeger.
Database tracing is currently limited to [@cap-js/sqlite](https://www.npmjs.com/package/@cap-js/sqlite) and [@cap-js/hana](https://www.npmjs.com/package/@cap-js/hana).



Expand All @@ -44,22 +42,22 @@ Prints traces and metrics to the console like so:
[telemetry] - elapsed times:
0.00 → 2.85 = 2.85 ms GET /odata/v4/processor/Incidents
0.47 → 1.24 = 0.76 ms ProcessorService - READ ProcessorService.Incidents
0.78 → 1.17 = 0.38 ms db - READ sap.capire.incidents.Incidents
0.78 → 1.17 = 0.38 ms db - READ ProcessorService.Incidents
0.97 → 1.06 = 0.09 ms @cap-js/sqlite - prepare SELECT json_object('ID',ID,'createdAt',createdAt,'creat…
1.10 → 1.13 = 0.03 ms @cap-js/sqlite - stmt.all SELECT json_object('ID',ID,'createdAt',createdAt,'crea…
1.27 → 1.88 = 0.61 ms ProcessorService - READ ProcessorService.Incidents.drafts
1.54 → 1.86 = 0.32 ms db - READ sap.capire.incidents.Incidents
1.54 → 1.86 = 0.32 ms db - READ ProcessorService.Incidents.drafts
1.74 → 1.78 = 0.04 ms @cap-js/sqlite - prepare SELECT json_object('ID',ID,'DraftAdministrativeData_Dra…
1.81 → 1.85 = 0.04 ms @cap-js/sqlite - stmt.all SELECT json_object('ID',ID,'DraftAdministrativeData_Dr…
```

No additional dependencies needed.
The default kind in both development and production.
No additional dependencies are needed.
This is the default kind in both development and production.

### `telemetry-to-dyntrace`

Exports traces and metrics to Dynatrace.
Hence, Dynatrace is required and the app must be bound to a Dynatrace instance.
Hence, a Dynatrace instance is required and the app must be bound to that Dynatrace instance.

Use via `cds.requires.telemetry.kind = 'to-dyntrace'`.

Expand All @@ -71,6 +69,7 @@ Required additional dependencies:
The necessary scope for exporting metrics (`metrics.ingest`) is not part of the standard `apitoken` and must be requested.
This can only be done via binding to a "managed service instance", i.e., not a user-provided service instance.
There are two config options: (1) `rest_apitoken` (to be deprecated) and (2) `metrics_apitoken` via `tokens`.

Example (you only need option 1 or option 2):
```yaml
requires:
Expand All @@ -87,7 +86,7 @@ requires:
- metrics.ingest
```
In Dynatrace, you need to ensure that the following two features are enabled:
In Dynatrace itself, you need to ensure that the following two features are enabled:
1. OpenTelemetry Node.js Instrumentation agent support:
- From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
- Find and turn on OpenTelemetry Node.js Instrumentation agent support.
Expand All @@ -97,11 +96,11 @@ In Dynatrace, you need to ensure that the following two features are enabled:
### `telemetry-to-jaeger`

Exports traces to Jaeger. Jaeger does not support metrics!
Exports traces to Jaeger.

Use via `cds.requires.telemetry.kind = 'to-jaeger'`.

Required additional dependencies:
Required additional dependencies (As Jaeger does not support metrics, only a trace exporter is needed.):
- `@opentelemetry/exporter-trace-otlp-proto`

Provide custom credentials like so:
Expand All @@ -123,7 +122,7 @@ Provide custom credentials like so:
}
```

Run Jaeger locally:
Run Jaeger locally via [docker](https://www.docker.com):
- Run `docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -e COLLECTOR_OTLP_ENABLED=true -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest`
- Open `localhost:16686` to see the traces

Expand Down
5 changes: 3 additions & 2 deletions lib/tracing/cds.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = () => {
})
cds.Service.prototype.handle = wrap(handle, {
wrapper: function (req) {
if (!cds.env.requires.telemetry.tracing.tx && req.event in { BEGIN: 1, COMMIT: 1, ROLLBACK: 1 })
if (!cds.env.requires.telemetry.tracing._tx && req.event in { BEGIN: 1, COMMIT: 1, ROLLBACK: 1 })
return handle.apply(this, arguments)
return trace(req, handle, this, arguments /*, { loggerName: LOG.label }*/)
}
Expand All @@ -55,6 +55,7 @@ module.exports = () => {
}
})

// REVISIT: inofficial @cds.tracing: true/ false
cds.on('serving', service => {
// Do trace event handler either when
// Logging is enabled for all and it is not explicitly disabled for this service
Expand Down Expand Up @@ -92,7 +93,7 @@ module.exports = () => {
})
dbService.prototype.exec = wrap(exec, {
wrapper: function (sql) {
if (!cds.env.requires.telemetry.tracing.tx && sql in { BEGIN: 1, COMMIT: 1, ROLLBACK: 1 })
if (!cds.env.requires.telemetry.tracing._tx && sql in { BEGIN: 1, COMMIT: 1, ROLLBACK: 1 })
return exec.apply(this, arguments)
return trace(`${impl} - exec ${sql}`, exec, this, arguments /*, { loggerName: LOG.label }*/)
}
Expand Down
3 changes: 3 additions & 0 deletions lib/tracing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ module.exports = resource => {
// REVISIT: better way to set/ pass tracer?
cds._telemetry.tracer = trace.getTracer(cds._telemetry.name, cds._telemetry.version)

// REVISIT: only start tracing once served
cds.on('served', () => { cds._telemetry.tracer._active = true })

/*
* add CAP instrumentations
*/
Expand Down
28 changes: 19 additions & 9 deletions lib/tracing/trace.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
const cds = require('@sap/cds')

const otel = require('@opentelemetry/api')
const { SpanKind, SpanStatusCode, ROOT_CONTEXT } = otel
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions')

const MASK_HEADERS = (cds.env.log.mask_headers || ['/authorization/i', '/cookie/i']).map(s => {
const parts = s.match(/\/(.+)\/(\w*)/)
if (parts) return new RegExp(parts[1], parts[2])
return new RegExp(s)
})

const otel = require('@opentelemetry/api')
const { SpanKind, SpanStatusCode, ROOT_CONTEXT } = otel
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions')
const HRTIME = cds.env.requires.telemetry.tracing._hrtime
const EPOCH_OFFSET = Math.floor(Date.now() / 1000) - process.hrtime()[0]

// returns [seconds, nanoseconds] since unix epoch
function _hrtime() {
const hrtime = process.hrtime()
return [hrtime[0] + EPOCH_OFFSET, hrtime[1]]
}

function _getParentSpan(HRTIME) {
function _getParentSpan() {
if (!cds.context) return
if (!cds.context._otelctx) {
cds.context._otelKey = otel.createContextKey(cds.context.id)
cds.context._otelctx = otel.context.active()
const parent = otel.trace.getSpan(cds.context._otelctx)
if (HRTIME && parent && !parent.__adjusted) {
parent.startTime = process.hrtime()
parent.startTime = _hrtime()
parent.__adjusted = true
}
if (!parent?._is_async) cds.context._otelctx.setValue(cds.context._otelKey, parent)
Expand Down Expand Up @@ -156,12 +165,13 @@ function _setAttributes(span, attributes) {
}

function trace(name, fn, targetObj, args, options = {}) {
const HRTIME = cds.env.requires.telemetry.tracing.hrtime
// REVISIT: only start tracing once served
if (!cds._telemetry.tracer._active) return fn.apply(targetObj, args)

/*
* create span
*/
const parentSpan = _getParentSpan(HRTIME)
const parentSpan = _getParentSpan()
const isAsync = parentSpan?._is_async && !parentSpan?.name.match(/cds\.spawn/)
const ctx = isAsync
? ROOT_CONTEXT
Expand All @@ -171,7 +181,7 @@ function trace(name, fn, targetObj, args, options = {}) {
const spanOptions = {
kind: _determineKind(targetObj, name?.phase, isAsync, options)
}
if (HRTIME) spanOptions.startTime = process.hrtime()
if (HRTIME) spanOptions.startTime = _hrtime()
if (isAsync) {
spanOptions.links = [{ context: parentSpan.spanContext() }]
spanOptions.parent = undefined
Expand Down Expand Up @@ -207,7 +217,7 @@ function trace(name, fn, targetObj, args, options = {}) {
throw e
}
const onDone = () => {
if (span.status.code !== SpanStatusCode.UNSET && !span.ended) span.end(HRTIME ? process.hrtime() : undefined)
if (span.status.code !== SpanStatusCode.UNSET && !span.ended) span.end(HRTIME ? _hrtime() : undefined)
}

try {
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cap-js/telemetry",
"version": "0.0.1-alpha.1",
"version": "0.0.1",
"description": "CDS plugin providing automatic OpenTelemetry instrumentation.",
"repository": "cap-js/telemetry",
"author": "SAP SE (https://www.sap.com)",
Expand Down Expand Up @@ -62,8 +62,8 @@
"propagators": [
"W3CTraceContextPropagator"
],
"hrtime": true,
"tx": false
"_hrtime": true,
"_tx": false
},
"metrics": {
"config": {
Expand Down Expand Up @@ -103,7 +103,6 @@
},
"telemetry-to-jaeger": {
"tracing": {
"hrtime": false,
"exporter": {
"module": "@opentelemetry/exporter-trace-otlp-proto",
"class": "OTLPTraceExporter"
Expand Down
9 changes: 8 additions & 1 deletion test/bookshop/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"dependencies": {
"@cap-js/telemetry": "*"
},
"_cds": {
"requires": {
"telemetry": {
"kind": "telemetry-to-jaeger"
}
}
}
}
}
6 changes: 6 additions & 0 deletions test/bookshop/test.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@host = http://localhost:4004

###

GET {{host}}/odata/v4/admin/Books
Authorization: Basic alice:wonderland

0 comments on commit b968a33

Please sign in to comment.