From 80f9f4fcaafa650007b12a1601819b85e72f7713 Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 3 Dec 2025 11:43:33 +0100 Subject: [PATCH 01/12] feat: add logging --- lib/utils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index ff38627..abb2e5f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -40,6 +40,8 @@ function getResource() { LOG._info && LOG.info('Unable to require package.json to resolve app name and version due to error:', err) } + // TODO: If we let PKG.name override the VCAP_APPLICATION.name + // TODO: We end up with my-app, while the name in the standarad deployment is my-app-srv const name = PKG?.name || VCAP_APPLICATION?.name || 'CAP Application' const version = PKG?.version || VCAP_APPLICATION?.application_version || '1.0.0' @@ -116,6 +118,9 @@ function getCredsForCLSAsUPS() { const vcap = JSON.parse(process.env.VCAP_SERVICES) let ups + LOG.debug('Trying to find credentials for Cloud Logging in VCAP SERVICES') + LOG.debug('VCAP SERVICES:', JSON.stringify(vcap, null, 2)) + // to support connection via user-provided services, the instance must have either tag "cloud-logging" or "Cloud Logging" ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) if (ups) return ups.credentials From 4073b06c7cffc96b3eb4e344b4d0c2103cf30251 Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 3 Dec 2025 12:03:36 +0100 Subject: [PATCH 02/12] feat: add more logs --- lib/metrics/index.js | 6 ++++++ lib/utils.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/metrics/index.js b/lib/metrics/index.js index 28c001f..c6378fb 100644 --- a/lib/metrics/index.js +++ b/lib/metrics/index.js @@ -27,6 +27,8 @@ function _getExporter() { credentials } = cds.env.requires.telemetry + cds.log('otel-debug').debug('Configured credentials: ', credentials) + // for kind telemetry-to-otlp based on env vars if (metricsExporter === 'env') { const cstm_env = getEnvWithoutDefaults() @@ -48,6 +50,8 @@ function _getExporter() { throw new Error(`Unknown metrics exporter "${metricsExporter.class}" in module "${metricsExporter.module}"`) const config = { ...(metricsExporter.config || {}) } + cds.log('otel-debug').debug('Metrics exporter kind:', kind) + if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() if (!credentials) throw new Error('No Dynatrace credentials found.') @@ -63,6 +67,8 @@ function _getExporter() { } if (kind.match(/to-cloud-logging$/)) { + cds.log('otel-debug').debug('Getting credentials?', !!credentials) + if (!credentials) credentials = getCredsForCLSAsUPS() if (!credentials) throw new Error('No SAP Cloud Logging credentials found.') augmentCLCreds(credentials) diff --git a/lib/utils.js b/lib/utils.js index abb2e5f..748d195 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -118,8 +118,8 @@ function getCredsForCLSAsUPS() { const vcap = JSON.parse(process.env.VCAP_SERVICES) let ups - LOG.debug('Trying to find credentials for Cloud Logging in VCAP SERVICES') - LOG.debug('VCAP SERVICES:', JSON.stringify(vcap, null, 2)) + cds.log('otel-debug').debug('Trying to find credentials for Cloud Logging in VCAP SERVICES') + cds.log('otel-debug').debug('VCAP SERVICES:', JSON.stringify(vcap, null, 2)) // to support connection via user-provided services, the instance must have either tag "cloud-logging" or "Cloud Logging" ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) From 8862664c624162ab544ebc8d47eadaaf1c7920b7 Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 10:02:33 +0100 Subject: [PATCH 03/12] docs: add tip for how to add the required tag to an existing service --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9faac36..117fc37 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,8 @@ 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 either tag `cloud-logging` or `Cloud Logging`. +> Tip: You can use `cf update-user-provided-service {service-name} -t "cloud-logging"` to add the tag to an existing user provided service. + ### `telemetry-to-jaeger` Exports traces to Jaeger. From 058a46b58013db00fb136839be42a0e9179db8de Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 10:04:04 +0100 Subject: [PATCH 04/12] docs: add tip for how to add the required tag to an existing service --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 117fc37..66528ab 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,10 @@ 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 either tag `cloud-logging` or `Cloud Logging`. -> Tip: You can use `cf update-user-provided-service {service-name} -t "cloud-logging"` to add the tag to an existing user provided service. +> 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" +> ``` ### `telemetry-to-jaeger` From dbea2eca5ddb2c158acc7db67e10eb1346a9531c Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 10:42:41 +0100 Subject: [PATCH 05/12] chore: remove debug logs --- lib/metrics/index.js | 6 ------ lib/utils.js | 3 --- 2 files changed, 9 deletions(-) diff --git a/lib/metrics/index.js b/lib/metrics/index.js index c6378fb..28c001f 100644 --- a/lib/metrics/index.js +++ b/lib/metrics/index.js @@ -27,8 +27,6 @@ function _getExporter() { credentials } = cds.env.requires.telemetry - cds.log('otel-debug').debug('Configured credentials: ', credentials) - // for kind telemetry-to-otlp based on env vars if (metricsExporter === 'env') { const cstm_env = getEnvWithoutDefaults() @@ -50,8 +48,6 @@ function _getExporter() { throw new Error(`Unknown metrics exporter "${metricsExporter.class}" in module "${metricsExporter.module}"`) const config = { ...(metricsExporter.config || {}) } - cds.log('otel-debug').debug('Metrics exporter kind:', kind) - if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() if (!credentials) throw new Error('No Dynatrace credentials found.') @@ -67,8 +63,6 @@ function _getExporter() { } if (kind.match(/to-cloud-logging$/)) { - cds.log('otel-debug').debug('Getting credentials?', !!credentials) - if (!credentials) credentials = getCredsForCLSAsUPS() if (!credentials) throw new Error('No SAP Cloud Logging credentials found.') augmentCLCreds(credentials) diff --git a/lib/utils.js b/lib/utils.js index 748d195..f510aed 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -118,9 +118,6 @@ function getCredsForCLSAsUPS() { const vcap = JSON.parse(process.env.VCAP_SERVICES) let ups - cds.log('otel-debug').debug('Trying to find credentials for Cloud Logging in VCAP SERVICES') - cds.log('otel-debug').debug('VCAP SERVICES:', JSON.stringify(vcap, null, 2)) - // to support connection via user-provided services, the instance must have either tag "cloud-logging" or "Cloud Logging" ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) if (ups) return ups.credentials From 4157d9d62a755b611a964f185160c84dcd3b4c7f Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 11:19:21 +0100 Subject: [PATCH 06/12] feat: adjust matching predicate --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7a77ab..5e1f79a 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ }, "telemetry-to-cloud-logging": { "vcap": { - "label": "cloud-logging" + "tag": "Cloud Logging" }, "tracing": { "exporter": { From 85725f4e5e24eef2df5cd26f0e9dfd28e1e43ec4 Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 11:45:32 +0100 Subject: [PATCH 07/12] docs: update comment & warning --- lib/logging/index.js | 2 +- lib/metrics/index.js | 4 ++-- lib/tracing/index.js | 4 ++-- lib/utils.js | 15 +++++++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/logging/index.js b/lib/logging/index.js index f7e204c..a2fda02 100644 --- a/lib/logging/index.js +++ b/lib/logging/index.js @@ -40,7 +40,7 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found.') + if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials diff --git a/lib/metrics/index.js b/lib/metrics/index.js index 28c001f..2334d4d 100644 --- a/lib/metrics/index.js +++ b/lib/metrics/index.js @@ -50,7 +50,7 @@ function _getExporter() { if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() - if (!credentials) throw new Error('No Dynatrace credentials found.') + if (!credentials) throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') config.url ??= `${credentials.apiurl}/v2/otlp/v1/metrics` config.headers ??= {} // credentials.rest_apitoken?.token is deprecated and only supported for compatibility reasons @@ -64,7 +64,7 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found.') + if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials diff --git a/lib/tracing/index.js b/lib/tracing/index.js index 7d735dd..622b616 100644 --- a/lib/tracing/index.js +++ b/lib/tracing/index.js @@ -106,7 +106,7 @@ function _getExporter() { if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() - if (!credentials) throw new Error('No Dynatrace credentials found') + if (!credentials) throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') config.url ??= `${credentials.apiurl}/v2/otlp/v1/traces` config.headers ??= {} // credentials.rest_apitoken?.token is deprecated and only supported for compatibility reasons @@ -119,7 +119,7 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found') + if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials diff --git a/lib/utils.js b/lib/utils.js index f510aed..d9456ac 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -108,7 +108,11 @@ function getCredsForDTAsUPS() { if (!process.env.VCAP_SERVICES) return const vcap = JSON.parse(process.env.VCAP_SERVICES) - // to support connection via user-provided services, APMs requirement is that the instance name contains "dynatrace" + // Legacy Compat: + // > APMs requirement is that the instance name contains "dynatrace" + // > In addition to matching predicate defined in package.json, also support + // > - name matching /dynatrace/ + // ... in case binding info is available from environment variable VCAP_SERVICES const dt = vcap['user-provided']?.find(b => b.name.match(/dynatrace/)) if (dt) return dt.credentials } @@ -118,15 +122,18 @@ function getCredsForCLSAsUPS() { const vcap = JSON.parse(process.env.VCAP_SERVICES) let ups - // to support connection via user-provided services, the instance must have either tag "cloud-logging" or "Cloud Logging" + // Legacy Compat: + // > In addition to matching predicate defined in package.json, also support + // > - tag: "cloud-logging" + // > - name matching /cloud-logging/ + // ... in case binding info is available from environment variable VCAP_SERVICES ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) if (ups) return ups.credentials - // legacy compat ups = vcap['user-provided']?.find(b => b.name.match(/cloud-logging/)) if (ups) { // prettier-ignore - LOG._warn && LOG.warn('User-provided service instances of SAP Cloud Logging should have either tag "cloud-logging" or "Cloud Logging"') + LOG._warn && LOG.warn('User-provided service instances of SAP Cloud Logging should have the tag "Cloud Logging"') return ups.credentials } } From 942a1cbd71c72be952f97d73a06883aaef792d65 Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 11:45:55 +0100 Subject: [PATCH 08/12] feat: adjust matching predicate for dynatrace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e1f79a..b179f52 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ }, "telemetry-to-dynatrace": { "vcap": { - "label": "dynatrace" + "tag": "dynatrace" }, "tracing": { "exporter": { From a955e33938eb35f00b42119e2e83b5cfe6bd3d8f Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 11:52:08 +0100 Subject: [PATCH 09/12] docs: update instructions in readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66528ab..0f92781 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ This is the default kind in both development and production. Exports traces and metrics to Dynatrace. Hence, a Dynatrace instance is required and the app must be bound to that Dynatrace instance. +Please note, that for the binding to be resolved, the bound service instance needs to use the tag `dynatrace`, regardless of whether it is a user-provided service instance or not. Use via `cds.requires.telemetry.kind = 'to-dynatrace'`. @@ -289,12 +290,13 @@ 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 either tag `cloud-logging` or `Cloud Logging`. +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: > ``` > cf update-user-provided-service {service-name} -t "cloud-logging" > ``` +> For detailed information about binding resolution in CAP, consult [the relevant documentation](https://cap.cloud.sap/docs/node.js/cds-connect#vcap_services). ### `telemetry-to-jaeger` From c9f02642339759d88f14951cd5ab7337f09642a0 Mon Sep 17 00:00:00 2001 From: I548646 Date: Thu, 4 Dec 2025 11:53:09 +0100 Subject: [PATCH 10/12] fix: tag in example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f92781..9ce1aa5 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ If you are binding your app to SAP Cloud Logging via a [user-provided service in > 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" +> cf update-user-provided-service {service-name} -t "Cloud Logging" > ``` > For detailed information about binding resolution in CAP, consult [the relevant documentation](https://cap.cloud.sap/docs/node.js/cds-connect#vcap_services). From 76eb12c689ed271983c89262e441c47bb99073aa Mon Sep 17 00:00:00 2001 From: I548646 Date: Mon, 19 Jan 2026 11:31:38 +0100 Subject: [PATCH 11/12] chore: remove redundant todo --- lib/utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index d9456ac..2adac29 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -40,8 +40,6 @@ function getResource() { LOG._info && LOG.info('Unable to require package.json to resolve app name and version due to error:', err) } - // TODO: If we let PKG.name override the VCAP_APPLICATION.name - // TODO: We end up with my-app, while the name in the standarad deployment is my-app-srv const name = PKG?.name || VCAP_APPLICATION?.name || 'CAP Application' const version = PKG?.version || VCAP_APPLICATION?.application_version || '1.0.0' From 41f9b5ab9280c92f9ea8f24a4ecda0defc5b16e0 Mon Sep 17 00:00:00 2001 From: I548646 Date: Mon, 19 Jan 2026 16:06:23 +0100 Subject: [PATCH 12/12] feat: integrate greedy matchers --- package.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 195b41f..c6af9e3 100644 --- a/package.json +++ b/package.json @@ -106,9 +106,10 @@ } }, "telemetry-to-dynatrace": { - "vcap": { - "tag": "dynatrace" - }, + "vcap": [ + { "label": "dynatrace" }, + { "tag": "dynatrace" } + ], "tracing": { "exporter": { "module": "@opentelemetry/exporter-trace-otlp-proto", @@ -124,9 +125,10 @@ "token_name": "ingest_apitoken" }, "telemetry-to-cloud-logging": { - "vcap": { - "tag": "Cloud Logging" - }, + "vcap": [ + { "label": "cloud-logging" }, + { "tag": "Cloud Logging" } + ], "tracing": { "exporter": { "module": "@opentelemetry/exporter-trace-otlp-grpc",