Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
51a142d
docs: add Generic OTel Collector + BTP Certificate Service mTLS desig…
DimitarKiryakovSAP Aug 21, 2026
8be179c
feat(otel-ext): add OtelCollector and CertificateService config prope…
DimitarKiryakovSAP Aug 21, 2026
a7bb36b
refactor(otel-ext): remove BTP Certificate Service integration — mTLS…
DimitarKiryakovSAP Aug 21, 2026
b95b108
feat(otel-ext): add findByName() to CloudFoundryServicesAdapter
DimitarKiryakovSAP Aug 21, 2026
46b2d93
feat(otel-ext): add OtelCollectorServiceProvider
DimitarKiryakovSAP Aug 21, 2026
72000ec
feat(otel-ext): add OtelCollectorBindingPropertiesSupplier reading mT…
DimitarKiryakovSAP Aug 21, 2026
e6c9d25
test(otel-ext): add exception-path test for OtelCollectorBindingPrope…
DimitarKiryakovSAP Aug 21, 2026
d56c3f1
feat(otel-ext): wire OtelCollectorBindingPropertiesSupplier into cust…
DimitarKiryakovSAP Aug 21, 2026
85a4c7e
docs(otel-ext): replace stale BTP Certificate Service docs with actua…
DimitarKiryakovSAP Aug 21, 2026
cf92fee
chore: remove internal planning docs
DimitarKiryakovSAP Aug 21, 2026
05f36f7
refactor(otel-ext): remove out-of-scope UAA credential merging
DimitarKiryakovSAP Aug 21, 2026
a0ab920
feat(otel-ext): allow mTLS and Bearer token to be used simultaneously
DimitarKiryakovSAP Aug 21, 2026
67eae45
refactor(otel-ext): restore original parseServiceCredentials signature
DimitarKiryakovSAP Aug 21, 2026
e7adff6
refactor: extract MtlsClientCertHelper and make OtelCollector mTLS ma…
DimitarKiryakovSAP Aug 21, 2026
eee5416
refactor(caas): inline getString calls in mTLS guard, remove stale lo…
DimitarKiryakovSAP Aug 21, 2026
bacb4c4
refactor(mtls): pass clientCert/clientKey strings into helper instead…
DimitarKiryakovSAP Aug 21, 2026
cf4a308
refactor(mtls): add serverCert to helper, unify all TLS PEM writes in…
DimitarKiryakovSAP Aug 21, 2026
b9348a6
refactor(mtls): introduce TlsCertificates parameter object to reduce …
DimitarKiryakovSAP Aug 21, 2026
a7ac3f3
Add Javadoc to public methods and remove task-tracking comments
DimitarKiryakovSAP Aug 21, 2026
6859cda
Add missing test coverage and fix assertion typo found in code review
DimitarKiryakovSAP Aug 21, 2026
d933b8d
fix(otel-ext): fix MtlsClientCertHelper partial-write regression and …
DimitarKiryakovSAP Aug 21, 2026
cc977fe
revert: remove javadoc from pre-existing classes not in scope of this PR
DimitarKiryakovSAP Aug 21, 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
93 changes: 77 additions & 16 deletions cf-java-logging-support-opentelemetry-agent-extension/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# OpenTelemetry Java Agent Extension for SAP BTP Observability

This module provides an extension for the [OpenTelemetry Java Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/).
The extension scans the service bindings of an application for SAP Collector as a Service (CaaS), [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) and [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring).
The extension scans the service bindings of an application for a generic OTel Collector, SAP Collector as a Service (CaaS), [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) and [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring).
If such a binding is found, the OpenTelemetry Java Agent is configured to ship observability data to those services.
Thus, this extension provides a convenient auto-instrumentation for Java applications running on SAP BTP.

> Note: CaaS currently is an SAP internal only service.

The extension provides the following main features:

* auto-configuration of the generic OpenTelemetry OTLP exporter to SAP Collector as a Service (CaaS) or [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging)
* auto-configuration of the generic OpenTelemetry OTLP exporter to any OTel Collector service binding (identified by instance name), SAP Collector as a Service (CaaS) or [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging)
* optional mTLS using client certificate and key read directly from the OTel Collector service binding credentials
* additional exporters for logs, metrics and traces for [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging)
* additional exporter for metrics for [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring)
* adding resource attributes describing the CF application
Expand Down Expand Up @@ -44,8 +45,9 @@ See the [example manifest](../sample-spring-boot/manifest-otel-javaagent.yml), h

Once the agent is attached to the JVM with the extension in place, the default `otlp` exporter is automatically configured based on available service bindings:

1. **CaaS Service Binding** (preferred): If a CaaS service binding is found, the `otlp` exporter sends data to the CaaS endpoint.
2. **Cloud Logging Service Binding** (fallback): If no CaaS binding exists, the `otlp` exporter sends data to Cloud Logging.
1. **Generic OTel Collector Binding** (highest priority, explicit opt-in): If `sap.otel.collector.cf.binding.name` is configured, the extension locates that CF service instance by name and uses it as the OTel Collector endpoint. Optionally uses mTLS credentials (`tls.crt`, `tls.key`) read directly from the binding. See [Using a Generic OTel Collector Service Binding](#using-a-generic-otel-collector-service-binding).
2. **CaaS Service Binding** (automatic, preferred): If no explicit binding name is set and a CaaS service binding is found, the `otlp` exporter sends data to the CaaS endpoint.
3. **Cloud Logging Service Binding** (automatic, fallback): If neither of the above applies, the `otlp` exporter sends data to Cloud Logging.

This means **metrics and traces are automatically exported** without additional configuration when either service is bound.
The recommended way to export data to Cloud Logging and Dynatrace is to use the provided exporters explicitly.
Expand Down Expand Up @@ -92,23 +94,25 @@ There is no custom network client provided by this extension.

The extension itself can be configured by specifying the following system properties:

| Property | Description | Default Value |
|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| `sap.caas.cf.binding.label.value` | The label of the managed CaaS service binding to bind to. | `caas-service` |
| `sap.cloud-logging.cf.binding.label.value` | The label of the managed service binding to bind to. | `cloud-logging` |
| `sap.cloud-logging.cf.binding.tag.value` | The tag of any service binding (managed or user-provided) to bind to. | `Cloud Logging` |
| `sap.dynatrace.cf.binding.label.value` | The label of the managed service binding to bind to. | `dynatrace` |
| `sap.dynatrace.cf.binding.tag.value` | The tag of any service binding (managed or user-provided) to bind to. | `dynatrace` |
| `sap.dynatrace.cf.binding.token.name` | The name of the field containing the Dynatrace API token within the service binding credentials. This is required to send metrics to Dynatrace. | |
| `sap.cloudfoundry.otel.resources.enabled` | Whether to add CF resource attributes to all events. | `true` |
| `sap.cloudfoundry.otel.resources.format` | The semantic convention to follow for the CF resource attributes. Supported values are `SAP` and `OTEL`. | `SAP` |
| Property | Description | Default Value |
|-------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|
| `sap.otel.collector.cf.binding.name` | The name of a CF service instance (managed or user-provided) to use as the **Generic OTel Collector** endpoint. When set, this binding takes priority over CaaS and Cloud Logging auto-detection. See [Using a Generic OTel Collector](#using-a-generic-otel-collector-service-binding). | *(absent)* |
| `sap.caas.cf.binding.label.value` | The label of the managed CaaS service binding to bind to. | `caas-service` |
| `sap.cloud-logging.cf.binding.label.value` | The label of the managed service binding to bind to. | `cloud-logging` |
| `sap.cloud-logging.cf.binding.tag.value` | The tag of any service binding (managed or user-provided) to bind to. | `Cloud Logging` |
| `sap.dynatrace.cf.binding.label.value` | The label of the managed service binding to bind to. | `dynatrace` |
| `sap.dynatrace.cf.binding.tag.value` | The tag of any service binding (managed or user-provided) to bind to. | `dynatrace` |
| `sap.dynatrace.cf.binding.token.name` | The name of the field containing the Dynatrace API token within the service binding credentials. This is required to send metrics to Dynatrace. | |
| `sap.cloudfoundry.otel.resources.enabled` | Whether to add CF resource attributes to all events. | `true` |
| `sap.cloudfoundry.otel.resources.format` | The semantic convention to follow for the CF resource attributes. Supported values are `SAP` and `OTEL`. | `SAP` |

> Each property can also be provided as environment variable, e.g., `sap.cloud-logging.cf.binding.label.value` as `SAP.CLOUD-LOGGING.CF.BINDING.LABEL.VALUE`.

The extension scans the `VCAP_SERVICES` environment variable for CF service bindings in the following order:

1. **CaaS bindings**: Searches for bindings matching the configured label (`sap.caas.cf.binding.label.value`, default: `caas-service`)
2. **Cloud Logging bindings**: If no CaaS binding is found, searches for bindings matching the configured label and tag (`sap.cloud-logging.cf.binding.label.value` and `sap.cloud-logging.cf.binding.tag.value`)
1. **Generic OTel Collector binding** (only when `sap.otel.collector.cf.binding.name` is configured): Finds the service instance by that exact name. Optionally reads mTLS credentials (`tls.crt`, `tls.key`) and a server CA (`tls.ca.crt`) directly from the binding credentials.
2. **CaaS bindings**: Searches for bindings matching the configured label (`sap.caas.cf.binding.label.value`, default: `caas-service`)
3. **Cloud Logging bindings**: If no CaaS binding is found, searches for bindings matching the configured label and tag (`sap.cloud-logging.cf.binding.label.value` and `sap.cloud-logging.cf.binding.tag.value`)

User-provided bindings take precedence over managed bindings.
The first matching binding configures the default OpenTelemetry `otlp` exporter.
Expand Down Expand Up @@ -202,6 +206,63 @@ The following table summarizes all configuration properties provided by the exte
| `sap.dynatrace.cf.binding.label.value` | The label value used to identify managed Dynatrace service bindings. | `dynatrace` |
| `sap.dynatrace.cf.binding.tag.value` | The tag value used to identify managed Dynatrace service bindings. | `dynatrace` |
| `sap.dynatrace.cf.binding.token.name` | The name of the field containing the Dynatrace API token within the service binding credentials. | |
| `sap.otel.collector.cf.binding.name` | CF service instance name to use as a Generic OTel Collector. When absent, this feature is disabled and the existing CaaS / Cloud Logging auto-detection is unaffected. | |

## Using a Generic OTel Collector Service Binding

_This feature was introduced with version 4.4.0 of the extension._

The extension supports sending observability data to **any OTel Collector** service binding — whether a managed service or a user-provided service — by specifying the CF service instance name via the `sap.otel.collector.cf.binding.name` property.
This is referred to as the **Generic OTel Collector** path to distinguish it from the CaaS-specific auto-detection.

When `sap.otel.collector.cf.binding.name` is set, the extension:
1. Locates the CF service instance with that exact name in `VCAP_SERVICES`.
2. Reads the `url` (or `http-url`) credential field as the OTLP endpoint.
3. If `tls.crt` and `tls.key` credential fields are present, uses them for mTLS (client certificate and private key in PEM format). If `tls.ca.crt` is also present, uses it as the server CA certificate.
4. Reads the optional `token` credential field and sends it as a `Bearer` authorization header. This can be combined with mTLS.

### Minimal setup (URL + token)

Create a user-provided service with at least a `url` field and optionally a `token`:

```bash
cf cups my-otel-collector -p '{"url":"https://my-otel-collector.example.com","token":"<api-token>"}'
```

Configure the extension to use it:

```sh
-Dsap.otel.collector.cf.binding.name=my-otel-collector
```

### Setup with mTLS from binding credentials

Place the PEM-encoded client certificate, private key, and optionally the server CA certificate directly in the user-provided service credentials:

```bash
cf cups my-otel-collector -p '{
"url": "https://my-otel-collector.example.com",
"tls.crt": "<PEM client certificate>",
"tls.key": "<PEM private key>",
"tls.ca.crt": "<PEM CA certificate (optional)>"
}'
```

Configure the extension to use it:

```sh
-Dsap.otel.collector.cf.binding.name=my-otel-collector
```

The required credential fields for mTLS are:

| Field name | Contents | Required |
|-------------|-------------------------------------------------------|----------|
| `url` | OTLP endpoint URL (e.g. `https://collector.example.com`). | Yes |
| `tls.crt` | PEM-encoded mTLS client certificate. | For mTLS |
| `tls.key` | PEM-encoded mTLS private key. | For mTLS |
| `tls.ca.crt`| PEM-encoded server CA certificate. | Optional |
| `token` | Bearer token sent as `Authorization: Bearer <token>`. Can be combined with mTLS. | Optional |

## Using User-Provided Service Instances

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CaasBindingPropertiesSupplier;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingBindingPropertiesSupplier;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.DefaultOtelBackendPropertiesSupplier;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.OtelCollectorBindingPropertiesSupplier;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.SanitizeSpanExporterCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
Expand All @@ -18,6 +19,7 @@ public class CloudLoggingConfigurationCustomizerProvider implements AutoConfigur

private static DefaultOtelBackendPropertiesSupplier getDefaultOtelBackendPropertiesSupplier() {
return builder() //
.add(new OtelCollectorBindingPropertiesSupplier()) // no-op unless sap.otel.collector.cf.binding.name is set
.add(new CaasBindingPropertiesSupplier()) // this has priority
.add(new CloudLoggingBindingPropertiesSupplier()) // look for Cloud Logging as fallback and backward compatibility
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import io.opentelemetry.common.ComponentLoader;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -28,6 +27,9 @@ public class CaasBindingPropertiesSupplier implements Supplier<Map<String, Strin
private final PemFileCreator pemFileCreator;
private final ServerCertificateDownloader serverCertificateDownloader;

/**
* Creates a new instance using default service discovery and TLS infrastructure.
*/
public CaasBindingPropertiesSupplier() {
this(new CaasServiceProvider(getDefaultConfigProperties()), new PemFileCreator(),
new ServerCertificateDownloader());
Expand All @@ -51,6 +53,14 @@ private static void putCaasDefaultProperties(Map<String, String> properties) {
properties.put("otel.exporter.otlp.compression", "gzip");
}

/**
* Reads the CaaS service instance credentials from VCAP_SERVICES and returns the OpenTelemetry OTLP exporter
* configuration properties for mTLS. When client credentials or a server certificate are unavailable, returns
* basic endpoint properties without TLS.
*
* @return The pre-configured connection properties for the OpenTelemetry SDK, or an empty map when no usable
* CaaS service instance is found.
*/
@Override
public Map<String, String> get() {
CloudFoundryServiceInstance serviceInstance = serviceProvider.get();
Expand Down Expand Up @@ -78,19 +88,17 @@ public Map<String, String> get() {

String clientCert = credentials.getString(CAAS_CLIENT_CERT);
String clientKey = credentials.getString(CAAS_CLIENT_KEY);
if (clientCert != null && clientKey != null) {
if (clientCert != null && !clientCert.isBlank() && clientKey != null && !clientKey.isBlank()) {
try {
String serverCert = serverCertificateDownloader.download(endpointUrl);
if (serverCert == null || serverCert.isBlank()) {
return properties;
}
File serverCertFile = pemFileCreator.writeFile("caas-server-cert-", ".crt", serverCert);
File clientCertFile = pemFileCreator.writeFile("caas-client-cert-", ".crt", clientCert);
File clientKeyFile = pemFileCreator.writeFile("caas-client-key-", ".key", clientKey);

properties.put("otel.exporter.otlp.certificate", serverCertFile.getAbsolutePath());
properties.put("otel.exporter.otlp.client.certificate", clientCertFile.getAbsolutePath());
properties.put("otel.exporter.otlp.client.key", clientKeyFile.getAbsolutePath());
MtlsClientCertHelper.applyTlsProperties(
new TlsCertificates(clientCert, clientKey, serverCert),
pemFileCreator,
"caas-client-cert-", "caas-client-key-", "caas-server-cert-",
properties);

} catch (IOException e) {
LOG.warning(
Expand Down
Loading