Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<excludes>
<exclude>io.opentelemetry</exclude>
<exclude>com.fasterxml.jackson.core</exclude>
<exclude>com.squareup.okhttp3</exclude>
<exclude>com.squareup.okio</exclude>
<exclude>org.jetbrains.kotlin</exclude>
<exclude>org.jetbrains</exclude>
</excludes>
</artifactSet>
</configuration>
Expand All @@ -58,7 +62,13 @@
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.31.0</version>
<scope>provided</scope>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.31.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.pivotal.cfenv</groupId>
Expand Down Expand Up @@ -83,6 +86,10 @@
<excludes>
<exclude>io.opentelemetry</exclude>
<exclude>com.fasterxml.jackson.core</exclude>
<exclude>com.squareup.okhttp3</exclude>
<exclude>com.squareup.okio</exclude>
<exclude>org.jetbrains.kotlin</exclude>
<exclude>org.jetbrains</exclude>
</excludes>
</artifactSet>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class CloudLoggingConfigurationCustomizerProvider implements AutoConfigur
public void customize(AutoConfigurationCustomizer autoConfiguration) {
autoConfiguration
.addPropertiesSupplier(new CloudLoggingBindingPropertiesSupplier(cfEnv));

// ConfigurableLogRecordExporterProvider
}

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

import io.pivotal.cfenv.core.CfCredentials;

import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;

class CloudLoggingCredentials {

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

private static final String CRED_OTLP_ENDPOINT = "ingest-otlp-endpoint";
private static final String CRED_OTLP_CLIENT_KEY = "ingest-otlp-key";
private static final String CRED_OTLP_CLIENT_CERT = "ingest-otlp-cert";
private static final String CRED_OTLP_SERVER_CERT = "server-ca";
private static final String CLOUD_LOGGING_ENDPOINT_PREFIX = "https://";


private String endpoint;
private byte[] clientKey;
private byte[] clientCert;
private byte[] serverCert;

private CloudLoggingCredentials() {
}

static CloudLoggingCredentials parseCredentials(CfCredentials cfCredentials) {
CloudLoggingCredentials parsed = new CloudLoggingCredentials();
parsed.endpoint = CLOUD_LOGGING_ENDPOINT_PREFIX + cfCredentials.getString(CRED_OTLP_ENDPOINT);
parsed.clientKey = getPEMBytes(cfCredentials, CRED_OTLP_CLIENT_KEY);
parsed.clientCert = getPEMBytes(cfCredentials, CRED_OTLP_CLIENT_CERT);
parsed.serverCert = getPEMBytes(cfCredentials, CRED_OTLP_SERVER_CERT);
return parsed;
}

private static byte[] getPEMBytes(CfCredentials credentials, String key) {
String raw = credentials.getString(key);
return raw == null ? null : raw.trim().replace("\\n", "\n").getBytes(StandardCharsets.UTF_8);
}

private static boolean isBlank(String text) {
return text == null || text.trim().isEmpty();
}

private static boolean isNullOrEmpty(byte[] bytes) {
return bytes == null || bytes.length == 0;
}

public boolean validate() {
if (isBlank(endpoint)) {
LOG.warning("Credential \"" + CRED_OTLP_ENDPOINT + "\" not found. Skipping cloud-logging exporter configuration");
return false;
}

if (isNullOrEmpty(clientKey)) {
LOG.warning("Credential \"" + CRED_OTLP_CLIENT_KEY + "\" not found. Skipping cloud-logging exporter configuration");
return false;
}

if (isNullOrEmpty(clientCert)) {
LOG.warning("Credential \"" + CRED_OTLP_CLIENT_CERT + "\" not found. Skipping cloud-logging exporter configuration");
return false;
}

if (isNullOrEmpty(serverCert)) {
LOG.warning("Credential \"" + CRED_OTLP_SERVER_CERT + "\" not found. Skipping cloud-logging exporter configuration");
return false;
}
return true;
}

public String getEndpoint() {
return endpoint;
}

public byte[] getClientKey() {
return clientKey;
}

public byte[] getClientCert() {
return clientCert;
}

public byte[] getServerCert() {
return serverCert;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;

import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider;
import io.opentelemetry.sdk.common.export.RetryPolicy;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.pivotal.cfenv.core.CfEnv;
import io.pivotal.cfenv.core.CfService;

import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CloudLoggingLogsExporterProvider implements ConfigurableLogRecordExporterProvider {

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

private final Function<ConfigProperties, Stream<CfService>> servicesProvider;

public CloudLoggingLogsExporterProvider() {
this(config -> new CloudLoggingServicesProvider(config, new CfEnv()).get());
}

public CloudLoggingLogsExporterProvider(Function<ConfigProperties, Stream<CfService>> serviceProvider) {
this.servicesProvider = serviceProvider;
}

private static String getCompression(ConfigProperties config) {
String compression = config.getString("otel.exporter.cloud-logging.logs.compression");
return compression != null ? compression : config.getString("otel.exporter.cloud-logging.compression", "gzip");
}

private static Duration getTimeOut(ConfigProperties config) {
Duration timeout = config.getDuration("otel.exporter.cloud-logging.logs.timeout");
return timeout != null ? timeout : config.getDuration("otel.exporter.cloud-logging.timeout");
}

@Override
public String getName() {
return "cloud-logging";
}

@Override
public LogRecordExporter createExporter(ConfigProperties config) {
List<LogRecordExporter> exporters = servicesProvider.apply(config)
.map(svc -> createExporter(config, svc))
.filter(exp -> !(exp instanceof NoopLogRecordExporter))
.collect(Collectors.toList());
return LogRecordExporter.composite(exporters);
}

private LogRecordExporter createExporter(ConfigProperties config, CfService service) {
LOG.info("Creating logs exporter for service binding " + service.getName() + " (" + service.getLabel() + ")");
CloudLoggingCredentials credentials = CloudLoggingCredentials.parseCredentials(service.getCredentials());
if (!credentials.validate()) {
return NoopLogRecordExporter.getInstance();
}

OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder();
builder.setEndpoint(credentials.getEndpoint())
.setCompression(getCompression(config))
.setClientTls(credentials.getClientKey(), credentials.getClientCert())
.setTrustedCertificates(credentials.getServerCert())
.setRetryPolicy(RetryPolicy.getDefault());

Duration timeOut = getTimeOut(config);
if (timeOut != null) {
builder.setTimeout(timeOut);
}

LOG.info("Created logs exporter for service binding " + service.getName() + " (" + service.getLabel() + ")");
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;

import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.common.export.RetryPolicy;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
import io.pivotal.cfenv.core.CfEnv;
import io.pivotal.cfenv.core.CfService;

import java.time.Duration;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram;

public class CloudLoggingMetricsExporterProvider implements ConfigurableMetricExporterProvider {

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

private final Function<ConfigProperties, Stream<CfService>> servicesProvider;

public CloudLoggingMetricsExporterProvider() {
this(config -> new CloudLoggingServicesProvider(config, new CfEnv()).get());
}

public CloudLoggingMetricsExporterProvider(Function<ConfigProperties, Stream<CfService>> serviceProvider) {
this.servicesProvider = serviceProvider;
}

private static String getCompression(ConfigProperties config) {
String compression = config.getString("otel.exporter.cloud-logging.metrics.compression");
return compression != null ? compression : config.getString("otel.exporter.cloud-logging.compression", "gzip");
}

private static Duration getTimeOut(ConfigProperties config) {
Duration timeout = config.getDuration("otel.exporter.cloud-logging.metrics.timeout");
return timeout != null ? timeout : config.getDuration("otel.exporter.cloud-logging.timeout");
}

private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) {
String temporalityStr = config.getString("otel.exporter.cloud-logging.metrics.temporality.preference");
if (temporalityStr == null) {
return AggregationTemporalitySelector.alwaysCumulative();
}
AggregationTemporalitySelector temporalitySelector;
switch (temporalityStr.toLowerCase(Locale.ROOT)) {
case "cumulative":
return AggregationTemporalitySelector.alwaysCumulative();
case "delta":
return AggregationTemporalitySelector.deltaPreferred();
case "lowmemory":
return AggregationTemporalitySelector.lowMemory();
default:
throw new ConfigurationException("Unrecognized aggregation temporality: " + temporalityStr);
}
}

private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
String defaultHistogramAggregation =
config.getString("otel.exporter.cloud-logging.metrics.default.histogram.aggregation");
if (defaultHistogramAggregation == null) {
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
}
if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
return
DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram());
} else if (AggregationUtil.aggregationName(explicitBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.explicitBucketHistogram());
} else {
throw new ConfigurationException(
"Unrecognized default histogram aggregation: " + defaultHistogramAggregation);
}
}

@Override
public String getName() {
return "cloud-logging";
}

@Override
public MetricExporter createExporter(ConfigProperties config) {
List<MetricExporter> exporters = servicesProvider.apply(config)
.map(svc -> createExporter(config, svc))
.filter(exp -> !(exp instanceof NoopMetricExporter))
.collect(Collectors.toList());
return new MultiMetricExporter(getAggregationTemporalitySelector(config), getDefaultAggregationSelector(config), exporters);
}

private MetricExporter createExporter(ConfigProperties config, CfService service) {
LOG.info("Creating metrics exporter for service binding " + service.getName() + " (" + service.getLabel() + ")");
CloudLoggingCredentials credentials = CloudLoggingCredentials.parseCredentials(service.getCredentials());
if (!credentials.validate()) {
return NoopMetricExporter.getInstance();
}

OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder();
builder.setEndpoint(credentials.getEndpoint())
.setCompression(getCompression(config))
.setClientTls(credentials.getClientKey(), credentials.getClientCert())
.setTrustedCertificates(credentials.getServerCert())
.setRetryPolicy(RetryPolicy.getDefault())
.setAggregationTemporalitySelector(getAggregationTemporalitySelector(config))
.setDefaultAggregationSelector(getDefaultAggregationSelector(config));

Duration timeOut = getTimeOut(config);
if (timeOut != null) {
builder.setTimeout(timeOut);
}

LOG.info("Created metrics exporter for service binding " + service.getName() + " (" + service.getLabel() + ")");
return builder.build();
}
}
Loading