Skip to content

Commit

Permalink
feat: predefined kind telemetry-to-otlp (#247)
Browse files Browse the repository at this point in the history
closes #243
  • Loading branch information
sjvans authored Nov 6, 2024
1 parent a6ca3ad commit 6fa9c56
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).

### Added

- Predefined kind `telemetry-to-otlp` that creates exporters based on OTLP exporter configuration via environment variables
- Experimental!: Propagate W3C trace context to SAP HANA via session context `SAP_PASSPORT`
- Enable via environment variable `SAP_PASSPORT`
- If `@opentelemetry/instrumentation-runtime-node` is in the project's dependencies but not in `cds.env.requires.telemetry.instrumentations`, it is registered automatically
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Documentation can be found at [cap.cloud.sap](https://cap.cloud.sap/docs) and [o
- [`telemetry-to-dynatrace`](#telemetry-to-dynatrace)
- [`telemetry-to-cloud-logging`](#telemetry-to-cloud-logging)
- [`telemetry-to-jaeger`](#telemetry-to-jaeger)
- [`telemetry-to-otlp`](#telemetry-to-otlp)
- [Detailed Configuration Options](#detailed-configuration-options)
- [Configuration Pass Through](#configuration-pass-through)
- [Instrumentations](#instrumentations)
Expand Down Expand Up @@ -262,6 +263,19 @@ Run Jaeger locally via [docker](https://www.docker.com):
- With this, no custom credentials are needed
- Open `localhost:16686` to see the traces

### `telemetry-to-otlp`

Exports traces and metrics to an OTLP/gRPC or OTLP/HTTP endpoint based on [environment variables](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter).

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

Required additional dependencies (`* = grpc|proto|http`):
- `@grpc/grpc-js` (in case of OTLP/gRPC)
- `@opentelemetry/exporter-trace-otlp-*`
- `@opentelemetry/exporter-metrics-otlp-*`

Please note that `@cap-js/telemetry` does not validate the configuration via environment variables!



## Detailed Configuration Options
Expand Down
23 changes: 21 additions & 2 deletions lib/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const cds = require('@sap/cds')
const LOG = cds.log('telemetry')

const { metrics } = require('@opentelemetry/api')
const { getEnv, getEnvWithoutDefaults } = require('@opentelemetry/core')
const { Resource } = require('@opentelemetry/resources')
const {
AggregationTemporality,
Expand All @@ -13,21 +14,39 @@ const {

const { getDynatraceMetadata, getCredsForDTAsUPS, getCredsForCLSAsUPS, augmentCLCreds, _require } = require('../utils')

const _protocol2module = {
grpc: '@opentelemetry/exporter-metrics-otlp-grpc',
'http/protobuf': '@opentelemetry/exporter-metrics-otlp-proto',
'http/json': '@opentelemetry/exporter-metrics-otlp-http'
}

function _getExporter() {
let {
kind,
metrics: { exporter: metricsExporter },
credentials
} = cds.env.requires.telemetry

// for kind telemetry-to-otlp based on env vars
if (metricsExporter === 'env') {
const otlp_env = getEnvWithoutDefaults()
const dflt_env = getEnv()
const protocol =
otlp_env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL ??
otlp_env.OTEL_EXPORTER_OTLP_PROTOCOL ??
dflt_env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL ??
dflt_env.OTEL_EXPORTER_OTLP_PROTOCOL
metricsExporter = { module: _protocol2module[protocol], class: 'OTLPMetricExporter' }
}

// use _require for better error message
const metricsExporterModule =
metricsExporter.module === '@cap-js/telemetry' ? require('../exporter') : _require(metricsExporter.module)
if (!metricsExporterModule[metricsExporter.class])
throw new Error(`Unknown metrics exporter "${metricsExporter.class}" in module "${metricsExporter.module}"`)
const metricsConfig = { ...(metricsExporter.config || {}) }

if (kind.match(/dynatrace/)) {
if (kind.match(/to-dynatrace$/)) {
if (!credentials) credentials = getCredsForDTAsUPS()
if (!credentials) throw new Error('No Dynatrace credentials found.')
metricsConfig.url ??= `${credentials.apiurl}/v2/otlp/v1/metrics`
Expand All @@ -41,7 +60,7 @@ function _getExporter() {
metricsConfig.headers.authorization ??= `Api-Token ${token}`
}

if (kind.match(/cloud-logging/)) {
if (kind.match(/to-cloud-logging$/)) {
if (!credentials) credentials = getCredsForCLSAsUPS()
if (!credentials) throw new Error('No SAP Cloud Logging credentials found.')
augmentCLCreds(credentials)
Expand Down
25 changes: 22 additions & 3 deletions lib/tracing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const cds = require('@sap/cds')
const LOG = cds.log('telemetry')

const { trace } = require('@opentelemetry/api')
const { getEnv, getEnvWithoutDefaults } = require('@opentelemetry/core')
const { Resource } = require('@opentelemetry/resources')
const { BatchSpanProcessor, SimpleSpanProcessor, SamplingDecision } = require('@opentelemetry/sdk-trace-base')
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node')
Expand Down Expand Up @@ -69,21 +70,39 @@ function _getPropagator() {
return new core.CompositePropagator({ propagators })
}

const _protocol2module = {
grpc: '@opentelemetry/exporter-trace-otlp-grpc',
'http/protobuf': '@opentelemetry/exporter-trace-otlp-proto',
'http/json': '@opentelemetry/exporter-trace-otlp-http'
}

function _getExporter() {
let {
kind,
tracing: { exporter: tracingExporter },
credentials
} = cds.env.requires.telemetry

// for kind telemetry-to-otlp based on env vars
if (tracingExporter === 'env') {
const otlp_env = getEnvWithoutDefaults()
const dflt_env = getEnv()
const protocol =
otlp_env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ??
otlp_env.OTEL_EXPORTER_OTLP_PROTOCOL ??
dflt_env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ??
dflt_env.OTEL_EXPORTER_OTLP_PROTOCOL
tracingExporter = { module: _protocol2module[protocol], class: 'OTLPTraceExporter' }
}

// use _require for better error message
const tracingExporterModule =
tracingExporter.module === '@cap-js/telemetry' ? require('../exporter') : _require(tracingExporter.module)
if (!tracingExporterModule[tracingExporter.class])
throw new Error(`Unknown tracing exporter "${tracingExporter.class}" in module "${tracingExporter.module}"`)
const tracingConfig = { ...(tracingExporter.config || {}) }

if (kind.match(/dynatrace/)) {
if (kind.match(/to-dynatrace$/)) {
if (!credentials) credentials = getCredsForDTAsUPS()
if (!credentials) throw new Error('No Dynatrace credentials found')
tracingConfig.url ??= `${credentials.apiurl}/v2/otlp/v1/traces`
Expand All @@ -96,7 +115,7 @@ function _getExporter() {
tracingConfig.headers.authorization ??= `Api-Token ${token}`
}

if (kind.match(/cloud-logging/)) {
if (kind.match(/to-cloud-logging$/)) {
if (!credentials) credentials = getCredsForCLSAsUPS()
if (!credentials) throw new Error('No SAP Cloud Logging credentials found')
augmentCLCreds(credentials)
Expand Down Expand Up @@ -127,7 +146,7 @@ module.exports = resource => {
}
const via_one_agent =
process.env.DT_NODE_PRELOAD_OPTIONS &&
cds.env.requires.telemetry.kind.match(/dynatrace/) &&
cds.env.requires.telemetry.kind.match(/to-dynatrace$/) &&
!hasDependency('@opentelemetry/exporter-trace-otlp-proto')
if (via_one_agent) {
// if Dynatrace OneAgent is present, no exporter is needed
Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@
"class": "OTLPTraceExporter"
}
}
},
"telemetry-to-otlp": {
"tracing": {
"exporter": "env"
},
"metrics": {
"exporter": "env"
}
}
}
}
Expand Down

0 comments on commit 6fa9c56

Please sign in to comment.