Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
118 changes: 53 additions & 65 deletions cf-java-logging-support-opentelemetry-agent-extension/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext;

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.exporter.SanitizeSpanExporterCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;

import java.util.logging.Logger;

import static com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.DefaultOtelBackendPropertiesSupplier.builder;

public class CloudLoggingConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider {

private static final Logger LOG = Logger.getLogger(CloudLoggingConfigurationCustomizerProvider.class.getName());
private static final String VERSION = "4.0.0";

private static DefaultOtelBackendPropertiesSupplier getDefaultOtelBackendPropertiesSupplier() {
return builder() //
.add(new CaasBindingPropertiesSupplier()) // this has priority
.add(new CloudLoggingBindingPropertiesSupplier()) // look for Cloud Logging as fallback and backward compatibility
.build();
}

@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
LOG.info("Initializing SAP BTP Observability extension " + VERSION);
autoConfiguration.addPropertiesSupplier(new CloudLoggingBindingPropertiesSupplier());
autoConfiguration.addPropertiesSupplier(getDefaultOtelBackendPropertiesSupplier());
autoConfiguration.addSpanExporterCustomizer(new SanitizeSpanExporterCustomizer());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;

import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.PemFileCreator;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.ServerCertificateDownloader;
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;
import java.util.function.Supplier;
import java.util.logging.Logger;

import static java.util.Collections.emptyMap;

public class CaasBindingPropertiesSupplier implements Supplier<Map<String, String>> {

private static final String CAAS_CLIENT_KEY = "tls.key";
private static final String CAAS_CLIENT_CERT = "tls.crt";
private static final String CAAS_ENDPOINT = "http-url";
private static final String CAAS_PORT_PLACEHOLDER = "<http-receiver-port>";
private static final String PORT_OTLP_HTTP = "4318";

private static final Logger LOG = Logger.getLogger(CaasBindingPropertiesSupplier.class.getName());

private final CaasServiceProvider serviceProvider;
private final PemFileCreator pemFileCreator;
private final ServerCertificateDownloader serverCertificateDownloader;

public CaasBindingPropertiesSupplier() {
this(new CaasServiceProvider(getDefaultConfigProperties()), new PemFileCreator(),
new ServerCertificateDownloader());
}

CaasBindingPropertiesSupplier(CaasServiceProvider serviceProvider, PemFileCreator pemFileCreator,
ServerCertificateDownloader serverCertificateDownloader) {
this.serviceProvider = serviceProvider;
this.pemFileCreator = pemFileCreator;
this.serverCertificateDownloader = serverCertificateDownloader;
}

private static DefaultConfigProperties getDefaultConfigProperties() {
ComponentLoader componentLoader =
ComponentLoader.forClassLoader(DefaultConfigProperties.class.getClassLoader());
return DefaultConfigProperties.create(emptyMap(), componentLoader);
}

private static void putCaasDefaultProperties(Map<String, String> properties) {
properties.put("otel.exporter.otlp.protocol", "http/protobuf");
properties.put("otel.exporter.otlp.compression", "gzip");
}

@Override
public Map<String, String> get() {
CloudFoundryServiceInstance serviceInstance = serviceProvider.get();
if (serviceInstance == null) {
LOG.config("No CaaS service instance found.");
return emptyMap();
}
CloudFoundryCredentials credentials = serviceInstance.getCredentials();
if (credentials == null) {
LOG.warning(() -> "CaaS service instance '" + serviceInstance.getName() + "' has no credentials.");
return emptyMap();
}
String endpointUrl = credentials.getString(CAAS_ENDPOINT);
if (endpointUrl == null || endpointUrl.isBlank()) {
LOG.warning(() -> "CaaS service instance '" + serviceInstance.getName() + "' has no endpoint URL.");
return emptyMap();
}

Map<String, String> properties = new HashMap<>();
endpointUrl = endpointUrl.replace(CAAS_PORT_PLACEHOLDER, PORT_OTLP_HTTP);
LOG.config("Using CaaS OTLP endpoint URL: " + endpointUrl);
properties.put("otel.exporter.otlp.endpoint", endpointUrl);

putCaasDefaultProperties(properties);

String clientCert = credentials.getString(CAAS_CLIENT_CERT);
String clientKey = credentials.getString(CAAS_CLIENT_KEY);
if (clientCert != null && clientKey != null) {
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());

} catch (IOException e) {
LOG.warning(
() -> "Failed to create PEM files for CaaS service instance '" + serviceInstance.getName() + "': " + e.getMessage());
}
} else {
LOG.warning(
() -> "CaaS service instance '" + serviceInstance.getName() + "' is missing client certificate or key.");
}
return properties;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;

import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;

import java.util.Collections;
import java.util.function.Supplier;

public class CaasServiceProvider implements Supplier<CloudFoundryServiceInstance> {

private final CloudFoundryServiceInstance service;

public CaasServiceProvider(ConfigProperties config) {
this(config, new CloudFoundryServicesAdapter());
}

CaasServiceProvider(ConfigProperties config, CloudFoundryServicesAdapter adapter) {
String label = ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CAAS.LABEL.getValue(config);
this.service =
adapter.stream(Collections.singletonList(label), Collections.emptyList()).findFirst().orElse(null);
}

@Override
public CloudFoundryServiceInstance get() {
return service;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;

import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.DEPRECATED;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.PemFileCreator;
import io.opentelemetry.common.ComponentLoader;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -24,13 +24,17 @@ public class CloudLoggingBindingPropertiesSupplier implements Supplier<Map<Strin
private static final String OTLP_SERVER_CERT = "server-ca";

private final CloudLoggingServicesProvider cloudLoggingServicesProvider;
private final PemFileCreator pemFileCreator;

public CloudLoggingBindingPropertiesSupplier() {
this(new CloudLoggingServicesProvider(getDefaultProperties(), new CloudFoundryServicesAdapter()));
this(new CloudLoggingServicesProvider(getDefaultProperties(), new CloudFoundryServicesAdapter()),
new PemFileCreator());
}

CloudLoggingBindingPropertiesSupplier(CloudLoggingServicesProvider cloudLoggingServicesProvider) {
CloudLoggingBindingPropertiesSupplier(CloudLoggingServicesProvider cloudLoggingServicesProvider,
PemFileCreator pemFileCreator) {
this.cloudLoggingServicesProvider = cloudLoggingServicesProvider;
this.pemFileCreator = pemFileCreator;
}

private static ConfigProperties getDefaultProperties() {
Expand All @@ -47,16 +51,6 @@ private static boolean isBlank(String text) {
return text == null || text.trim().isEmpty();
}

private static File writeFile(String prefix, String suffix, String content) throws IOException {
File file = File.createTempFile(prefix, suffix);
file.deleteOnExit();
try (FileWriter writer = new FileWriter(file)) {
writer.append(content);
LOG.fine("Created temporary file " + file.getAbsolutePath());
}
return file;
}

/**
* Scans service bindings, both managed and user-provided for Cloud Logging. Managed services require the label
* "cloud-logging" to be considered. Services will be selected by the tag "Cloud Logging". User-provided services
Expand Down Expand Up @@ -95,9 +89,9 @@ private Map<String, String> createEndpointConfiguration(CloudFoundryServiceInsta
}

try {
File clientKeyFile = writeFile("cloud-logging-client", ".key", clientKey);
File clientCertFile = writeFile("cloud-logging-client", ".cert", clientCert);
File serverCertFile = writeFile("cloud-logging-server", ".cert", serverCert);
File clientKeyFile = pemFileCreator.writeFile("cloud-logging-client", ".key", clientKey);
File clientCertFile = pemFileCreator.writeFile("cloud-logging-client", ".cert", clientCert);
File serverCertFile = pemFileCreator.writeFile("cloud-logging-server", ".cert", serverCert);

HashMap<String, String> properties = new HashMap<>();
properties.put("otel.exporter.otlp.endpoint", "https://" + endpoint);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Logger;

import static java.util.Collections.emptyMap;
import static java.util.function.Predicate.not;

public class DefaultOtelBackendPropertiesSupplier implements Supplier<Map<String, String>> {

private static final Logger LOG = Logger.getLogger(DefaultOtelBackendPropertiesSupplier.class.getName());

private final List<Supplier<Map<String, String>>> suppliers;

private DefaultOtelBackendPropertiesSupplier(Builder builder) {
this.suppliers = builder.suppliers;
}

@Override
public Map<String, String> get() {
if (suppliers.isEmpty()) {
LOG.config("No OpenTelemetry backend properties suppliers configured.");
return emptyMap();
}
return suppliers.stream().map(Supplier::get).filter(not(Map::isEmpty)).findFirst().orElse(emptyMap());
}

public static Builder builder() {
return new Builder();
}

public static class Builder {

private final List<Supplier<Map<String, String>>> suppliers = new ArrayList<>();

public Builder add(Supplier<Map<String, String>> supplier) {
suppliers.add(supplier);
return this;
}

public DefaultOtelBackendPropertiesSupplier build() {
return new DefaultOtelBackendPropertiesSupplier(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ interface CLOUD_FOUNDRY {
interface RUNTIME {
interface CLOUD_FOUNDRY {
interface SERVICE {
interface CAAS {
/**
* <p>Parses {@code sap.caas.cf.binding.label.value}.</p>
* <p>The label value used to identify managed CaaS service bindings. Default is
* {@code "caas-service"}.</p>
*/
ConfigProperty<String> LABEL =
stringValued("sap.caas.cf.binding.label.value").withDefaultValue("caas-service").build();
}

interface CLOUD_LOGGING {
/**
* <p>Parses {@code sap.cloud-logging.cf.binding.label.value}.</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Logger;

public class PemFileCreator {

private static final Logger LOG = Logger.getLogger(PemFileCreator.class.getName());

public File writeFile(String prefix, String suffix, String content) throws IOException {
File file = File.createTempFile(prefix, suffix);
file.deleteOnExit();
try (FileWriter writer = new FileWriter(file)) {
writer.append(content);
LOG.fine("Created temporary file " + file.getAbsolutePath());
}
return file;
}
}
Loading