Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
08837fe
Add telemetry-to-caas kind for CaaS (Collector as a Service) support
vkozyura Jun 22, 2026
79eaadc
Add certificate-service detection for CaaS mTLS
vkozyura Jun 22, 2026
12223ce
Add mTLS support for CaaS via caas-mtls-creds user-provided service
vkozyura Jun 22, 2026
2da163c
Fix mTLS config - use httpAgentOptions
vkozyura Jun 22, 2026
46b0129
Monkey-patch https.request for CaaS mTLS
vkozyura Jun 22, 2026
a10eb6b
Remove package-lock.json from tracking
vkozyura Jun 22, 2026
41f70d5
Use agentOptions for CaaS mTLS instead of monkey-patching
vkozyura Jun 22, 2026
6de322b
Fix mTLS config: use httpAgentOptions instead of agentOptions
vkozyura Jun 23, 2026
10140df
Fix OTLP URL: append signal paths (/v1/traces, /v1/metrics)
vkozyura Jun 23, 2026
d592028
Merge branch 'main' into feature/caas-support
vkozyura Jun 30, 2026
7aca1b4
rem hard-coded names and debug logs
vkozyura Jun 30, 2026
a3d0f5e
fix env vars
vkozyura Jun 30, 2026
00c635d
add readme, changelog and test
vkozyura Jun 30, 2026
e4dbdf6
small fix
vkozyura Jun 30, 2026
11efddb
use cds.env.requires.telemetry.x509 for certificate
vkozyura Jul 21, 2026
8d2eb24
add logging
vkozyura Jul 22, 2026
7c02946
add tracing to package.json
vkozyura Jul 22, 2026
e74ef96
fix test
vkozyura Jul 22, 2026
a7cfae0
Merge branch 'main' into feature/caas-support
vkozyura Aug 17, 2026
67a1488
Update lib/tracing/index.js
vkozyura Aug 17, 2026
566a6bb
Update lib/metrics/index.js
vkozyura Aug 17, 2026
f7e4e68
Update lib/logging/index.js
vkozyura Aug 17, 2026
4c8624e
Update lib/utils.js
vkozyura Aug 17, 2026
51cd815
remove backwars compat for credentials
vkozyura Aug 17, 2026
6e883e2
simplify mtlsCreds check
vkozyura Aug 17, 2026
a86df66
update warning
vkozyura Aug 17, 2026
7a39ade
readme
vkozyura Aug 17, 2026
e5bff75
testing certificate renewal w/o re-assigning env vars
vkozyura Aug 17, 2026
9967e8c
fix test
vkozyura Aug 17, 2026
0e61476
fix if no logging lib
vkozyura Aug 17, 2026
c160fbc
use ztis
vkozyura Aug 22, 2026
27a7049
fixes and logs
vkozyura Aug 22, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
- Support for OpenTelemetry SDK 2.0
- `@opentelemetry/instrumentation-undici` added to the list of default instrumentations
- Support for `@sap/cds^10`
- Support for `telemetry-to-caas` kind for CaaS (Collector as a Service)

### Changed

Expand Down
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Documentation can be found at [cap.cloud.sap](https://cap.cloud.sap/docs) and [o
- [`telemetry-to-console`](#telemetry-to-console)
- [`telemetry-to-dynatrace`](#telemetry-to-dynatrace)
- [`telemetry-to-cloud-logging`](#telemetry-to-cloud-logging)
- [`telemetry-to-caas`](#telemetry-to-caas)
- [`telemetry-to-jaeger`](#telemetry-to-jaeger)
- [`telemetry-to-otlp`](#telemetry-to-otlp)
- [Detailed Configuration Options](#detailed-configuration-options)
Expand Down Expand Up @@ -200,7 +201,7 @@ Please note that in order for logs to be exported via OpenTelemetry, `cds.log()`

## Predefined Kinds

There are five predefined kinds as follows:
There are six predefined kinds as follows:


### `telemetry-to-console`
Expand Down Expand Up @@ -283,12 +284,52 @@ In order to receive OpenTelemetry credentials in the binding to the SAP Cloud Lo

If you are binding your app to SAP Cloud Logging via a [user-provided service instance](https://docs.cloudfoundry.org/devguide/services/user-provided.html), make sure that it has the tag `Cloud Logging`.

> Tip: To add the required tag to an existing user-provided service, you can use:
> Tip: To add the required tag to an existing user-provided service, you can use:
> ```
> cf update-user-provided-service {service-name} -t "Cloud Logging"
> ```
> For detailed information about binding resolution in CAP, consult [`cds.connect()` → Service Bindings](https://cap.cloud.sap/docs/node.js/cds-connect#service-bindings).

### `telemetry-to-caas`

Exports traces and metrics to CaaS (Collector as a Service).
CaaS acts as a managed OpenTelemetry Collector that can route telemetry data to downstream backends like SAP Cloud Logging.

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

Required additional dependencies:
- `@opentelemetry/exporter-trace-otlp-proto`
- `@opentelemetry/exporter-metrics-otlp-proto`

CaaS requires mTLS authentication with SAP-signed certificates. You need to:

1. **Bind the CaaS service** to your app with subject/issuer configuration:
```yaml
# mta.yaml
requires:
- name: my-caas-instance
parameters:
config:
subject: "CN=my-app,..."
issuer: "CN=SAP PKI Certificate Service Client CA,..."
```

2. **Provide mTLS credentials** via environment variables (base64 encoded):
```yaml
# mta.yaml
properties:
CDS_REQUIRES_TELEMETRY_X509_CERT: '<base64-encoded-certificate-chain>'
CDS_REQUIRES_TELEMETRY_X509_KEY: '<base64-encoded-private-key>'
```

Or set directly via Cloud Foundry CLI:
```bash
cf set-env my-app CDS_REQUIRES_TELEMETRY_X509_CERT "<base64-cert>"
cf set-env my-app CDS_REQUIRES_TELEMETRY_X509_KEY "<base64-key>"
```

The mTLS certificate must be SAP-signed through the BTP Certificate Service. Certificates can be created with validity from 7 days up to 1 year and must be renewed before expiration. For detailed certificate setup and renewal instructions, refer to your project's certificate management documentation.

### `telemetry-to-jaeger`

Exports traces to Jaeger.
Expand Down
5 changes: 4 additions & 1 deletion cds-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@

if (!!process.env.NO_TELEMETRY && process.env.NO_TELEMETRY !== 'false') return

require('./lib')()
// Setup is async to support ZTI initialization (waits for SVID files)
require('./lib')().catch(err => {
cds.log('telemetry')._error && cds.log('telemetry').error('Telemetry setup failed:', err)
})
})()
23 changes: 20 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { registerInstrumentations } = require('@opentelemetry/instrumentation')
const tracing = require('./tracing')
const metrics = require('./metrics')
const logging = require('./logging')
const { getDiagLogLevel, getResource, hasDependency, _require } = require('./utils')
const { getDiagLogLevel, getResource, hasDependency, initializeZTI, _require } = require('./utils')

function _getInstrumentations() {
const _instrumentations = cds.env.requires.telemetry.instrumentations
Expand Down Expand Up @@ -89,13 +89,23 @@ function _getInstrumentations() {
return instrumentations
}

function setup_standalone() {
async function setup_standalone() {
// set logger and propagate log level
diag.setLogger(cds.log('telemetry'), getDiagLogLevel())

// create resource
const resource = getResource()

// Initialize ZTI if configured (blocks until SVID files ready)
// Must complete BEFORE creating exporters so credentials are available
try {
await initializeZTI()
} catch (err) {
LOG._error && LOG.error('Failed to initialize ZTI for CaaS mTLS:', err)
// Don't throw - let telemetry continue without ZTI
// The warning will be logged when augmentCaaSCreds() is called
}

// setup tracing, metrics, and logging
const tracerProvider = tracing(resource)
const meterProvider = metrics(resource)
Expand All @@ -110,7 +120,14 @@ function setup_standalone() {
})
}

function setup_with_calm() {
async function setup_with_calm() {
// Initialize ZTI if configured - must complete before creating exporters
try {
await initializeZTI()
} catch (err) {
LOG._error && LOG.error('Failed to initialize ZTI for CaaS mTLS:', err)
}

// setup tracing, metrics, and logging
tracing()
metrics()
Expand Down
27 changes: 25 additions & 2 deletions lib/logging/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const LOG = cds.log('telemetry')

const { getStringFromEnv } = require('@opentelemetry/core')

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

const _protocol2module = {
grpc: '@opentelemetry/exporter-logs-otlp-grpc',
Expand Down Expand Up @@ -33,7 +33,16 @@ function _getExporter() {
}

// use _require for better error message
const loggingExporterModule = _require(loggingExporter.module)
let loggingExporterModule
try {
loggingExporterModule = _require(loggingExporter.module)
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
LOG._warn && LOG.warn(`Logs exporter module '${loggingExporter.module}' not found. Logging telemetry will be disabled.`)
return null
}
throw err
}
if (!loggingExporterModule[loggingExporter.class])
throw new Error(`Unknown logs exporter "${loggingExporter.class}" in module "${loggingExporter.module}"`)
const config = { ...(loggingExporter.config || {}) }
Expand All @@ -46,6 +55,16 @@ function _getExporter() {
config.credentials ??= credentials.credentials
}

if (kind.match(/to-caas$/)) {
if (!credentials) throw new Error('No CaaS credentials found.')
augmentCaaSCreds(credentials)
// Append /v1/logs to base URL (OTLP exporter expects full URL when config.url is provided)
config.url = credentials.baseUrl + '/v1/logs'
if (credentials.httpAgentOptions) {
config.httpAgentOptions = credentials.httpAgentOptions
}
}

const exporter = new loggingExporterModule[loggingExporter.class](config)
LOG._debug && LOG.debug('Using logs exporter:', exporter)

Expand Down Expand Up @@ -131,6 +150,10 @@ module.exports = resource => {
* create processor
*/
const exporter = _getExporter()
if (!exporter) {
LOG._info && LOG.info('Logging telemetry is disabled due to missing exporter module')
return null
}
const processor =
_getCustomProcessor(exporter) ||
(process.env.NODE_ENV === 'production'
Expand Down
12 changes: 11 additions & 1 deletion lib/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { getStringFromEnv } = require('@opentelemetry/core')
const { resourceFromAttributes } = require('@opentelemetry/resources')
const { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics')

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

const _protocol2module = {
grpc: '@opentelemetry/exporter-metrics-otlp-grpc',
Expand Down Expand Up @@ -74,6 +74,16 @@ function _getExporter() {
config.credentials ??= credentials.credentials
}

if (kind.match(/to-caas$/)) {
if (!credentials) throw new Error('No CaaS credentials found.')
augmentCaaSCreds(credentials)
// Append /v1/metrics to base URL (OTLP exporter expects full URL when config.url is provided)
config.url = credentials.baseUrl + '/v1/metrics'
if (credentials.httpAgentOptions) {
config.httpAgentOptions = credentials.httpAgentOptions
}
}

const exporter = new metricsExporterModule[metricsExporter.class](config)
LOG._debug && LOG.debug('Using metrics exporter:', exporter)
return exporter
Expand Down
11 changes: 11 additions & 0 deletions lib/tracing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
getCredsForDTAsUPS,
getCredsForCLSAsUPS,
augmentCLCreds,
augmentCaaSCreds,
hasDependency,
_require
} = require('../utils')
Expand Down Expand Up @@ -125,6 +126,16 @@ function _getExporter() {
config.credentials ??= credentials.credentials
}

if (kind.match(/to-caas$/)) {
if (!credentials) throw new Error('No CaaS credentials found.')
augmentCaaSCreds(credentials)
// Append /v1/traces to base URL (OTLP exporter expects full URL when config.url is provided)
config.url = credentials.baseUrl + '/v1/traces'
if (credentials.httpAgentOptions) {
config.httpAgentOptions = credentials.httpAgentOptions
}
}

const exporter = new tracingExporterModule[tracingExporter.class](config)
LOG._debug && LOG.debug('Using trace exporter:', exporter)

Expand Down
Loading