diff --git a/cf-java-logging-support-opentelemetry-agent-extension/README.md b/cf-java-logging-support-opentelemetry-agent-extension/README.md index 62c2554c..c711e16c 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/README.md +++ b/cf-java-logging-support-opentelemetry-agent-extension/README.md @@ -35,7 +35,7 @@ java -javaagent:BOOT-INF/lib/opentelemetry-javaagent-.jar \ # your Java application command ``` -> You need to use OpenTelemetry Java Agent 1.29.0+, so it provides jackson-databind to the extension. +> You need to use the OpenTelemetry Java Agent version this extension was built against to avoid compatibility issues. See the [example manifest](../sample-spring-boot/manifest-otel-javaagent.yml), how this translates into a deployment description. @@ -43,6 +43,7 @@ Once the agent is attached to the JVM with the extension in place, there are two 1. Use the `cloud-logging` and/or `dynatrace` exporters explicitly as provided by the extension. This can be achieved via system properties or environment variables: + ```sh -Dotel.logs.exporter=cloud-logging \ -Dotel.metrics.exporter=cloud-logging,dynatrace \ @@ -84,34 +85,37 @@ The service instances can be either managed or [user-provided](#using-user-provi The OpenTelemetry Java Agent supports a wide variety of [configuration options](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/). As the extension provides configuration via SPI, all its configuration takes lower precedence than other configuration options for OpenTelemetry. Users can easily overwrite any setting using environment variables or system properties. +The full list of configuration properties provided by the extension is available in the [Configuration Properties Summary](#configuration-properties-summary) section. ### Using the Extension The extension needs to be started with the OpenTelemetry Java Agent as outlined in the [Quick Start Guide](#quickstart-guide). -You need to enable shipping data either by using the `cloud-logging` exporters or relying on the `otlp` exporters for each signal type. +You need to enable shipping data either by using the `cloud-logging` exporters for each signal type or `dynatrace` for metrics explicitly. Multiple different exporters can be configured with comma separation. -Using the custom `cloud-logging` exporter will enable you, to use the default `otlp` exporter for different services. -The extension will configure a default endpoint and credentials for the `otlp` endpoints, so no further configuration is required. +Using the custom `cloud-logging` exporter enables you, to use the default `otlp` exporter for different services. -Note, that the `cloud-logging` exporter is just a facade for the `otlp` exporter to allow configuration of multiple data sinks. +Note, that the `cloud-logging` and `dynatrace` exporters are just facades for the `otlp` exporter to allow configuration of multiple data sinks. There is no custom network client provided by this extension. ### Configuring the Extension +> Note: This section describes configuration options introduced with version 4.1.0 of the extension. +> Earlier versions use different property names, which are still supported as fallback. +> They will create warning messages during initialization as those properties are deprecated for removal. + The extension itself can be configured by specifying the following system properties: -| Property | Default Value | Comment | -|----------|---------------|---------| -| `otel.javaagent.extension.sap.cf.binding.cloud-logging.label` or `com.sap.otel.extension.cloud-logging.label` | `cloud-logging` | The label of the managed service binding to bind to. | -| `otel.javaagent.extension.sap.cf.binding.cloud-logging.tag` or `com.sap.otel.extension.cloud-logging.tag` | `Cloud Logging` | The tag of any service binding (managed or user-provided) to bind to. | -| `otel.javaagent.extension.sap.cf.binding.dynatrace.label` | `dynatrace` | The label of the managed service binding to bind to. | -| `otel.javaagent.extension.sap.cf.binding.dynatrace.tag` | `dynatrace` | The tag of any service binding (managed or user-provided) to bind to. | -| `otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.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. | -| `otel.javaagent.extension.sap.cf.binding.user-provided.label` | `user-provided` | The label of a user-provided service binding to bind to. Note, this label is defined by the Cloud Foundry instance. | -| `otel.javaagent.extension.sap.cf.resource.enabled` | `true` | Whether to add CF resource attributes to all events. | +| Property | Description | Default Value | +|--------------------------------------------|-------------|---------------| +| `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` | -> The `otel.javaagent.extension.sap.*` properties are preferred over the `com.sap.otel.extension.*` properties, which are kept for compatibility. -Each `otel.javaagent.extension.sap.*` property can also be provided as environment variable `OTEL_JAVAAGENT_EXTENSION_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 will scan the environment variable `VCAP_SERVICES` for CF service bindings. User-provided bindings will take precedence over managed bindings of the configured label ("cloud-logging" or "dynatrace" by default). @@ -148,8 +152,8 @@ _This feature was introduced with version 4.1.0 of the extension._ You can filter which metrics are exported to Cloud Logging or Dynatrace by name using the following properties: -| Property | Description | -|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| Property | Description | +|--------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | `otel.exporter.cloud-logging.metrics.include.names` or `otel.exporter.dynatrace.metrics.include.names` | A comma-separated list of metric names to be forwarded. This may include a wildcard "*" at the end of the name. | | `otel.exporter.cloud-logging.metrics.exclude.names` or `otel.exporter.dynatrace.metrics.exclude.names` | A comma-separated list of metric names to be rejected. This may include a wildcard "*" at the end of the name. | @@ -157,6 +161,39 @@ Note, that the `include` filter is applied before the `exclude` filter. That means, if a metric matches both filters, it will be excluded. The configuration applies to both the `cloud-logging` and `dynatrace` exporters independently. +### Configuration Properties Summary + +The following table summarizes all configuration properties provided by the extension: + +| Property | Description | Default Value | +|---------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------| +| `otel.exporter.cloud-logging.compression` | The compression algorithm to use when exporting logs. | `gzip` | +| `otel.exporter.cloud-logging.timeout` | The maximum duration to wait for Cloud Logging when exporting data. | `10000 (from OTel SDK) | +| `otel.exporter.cloud-logging.logs.compression` | The compression algorithm to use when exporting logs. Falls back to `otel.exporter.cloud-logging.compression` if not set. | `gzip` (from `otel.exporter.cloud-logging.compression`) | +| `otel.exporter.cloud-logging.logs.timeout` | The maximum duration to wait for Cloud Logging when exporting logs. Falls back to `otel.exporter.cloud-logging.timeout` if not set. | `10000` (from OTel SDK) | +| `otel.exporter.cloud-logging.metrics.compression` | The compression algorithm to use when exporting metrics. Falls back to `otel.exporter.cloud-logging.compression` if not set. | `gzip` (from `otel.exporter.cloud-logging.compression`) | +| `otel.exporter.cloud-logging.metrics.default.histogram.aggregation` | The default histogram aggregation for metrics exported to Cloud Logging. Delegates to the underlying OTLP exporter, supporting all its configurations. | `EXPLICIT_BUCKET_HISTOGRAM` (from OTel SDK) | +| `otel.exporter.cloud-logging.metrics.exclude.names` | A comma-separated list of metric name patterns to be excluded when exporting metrics to Cloud Logging. Wildcard "\*" is only supported at the end of the name. If not set, no metrics are excluded. | | +| `otel.exporter.cloud-logging.metrics.include.names` | A comma-separated list of metric name patterns to be included when exporting metrics to Cloud Logging. Wildcard "\*" is only supported at the end of the name. If not set, all metrics are exported. | | +| `otel.exporter.cloud-logging.metrics.temporality.preference` | The preferred aggregation temporality for metrics exported to Cloud Logging. Can be either `cumulative`, `delta`, or `lowmemory`. | `cumulative` | +| `otel.exporter.cloud-logging.metrics.timeout` | The maximum duration to wait for Cloud Logging when exporting metrics. Falls back to `otel.exporter.cloud-logging.timeout` if not set. | `10000` (from OTel SDK) | +| `otel.exporter.cloud-logging.traces.compression` | The compression algorithm to use when exporting traces. Falls back to `otel.exporter.cloud-logging.compression` if not set. | `gzip` (from `otel.exporter.cloud-logging.compression`) | +| `otel.exporter.cloud-logging.traces.timeout` | The maximum duration to wait for Cloud Logging when exporting traces. Falls back to `otel.exporter.cloud-logging.timeout` if not set. | `10000` (from OTel SDK) | +| `otel.exporter.dynatrace.metrics.compression` | The compression algorithm to use when exporting metrics. | `gzip` | +| `otel.exporter.dynatrace.metrics.default.histogram.aggregation` | The default histogram aggregation for metrics exported to Dynatrace. Delegates to the underlying OTLP exporter, supporting all its configurations. | `EXPLICIT_BUCKET_HISTOGRAM` (from OTel SDK) | +| `otel.exporter.dynatrace.metrics.exclude.names` | A comma-separated list of metric name patterns to be excluded when exporting metrics to Dynatrace. Wildcard "\*" is only supported at the end of the name. If not set, no metrics are excluded. | | +| `otel.exporter.dynatrace.metrics.include.names` | A comma-separated list of metric name patterns to be included when exporting metrics to Dynatrace. Wildcard "\*" is only supported at the end of the name. If not set, all metrics are exported. | | +| `otel.exporter.dynatrace.metrics.temporality.preference` | The default histogram aggregation for metrics exported to Dynatrace. Delegates to the underlying OTLP exporter, supporting all its configurations. The Dynatrace metrics exporter provides an additional option `always_delta` which always uses delta aggregation temporality. This is also the default behavior if the property is not set. | `always_delta` | +| `otel.exporter.dynatrace.metrics.timeout` | The maximum duration to wait for Dynatrace when exporting metrics. | `10000` (from OTel SDK) | +| `sap.cf.integration.otel.extension.sanitizer.enabled` | Enables or disables the sanitizer. | `true` | +| `sap.cloudfoundry.otel.resources.enabled` | Should Cloud Foundry resource attributes be added to the OpenTelemetry resource? | `true` | +| `sap.cloudfoundry.otel.resources.format` | Determines the semantic convention used for Cloud Foundry resource attributes names. `SAP` - use SAP specific attribute names (default). `OTEL` - use OpenTelemetry semantic convention attribute names. | `SAP` | +| `sap.cloud-logging.cf.binding.label.value` | The label value used to identify managed Cloud Logging service bindings. | `cloud-logging` | +| `sap.cloud-logging.cf.binding.tag.value` | The tag value used to identify managed Cloud Logging service bindings. | `Cloud Logging` | +| `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. | | + ## Using User-Provided Service Instances ### SAP Cloud Logging @@ -167,12 +204,12 @@ Furthermore, this helps on sharing service instances across CF orgs or landscape The extension requires four fields in the user-provided service credentials and needs to be tagged with the `otel.javaagent.extension.sap.cf.binding.cloud-logging.tag` (default: `Cloud Logging`) documented in section [Configuration](#configuration). -| Field name | Contents | -|------------|----------| -| `ingest-otlp-endpoint` | The OTLP endpoint including port. It will be prefixed with `https://`. | -| `ingest-otlp-key` | The mTLS client key in PCKS#8 format. Line breaks as `\n`. | -| `ingest-otlp-cert`| The mTLS client certificate in PEM format matching the client key. Line breaks as `\n`. | -| `server-ca` | The trusted mTLS server certificate in PEM format. Line breaks as `\n`. | +| Field name | Contents | +|------------------------|-----------------------------------------------------------------------------------------| +| `ingest-otlp-endpoint` | The OTLP endpoint including port. It will be prefixed with `https://`. | +| `ingest-otlp-key` | The mTLS client key in PCKS#8 format. Line breaks as `\n`. | +| `ingest-otlp-cert` | The mTLS client certificate in PEM format matching the client key. Line breaks as `\n`. | +| `server-ca` | The trusted mTLS server certificate in PEM format. Line breaks as `\n`. | If you have a [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) service key, you can generate the required JSON file with jq: @@ -198,23 +235,23 @@ You can even change the tag using the configuration parameters of the extension. SAP BTP internally offers a managed Dynatrace service, that is recognized by the extension. Externally, user-provided service instances need to be created. The [Dynatrace documentation](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring) explains, how to generate the necessary access url and tokens. -The extension requires two fields in the user-provided service credentials and needs to be tagged with the `otel.javaagent.extension.sap.cf.binding.dynatrace.tag` (default: `dynatrace`) documented in section [Configuration](#configuration). +The extension requires two fields in the user-provided service credentials and needs to be tagged with the `sap.dynatrace.cf.binding.tag.value` (default: `dynatrace`) documented in section [Configuration](#configuration). -| Field name | Contents | -|------------|----------| -| `apiurl` | The Dynatrace API endpoint, e.g. `https://apm.example.com/e//api`. This url will be appended with `/v2/otlp/v1/metrics` to create the full endpoint url. | -| `` | The API token to be used with the above endpoint. Ensure, that it has the required permissions to ingest data over the endpoint. | +| Field name | Contents | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `apiurl` | The Dynatrace API endpoint, e.g. `https://apm.example.com/e//api`. This url will be appended with `/v2/otlp/v1/metrics` to create the full endpoint url. | +| `` | The API token to be used with the above endpoint. Ensure, that it has the required permissions to ingest data over the endpoint. | Do not forget to configure the name chosen for `` via the respective configuration property: ```sh java #... \ --Dotel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name= \ +-Dsap.dynatrace.cf.binding.token.name= \ # ... # or -OTEL_JAVAAGENT_EXTENSION_SAP_CF_BINDING_DYNATRACE_METRICS_TOKEN-NAME= +SAP_DYNATRACE_CF_BINDING_TOKEN_NAME= java #... ``` @@ -225,7 +262,5 @@ The difference is just during the bootstrapping phase. The main differences are: * The `cloud-logging` exporter will send data to all found bindings to SAP Cloud Logging. -The auto-instrumentation of the `otlp` exporter will just configure the first binding it finds priotizing user-provided services. * The `otlp` configuration will write the required certificates and keys to temporary files, which are deleted when the JVM is shut down. The `cloud-logging` exporter will keep the secrets in memory. -* Since the `otlp` exporter is the default for traces and metrics, just attaching the extension and binding to Cloud Logging will result in metrics and traces being forwarded. -The `cloud-logging` exporter needs to be configured explictly as does the `otlp` exporter for logs. +* The `cloud-logging` exporter needs to be configured explicitly. diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/attributes/CloudFoundryResourceCustomizer.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/attributes/CloudFoundryResourceCustomizer.java index 12ca24a3..f041745e 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/attributes/CloudFoundryResourceCustomizer.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/attributes/CloudFoundryResourceCustomizer.java @@ -1,5 +1,6 @@ package com.sap.hcf.cf.logging.opentelemetry.agent.ext.attributes; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.RESOURCE; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; @@ -13,10 +14,6 @@ public class CloudFoundryResourceCustomizer implements BiFunction { - private static final String OTEL_JAVAAGENT_EXTENSION_SAP_CF_RESOURCE_ENABLED = - "otel.javaagent.extension.sap.cf.resource.enabled"; - private static final String OTEL_JAVAAGENT_EXTENTION_CF_RESOURCE_FORMAT = - "otel.javaagent.extension.sap.cf.resource.format"; private static final Logger LOG = Logger.getLogger(CloudFoundryResourceCustomizer.class.getName()); private static final Map SAP_CF_RESOURCE_ATTRIBUTES = new HashMap() {{ put("cloudfoundry.app.id", "sap.cf.app_id"); @@ -32,7 +29,7 @@ public class CloudFoundryResourceCustomizer implements BiFunction defaults = new HashMap<>(); - defaults.put("com.sap.otel.extension.cloud-logging.label", "cloud-logging"); - defaults.put("com.sap.otel.extension.cloud-logging.tag", "Cloud Logging"); + defaults.put(DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL_SAP.getKey(), "cloud-logging"); + defaults.put(DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG_SAP.getKey(), "Cloud Logging"); defaults.put("otel.javaagent.extension.sap.cf.binding.user-provided.label", "user-provided"); ComponentLoader componentLoader = ComponentLoader.forClassLoader(DefaultConfigProperties.class.getClassLoader()); diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/CloudLoggingServicesProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/CloudLoggingServicesProvider.java index 1e3b0d1f..0f2dfbd3 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/CloudLoggingServicesProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/CloudLoggingServicesProvider.java @@ -1,5 +1,7 @@ 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.config.ExtensionConfigurations.RUNTIME; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.List; @@ -10,10 +12,6 @@ public class CloudLoggingServicesProvider implements Supplier> { - private static final String DEFAULT_USER_PROVIDED_LABEL = "user-provided"; - private static final String DEFAULT_CLOUD_LOGGING_LABEL = "cloud-logging"; - private static final String DEFAULT_CLOUD_LOGGING_TAG = "Cloud Logging"; - private final List services; public CloudLoggingServicesProvider(ConfigProperties config) { @@ -27,20 +25,15 @@ public CloudLoggingServicesProvider(ConfigProperties config) { } private String getUserProvidedLabel(ConfigProperties config) { - return config.getString("otel.javaagent.extension.sap.cf.binding.user-provided.label", - DEFAULT_USER_PROVIDED_LABEL); + return DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.USER_PROVIDED.LABEL_OTEL.getValue(config); } private String getCloudLoggingLabel(ConfigProperties config) { - String fromOwnProperties = - System.getProperty("com.sap.otel.extension.cloud-logging.label", DEFAULT_CLOUD_LOGGING_LABEL); - return config.getString("otel.javaagent.extension.sap.cf.binding.cloud-logging.label", fromOwnProperties); + return RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL.getValue(config); } private String getCloudLoggingTag(ConfigProperties config) { - String fromOwnProperties = - System.getProperty("com.sap.otel.extension.cloud-logging.tag", DEFAULT_CLOUD_LOGGING_TAG); - return config.getString("otel.javaagent.extension.sap.cf.binding.cloud-logging.tag", fromOwnProperties); + return RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG.getValue(config); } @Override diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/DynatraceServiceProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/DynatraceServiceProvider.java index aca506ce..8e04e5b1 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/DynatraceServiceProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/DynatraceServiceProvider.java @@ -1,5 +1,7 @@ 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.config.ExtensionConfigurations.RUNTIME; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.List; @@ -7,10 +9,6 @@ public class DynatraceServiceProvider implements Supplier { - private static final String DEFAULT_USER_PROVIDED_LABEL = "user-provided"; - private static final String DEFAULT_DYNATRACE_LABEL = "dynatrace"; - private static final String DEFAULT_DYNATRACE_TAG = "dynatrace"; - private final CloudFoundryServiceInstance service; public DynatraceServiceProvider(ConfigProperties config) { @@ -24,16 +22,15 @@ public DynatraceServiceProvider(ConfigProperties config) { } private String getUserProvidedLabel(ConfigProperties config) { - return config.getString("otel.javaagent.extension.sap.cf.binding.user-provided.label", - DEFAULT_USER_PROVIDED_LABEL); + return DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.USER_PROVIDED.LABEL_OTEL.getValue(config); } private String getDynatraceLabel(ConfigProperties config) { - return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.label", DEFAULT_DYNATRACE_LABEL); + return RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.LABEL.getValue(config); } private String getDynatraceTag(ConfigProperties config) { - return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.tag", DEFAULT_DYNATRACE_TAG); + return RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TAG.getValue(config); } @Override diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ConfigProperty.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ConfigProperty.java new file mode 100644 index 00000000..85182b10 --- /dev/null +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ConfigProperty.java @@ -0,0 +1,114 @@ +package com.sap.hcf.cf.logging.opentelemetry.agent.ext.config; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; + +import java.time.Duration; +import java.util.List; +import java.util.function.BiFunction; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.util.Collections.emptyList; + +public class ConfigProperty { + + private static final Logger LOG = Logger.getLogger(ConfigProperty.class.getName()); + + private final String key; + private final ConfigProperty fallback; + private final T defaultValue; + private final BiFunction extractor; + private final boolean deprecated; + + private ConfigProperty(Builder builder) { + this.key = builder.key; + this.fallback = builder.fallback; + this.defaultValue = builder.defaultValue; + this.extractor = builder.extractor; + this.deprecated = builder.deprecated; + } + + public T getValue(ConfigProperties config) { + if (config == null) { + LOG.warning( + "No configuration provided, using default value \"" + defaultValue + "\" for key: \"" + key + "\""); + return defaultValue; + } + T directValue = extractor.apply(config, key); + if (directValue != null) { + return directValue; + } + if (fallback == null) { + return defaultValue; + } + T fallbackValue = fallback.getValue(config); + if (fallbackValue != null && !fallbackValue.equals(defaultValue)) { + if (fallback.deprecated && LOG.isLoggable(Level.WARNING)) { + LOG.warning( + "Using deprecated configuration key \"" + fallback.key + "\". Please migrate to key: \"" + key + "\""); + } + return fallbackValue; + } + return defaultValue; + } + + public String getKey() { + return key; + } + + T getDefaultValue() { + return defaultValue; + } + + static Builder stringValued(String key) { + return new Builder<>(ConfigProperties::getString).withKey(key); + } + + static Builder booleanValued(String key) { + return new Builder<>(ConfigProperties::getBoolean).withKey(key); + } + + static Builder durationValued(String key) { + return new Builder<>(ConfigProperties::getDuration).withKey(key); + } + + static Builder> listValued(String key) { + return new Builder<>(ConfigProperties::getList).withKey(key).withDefaultValue(emptyList()); + } + + static class Builder { + private final BiFunction extractor; + private String key; + private T defaultValue; + private ConfigProperty fallback; + private boolean deprecated; + + Builder(BiFunction extractor) { + this.extractor = extractor; + } + + private Builder withKey(String key) { + this.key = key; + return this; + } + + Builder withDefaultValue(T defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + Builder withFallback(ConfigProperty fallback) { + this.fallback = fallback; + return this; + } + + Builder setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + return this; + } + + ConfigProperty build() { + return new ConfigProperty<>(this); + } + } +} diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurations.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurations.java new file mode 100644 index 00000000..c17ebb10 --- /dev/null +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurations.java @@ -0,0 +1,325 @@ +package com.sap.hcf.cf.logging.opentelemetry.agent.ext.config; + +import java.time.Duration; +import java.util.List; + +import static com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ConfigProperty.*; + +public interface ExtensionConfigurations { + + interface EXPORTER { + interface CLOUD_LOGGING { + interface GENERAL { + /** + *

Parses {@code otel.exporter.cloud-logging.compression}.

+ *

The compression algorithm to use when exporting logs. Default is {@code "gzip"}.

+ */ + ConfigProperty COMPRESSION = + stringValued("otel.exporter.cloud-logging.compression").withDefaultValue("gzip").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.timeout}.

+ *

The maximum duration to wait for Cloud Logging when exporting data.

+ */ + ConfigProperty TIMEOUT = durationValued("otel.exporter.cloud-logging.timeout").build(); + } + + interface LOGS { + /** + *

Parses {@code otel.exporter.cloud-logging.logs.compression}.

+ *

The compression algorithm to use when exporting logs. Default is {@code "gzip"}. Falls back to + * {@code otel.exporter.cloud-logging.compression} if not set.

+ */ + ConfigProperty COMPRESSION = + stringValued("otel.exporter.cloud-logging.logs.compression").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.COMPRESSION).withDefaultValue("gzip").build(); + + /** + *

{@code otel.exporter.cloud-logging.logs.timeout}.

+ *

The maximum duration to wait for Cloud Logging when exporting logs. Falls back to + * {@code otel.exporter.cloud-logging.timeout} if not set.

+ */ + ConfigProperty TIMEOUT = + durationValued("otel.exporter.cloud-logging.logs.timeout").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.TIMEOUT).build(); + } + + interface METRICS { + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.compression}.

+ *

The compression algorithm to use when exporting metrics. Default is {@code "gzip"}. Falls back to + * {@code otel.exporter.cloud-logging.compression} if not set.

+ */ + ConfigProperty COMPRESSION = + stringValued("otel.exporter.cloud-logging.metrics.compression").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.COMPRESSION).withDefaultValue("gzip").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.default.histogram.aggregation}.

+ *

The default histogram aggregation for metrics exported to Cloud Logging. Delegates to the + * underlying OTLP exporter, supporting all its configurations.

+ */ + ConfigProperty DEFAULT_HISTOGRAM_AGGREGATION = + stringValued("otel.exporter.cloud-logging.metrics.default.histogram.aggregation").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.exclude.names}.

+ *

A comma-seperated list of metric name patterns to be excluded when exporting metrics to Cloud + * Logging. Wildcard "*" is only supported at the end of the name. If not set, no metrics are + * excluded.

+ */ + ConfigProperty> EXCLUDE_NAMES = + listValued("otel.exporter.cloud-logging.metrics.exclude.names").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.include.names}.

+ *

A comma-seperated list of metric name patterns to be included when exporting metrics to Cloud + * Logging. Wildcard "*" is only supported at the end of the name. If not set, all metrics are + * exported.

+ */ + ConfigProperty> INCLUDE_NAMES = + listValued("otel.exporter.cloud-logging.metrics.include.names").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.temporality.preference}.

+ *

The preferred aggregation temporality for metrics exported to Cloud Logging. Can be either + * {@code "cumulative"}, {@code "delta"}, or {@code "lowmemory"}. Default is {@code "cumulative"}.

+ */ + ConfigProperty TEMPORALITY_PREFERENCE = + stringValued("otel.exporter.cloud-logging.metrics.temporality.preference").withDefaultValue( + "cumulative").build(); + /** + *

Parses {@code otel.exporter.cloud-logging.metrics.timeout}.

+ *

The maximum duration to wait for Cloud Logging when exporting metrics. Falls back to + * {@code otel.exporter.cloud-logging.timeout} if not set.

+ */ + ConfigProperty TIMEOUT = + durationValued("otel.exporter.cloud-logging.metrics.timeout").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.TIMEOUT).build(); + } + + interface TRACES { + /** + *

Parses {@code otel.exporter.cloud-logging.traces.compression}.

+ *

The compression algorithm to use when exporting traces. Default is {@code "gzip"}. Falls back to + * {@code otel.exporter.cloud-logging.compression} if not set.

+ */ + ConfigProperty COMPRESSION = + stringValued("otel.exporter.cloud-logging.traces.compression").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.COMPRESSION).withDefaultValue("gzip").build(); + + /** + *

Parses {@code otel.exporter.cloud-logging.traces.timeout}.

+ *

The maximum duration to wait for Cloud Logging when exporting traces. Falls back to + * {@code otel.exporter.cloud-logging.timeout} if not set.

+ */ + ConfigProperty TIMEOUT = + durationValued("otel.exporter.cloud-logging.traces.timeout").withFallback( + EXPORTER.CLOUD_LOGGING.GENERAL.TIMEOUT).build(); + } + } + + interface DYNATRACE { + interface METRICS { + /** + *

Parses {@code otel.exporter.dynatrace.metrics.compression}.

+ *

The compression algorithm to use when exporting metrics. Default is {@code "gzip"}.

+ */ + ConfigProperty COMPRESSION = + stringValued("otel.exporter.dynatrace.metrics.compression").withDefaultValue("gzip").build(); + + /** + *

Parses {@code otel.exporter.dynatrace.metrics.default.histogram.aggregation}.

+ *

The default histogram aggregation for metrics exported to Dynatrace. Delegates to the underlying + * OTLP exporter, supporting all its configurations.

+ */ + ConfigProperty DEFAULT_HISTOGRAM_AGGREGATION = + stringValued("otel.exporter.dynatrace.metrics.default.histogram.aggregation").build(); + + /** + *

Parses {@code otel.exporter.dynatrace.metrics.exclude.names}.

+ *

A comma-seperated list of metric name patterns to be excluded when exporting metrics to + * CDynatrace. Wildcard "*" is only supported at the end of the name. If not set, no metrics are + * excluded.

+ */ + ConfigProperty> EXCLUDE_NAMES = + listValued("otel.exporter.dynatrace.metrics.exclude.names").build(); + + /** + *

Parses {@code otel.exporter.dynatrace.metrics.include.names}.

+ *

A comma-seperated list of metric name patterns to be included when exporting metrics to + * Dynatrace. Wildcard "*" is only supported at the end of the name. If not set, all metrics are + * exported.

+ */ + ConfigProperty> INCLUDE_NAMES = + listValued("otel.exporter.dynatrace.metrics.include.names").build(); + + /** + *

Parses {@code otel.exporter.dynatrace.metrics.default.histogram.aggregation}.

+ *

The default + * histogram aggregation for metrics exported to Dynatrace. Delegates to the underlying OTLP exporter, + * supporting all its configurations.

+ *

The Dynatrace metrics exporter provides an additional option {@code "always_delta"} which always + * uses delta aggregation temporality. This is also the default behavior if the property is not + * set.

+ */ + ConfigProperty TEMPORALITY_PREFERENCE = + stringValued("otel.exporter.dynatrace.metrics.temporality.preference").withDefaultValue( + "always_delta").build(); + + /** + *

Parses {@code otel.exporter.dynatrace.metrics.timeout}.

+ *

The maximum duration to wait for Dynatrace when exporting metrics.

+ */ + ConfigProperty TIMEOUT = durationValued("otel.exporter.dynatrace.metrics.timeout").build(); + + } + } + } + + interface EXTENSION { + interface SANITIZER { + /** + *

Parses {@code sap.cf.integration.otel.extension.sanitizer.enabled}.

+ *

Enables or disables the sanitizer. Default is {@code true}.

+ */ + ConfigProperty ENABLED = + booleanValued("sap.cf.integration.otel.extension.sanitizer.enabled").withDefaultValue(true).build(); + } + } + + interface RESOURCE { + interface CLOUD_FOUNDRY { + /** + *

Parses {@code sap.cloudfoundry.otel.resources.enabled}.

+ *

Should Cloud Foundry resource attributes be added to the OpenTelemetry resource? Default is + * {@code true}.

+ */ + ConfigProperty ENABLED = booleanValued("sap.cloudfoundry.otel.resources.enabled").withFallback( + DEPRECATED.RESOURCE.CLOUD_FOUNDRY.ENABLED).withDefaultValue(true).build(); + /** + *

Parses {@code sap.cloudfoundry.otel.resources.format}.

+ *

Determines the semantic convention used for Cloud Foundry resource attributes names.

+ *
    + *
  • {@code "SAP"} - use SAP specific attribute names (default)
  • + *
  • {@code "OTEL"} - use OpenTelemetry semantic convention attribute names
  • + *
+ */ + ConfigProperty FORMAT = stringValued("sap.cloudfoundry.otel.resources.format").withFallback( + DEPRECATED.RESOURCE.CLOUD_FOUNDRY.FORMAT).withDefaultValue("SAP").build(); + } + } + + interface RUNTIME { + interface CLOUD_FOUNDRY { + interface SERVICE { + interface CLOUD_LOGGING { + /** + *

Parses {@code sap.cloud-logging.cf.binding.label.value}.

+ *

The label value used to identify managed Cloud Logging service bindings. Default is + * {@code "cloud-logging"}.

+ */ + ConfigProperty LABEL = + stringValued("sap.cloud-logging.cf.binding.label.value").withFallback( + DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL_OTEL) + .withDefaultValue("cloud-logging") + .build(); + /** + *

Parses {@code sap.cloud-logging.cf.binding.tag.value}.

+ *

The tag value used to identify managed Cloud Logging service bindings. Default is + * {@code "Cloud Logging"}.

+ */ + ConfigProperty TAG = stringValued("sap.cloud-logging.cf.binding.tag.value").withFallback( + DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG_OTEL).withDefaultValue( + "Cloud Logging").build(); + } + + interface DYNATRACE { + /** + *

Parses {@code sap.dynatrace.cf.binding.label.value}.

+ *

The label value used to identify managed Dynatrace service bindings. Default is + * {@code "dynatrace"}.

+ */ + ConfigProperty LABEL = stringValued("sap.dynatrace.cf.binding.label.value").withFallback( + DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.LABEL_OTEL).withDefaultValue("dynatrace") + .build(); + /** + *

Parses {@code sap.dynatrace.cf.binding.tag.value}.

+ *

The tag value used to identify managed Dynatrace service bindings. Default is + * {@code "dynatrace"}.

+ */ + ConfigProperty TAG = stringValued("sap.dynatrace.cf.binding.tag.value").withFallback( + DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TAG_OTEL).withDefaultValue("dynatrace") + .build(); + + ConfigProperty TOKEN_NAME = + stringValued("sap.dynatrace.cf.binding.token.name").withFallback( + DEPRECATED.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TOKEN_NAME_OTEL).build(); + } + + } + } + } + + @Deprecated(since = "4.1.0", forRemoval = true) + interface DEPRECATED { + interface RESOURCE { + interface CLOUD_FOUNDRY { + ConfigProperty ENABLED = + booleanValued("otel.javaagent.extension.sap.cf.resource.enabled").setDeprecated(true) + .withDefaultValue(true) + .build(); + ConfigProperty FORMAT = + stringValued("otel.javaagent.extension.sap.cf.resource.format").setDeprecated(true) + .withDefaultValue("SAP").build(); + } + } + + interface RUNTIME { + interface CLOUD_FOUNDRY { + interface SERVICE { + interface CLOUD_LOGGING { + ConfigProperty LABEL_SAP = + stringValued("com.sap.otel.extension.cloud-logging.label").setDeprecated(true) + .withDefaultValue( + "cloud-logging") + .build(); + ConfigProperty LABEL_OTEL = stringValued( + "otel.javaagent.extension.sap.cf.binding.cloud-logging.label").setDeprecated(true) + .withFallback(LABEL_SAP) + .build(); + ConfigProperty TAG_SAP = + stringValued("com.sap.otel.extension.cloud-logging.tag").setDeprecated(true) + .withDefaultValue( + "Cloud Logging") + .build(); + ConfigProperty TAG_OTEL = + stringValued("otel.javaagent.extension.sap.cf.binding.cloud-logging.tag").setDeprecated( + true).withFallback(TAG_SAP).build(); + } + + interface DYNATRACE { + ConfigProperty LABEL_OTEL = + stringValued("otel.javaagent.extension.sap.cf.binding.dynatrace.label").setDeprecated( + true).withDefaultValue("dynatrace").build(); + ConfigProperty TAG_OTEL = + stringValued("otel.javaagent.extension.sap.cf.binding.dynatrace.tag").setDeprecated( + true).withDefaultValue("dynatrace").build(); + + ConfigProperty TOKEN_NAME_OTEL = stringValued( + "otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name").setDeprecated( + true).build(); + } + + interface USER_PROVIDED { + ConfigProperty LABEL_OTEL = stringValued( + "otel.javaagent.extension.sap.cf.binding.user-provided.label").setDeprecated(true) + .withDefaultValue( + "user-provided") + .build(); + } + } + } + } + } +} diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingLogsExporterProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingLogsExporterProvider.java index aa9a0e96..f5eecc1f 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingLogsExporterProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingLogsExporterProvider.java @@ -2,6 +2,7 @@ import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudFoundryServiceInstance; import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingServicesProvider; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.EXPORTER; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -34,13 +35,11 @@ public CloudLoggingLogsExporterProvider() { } 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"); + return EXPORTER.CLOUD_LOGGING.LOGS.COMPRESSION.getValue(config); } 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"); + return EXPORTER.CLOUD_LOGGING.LOGS.TIMEOUT.getValue(config); } @Override diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingMetricsExporterProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingMetricsExporterProvider.java index d7895194..fb03c4a6 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingMetricsExporterProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingMetricsExporterProvider.java @@ -2,6 +2,7 @@ import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudFoundryServiceInstance; import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingServicesProvider; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.EXPORTER; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -27,7 +28,6 @@ public class CloudLoggingMetricsExporterProvider implements ConfigurableMetricExporterProvider { - private static final String GENERIC_CONFIG_PREFIX = "otel.exporter.cloud-logging."; private static final String METRICS_CONFIG_PREFIX = "otel.exporter.cloud-logging.metrics."; private static final Logger LOG = Logger.getLogger(CloudLoggingMetricsExporterProvider.class.getName()); @@ -45,21 +45,15 @@ public CloudLoggingMetricsExporterProvider() { } private static String getCompression(ConfigProperties config) { - String compression = config.getString(METRICS_CONFIG_PREFIX + "compression"); - return compression != null ? compression : config.getString(GENERIC_CONFIG_PREFIX + "compression", "gzip"); + return EXPORTER.CLOUD_LOGGING.METRICS.COMPRESSION.getValue(config); } private static Duration getTimeOut(ConfigProperties config) { - Duration timeout = config.getDuration(METRICS_CONFIG_PREFIX + "timeout"); - return timeout != null ? timeout : config.getDuration(GENERIC_CONFIG_PREFIX + "timeout"); + return EXPORTER.CLOUD_LOGGING.METRICS.TIMEOUT.getValue(config); } private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) { - String temporalityStr = config.getString(METRICS_CONFIG_PREFIX + "temporality.preference"); - if (temporalityStr == null) { - return AggregationTemporalitySelector.alwaysCumulative(); - } - AggregationTemporalitySelector temporalitySelector; + String temporalityStr = EXPORTER.CLOUD_LOGGING.METRICS.TEMPORALITY_PREFERENCE.getValue(config); switch (temporalityStr.toLowerCase(Locale.ROOT)) { case "cumulative": return AggregationTemporalitySelector.alwaysCumulative(); @@ -73,7 +67,8 @@ private static AggregationTemporalitySelector getAggregationTemporalitySelector( } private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) { - String defaultHistogramAggregation = config.getString(METRICS_CONFIG_PREFIX + "default.histogram.aggregation"); + String defaultHistogramAggregation = + EXPORTER.CLOUD_LOGGING.METRICS.DEFAULT_HISTOGRAM_AGGREGATION.getValue(config); if (defaultHistogramAggregation == null) { return DefaultAggregationSelector.getDefault() .with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation()); @@ -104,9 +99,9 @@ public MetricExporter createExporter(ConfigProperties config) { .collect(Collectors.toList()); MetricExporter exporter = MultiMetricExporter.composite(exporters, getAggregationTemporalitySelector(config), getDefaultAggregationSelector(config)); - exporter = FilteringMetricExporter.wrap(exporter).withConfig(config).withPropertyPrefix(METRICS_CONFIG_PREFIX) - .build(); - return exporter; + return FilteringMetricExporter.wrap(exporter).withConfig(config) + .withIncludedNames(EXPORTER.CLOUD_LOGGING.METRICS.INCLUDE_NAMES) + .withExcludedNames(EXPORTER.CLOUD_LOGGING.METRICS.EXCLUDE_NAMES).build(); } private MetricExporter createExporter(ConfigProperties config, CloudFoundryServiceInstance service) { diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingSpanExporterProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingSpanExporterProvider.java index d62af7e9..c6b22897 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingSpanExporterProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingSpanExporterProvider.java @@ -2,6 +2,7 @@ import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudFoundryServiceInstance; import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingServicesProvider; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.EXPORTER; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -34,13 +35,11 @@ public CloudLoggingSpanExporterProvider() { } private static String getCompression(ConfigProperties config) { - String compression = config.getString("otel.exporter.cloud-logging.traces.compression"); - return compression != null ? compression : config.getString("otel.exporter.cloud-logging.compression", "gzip"); + return EXPORTER.CLOUD_LOGGING.TRACES.COMPRESSION.getValue(config); } private static Duration getTimeOut(ConfigProperties config) { - Duration timeout = config.getDuration("otel.exporter.cloud-logging.traces.timeout"); - return timeout != null ? timeout : config.getDuration("otel.exporter.cloud-logging.timeout"); + return EXPORTER.CLOUD_LOGGING.TRACES.TIMEOUT.getValue(config); } @Override diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/DynatraceMetricsExporterProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/DynatraceMetricsExporterProvider.java index 0b43ed88..2bc6b263 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/DynatraceMetricsExporterProvider.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/DynatraceMetricsExporterProvider.java @@ -3,6 +3,8 @@ import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudFoundryCredentials; import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudFoundryServiceInstance; import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.DynatraceServiceProvider; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.DEPRECATED; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.EXPORTER; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -45,21 +47,15 @@ public DynatraceMetricsExporterProvider(Function> included; + private ConfigProperty> excluded; public Builder(MetricExporter delegate) { this.delegate = delegate; @@ -85,8 +89,13 @@ public Builder withConfig(ConfigProperties config) { return this; } - public Builder withPropertyPrefix(String prefix) { - this.prefix = prefix.endsWith(".") ? prefix : prefix + "."; + public Builder withIncludedNames(ConfigProperty> included) { + this.included = included; + return this; + } + + public Builder withExcludedNames(ConfigProperty> excluded) { + this.excluded = excluded; return this; } @@ -95,8 +104,8 @@ public MetricExporter build() { return delegate; } - List includedNames = config.getList(prefix + INCLUDED_NAMES_KEY); - List excludedNames = config.getList(prefix + EXCLUDED_NAMES_KEY); + List includedNames = ofNullable(included).map(p -> p.getValue(config)).orElse(emptyList()); + List excludedNames = ofNullable(excluded).map(p -> p.getValue(config)).orElse(emptyList()); if (includedNames.isEmpty() && excludedNames.isEmpty()) { return delegate; } diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java index 0690833e..cf321f82 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java @@ -1,5 +1,6 @@ package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.EXTENSION; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -24,7 +25,7 @@ public class SanitizeSpanExporterCustomizer implements BiFunction prop = + ConfigProperty.stringValued("non.existent.key").withDefaultValue("defaultValue").build(); + String value = prop.getValue(null); + assertThat(value).isEqualTo("defaultValue"); + } + + @Test + void returnsNullWhenDefaultIsNull() { + ConfigProperty prop = ConfigProperty.stringValued("non.existent.key").build(); + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + String value = prop.getValue(config); + assertThat(value).isNull(); + } + + @Test + void returnsDefaultStringWhenNotSet() { + ConfigProperty prop = + ConfigProperty.stringValued("non.existent.key").withDefaultValue("defaultValue").build(); + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + String value = prop.getValue(config); + assertThat(value).isEqualTo("defaultValue"); + } + + @Test + void returnsStringWhenSet() { + ConfigProperty prop = + ConfigProperty.stringValued("existent.key").withDefaultValue("defaultValue").build(); + DefaultConfigProperties config = createFromMap(Collections.singletonMap("existent.key", "actualValue")); + String value = prop.getValue(config); + assertThat(value).isEqualTo("actualValue"); + } + + @Test + void returnsFallbackStringWhenNotSet() { + ConfigProperty fallbackProp = + ConfigProperty.stringValued("fallback.key").withDefaultValue("defaultValue").build(); + ConfigProperty prop = + ConfigProperty.stringValued("non.existent.key").withFallback(fallbackProp).build(); + DefaultConfigProperties config = createFromMap(Collections.singletonMap("fallback.key", "fallbackValue")); + String value = prop.getValue(config); + assertThat(value).isEqualTo("fallbackValue"); + } + + @Test + void primaryKeyOverridesFallback() { + ConfigProperty fallbackProp = + ConfigProperty.stringValued("fallback.key").withDefaultValue("defaultValue").build(); + ConfigProperty prop = ConfigProperty.stringValued("primary.key").withFallback(fallbackProp).build(); + DefaultConfigProperties config = createFromMap(new java.util.HashMap() {{ + put("primary.key", "primaryValue"); + put("fallback.key", "fallbackValue"); + }}); + String value = prop.getValue(config); + assertThat(value).isEqualTo("primaryValue"); + } + + @Test + void returnsDefaultBooleanWhenNotSet() { + ConfigProperty prop = ConfigProperty.booleanValued("non.existent.key").withDefaultValue(true).build(); + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + Boolean value = prop.getValue(config); + assertThat(value).isTrue(); + } + + @Test + void returnsBooleanWhenSet() { + ConfigProperty prop = ConfigProperty.booleanValued("existent.key").withDefaultValue(true).build(); + DefaultConfigProperties config = createFromMap(Collections.singletonMap("existent.key", "false")); + Boolean value = prop.getValue(config); + assertThat(value).isFalse(); + } + + @Test + void returnsFallbackBooleanWhenNotSet() { + ConfigProperty fallbackProp = ConfigProperty.booleanValued("fallback.key").build(); + ConfigProperty prop = + ConfigProperty.booleanValued("non.existent.key").withFallback(fallbackProp).build(); + DefaultConfigProperties config = createFromMap(Collections.singletonMap("fallback.key", "true")); + Boolean value = prop.getValue(config); + assertThat(value).isTrue(); + } +} diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurationsTest.java b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurationsTest.java new file mode 100644 index 00000000..33cc8b9d --- /dev/null +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurationsTest.java @@ -0,0 +1,181 @@ +package com.sap.hcf.cf.logging.opentelemetry.agent.ext.config; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.of; + +class ExtensionConfigurationsTest { + + private static Stream provideStringProperties() { + return Stream.of(of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.LOGS.COMPRESSION, + "otel.exporter.cloud-logging.logs.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.LOGS.COMPRESSION, + "otel.exporter.cloud-logging.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.COMPRESSION, + "otel.exporter.cloud-logging.metrics.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.COMPRESSION, + "otel.exporter.cloud-logging.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.TRACES.COMPRESSION, + "otel.exporter.cloud-logging.traces.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.TRACES.COMPRESSION, + "otel.exporter.cloud-logging.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.GENERAL.COMPRESSION, + "otel.exporter.cloud-logging.compression", "none"), + of(ExtensionConfigurations.EXPORTER.DYNATRACE.METRICS.COMPRESSION, + "otel.exporter.dynatrace.metrics.compression", "none"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.DEFAULT_HISTOGRAM_AGGREGATION, + "otel.exporter.cloud-logging.metrics.default.histogram.aggregation", + "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM"), + of(ExtensionConfigurations.EXPORTER.DYNATRACE.METRICS.DEFAULT_HISTOGRAM_AGGREGATION, + "otel.exporter.dynatrace.metrics.default.histogram.aggregation", + "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.TEMPORALITY_PREFERENCE, + "otel.exporter.cloud-logging.metrics.temporality.preference", "lowmemory"), + of(ExtensionConfigurations.EXPORTER.DYNATRACE.METRICS.TEMPORALITY_PREFERENCE, + "otel.exporter.dynatrace.metrics.temporality.preference", "lowmemory"), + of(ExtensionConfigurations.RESOURCE.CLOUD_FOUNDRY.FORMAT, + "sap.cloudfoundry.otel.resources.format", "OTEL"), + of(ExtensionConfigurations.RESOURCE.CLOUD_FOUNDRY.FORMAT, + "otel.javaagent.extension.sap.cf.resource.format", "OTEL"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL, + "sap.cloud-logging.cf.binding.label.value", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL, + "otel.javaagent.extension.sap.cf.binding.cloud-logging.label", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.LABEL, + "com.sap.otel.extension.cloud-logging.label", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG, + "sap.cloud-logging.cf.binding.tag.value", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG, + "otel.javaagent.extension.sap.cf.binding.cloud-logging.tag", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CLOUD_LOGGING.TAG, + "com.sap.otel.extension.cloud-logging.tag", "cls"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.LABEL, + "sap.dynatrace.cf.binding.label.value", "dt"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.LABEL, + "otel.javaagent.extension.sap.cf.binding.dynatrace.label", "dt"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TAG, + "sap.dynatrace.cf.binding.tag.value", "dt"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TAG, + "otel.javaagent.extension.sap.cf.binding.dynatrace.tag", "dt"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TOKEN_NAME, + "sap.dynatrace.cf.binding.token.name", "api-token"), + of(ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.DYNATRACE.TOKEN_NAME, + "otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name", "api-token")); + } + + private static Stream provideBooleanProperties() { + return Stream.of(of(ExtensionConfigurations.EXTENSION.SANITIZER.ENABLED, + "sap.cf.integration.otel.extension.sanitizer.enabled", "false"), + of(ExtensionConfigurations.RESOURCE.CLOUD_FOUNDRY.ENABLED, + "sap.cloudfoundry.otel.resources.enabled", "false"), + of(ExtensionConfigurations.RESOURCE.CLOUD_FOUNDRY.ENABLED, + "otel.javaagent.extension.sap.cf.resource.enabled", "false")); + } + + private static Stream provideDurationProperties() { + return Stream.of(of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.LOGS.TIMEOUT, + "otel.exporter.cloud-logging.logs.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.LOGS.TIMEOUT, + "otel.exporter.cloud-logging.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.TIMEOUT, + "otel.exporter.cloud-logging.metrics.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.TIMEOUT, + "otel.exporter.cloud-logging.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.TRACES.TIMEOUT, + "otel.exporter.cloud-logging.traces.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.TRACES.TIMEOUT, + "otel.exporter.cloud-logging.timeout", "100"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.GENERAL.TIMEOUT, + "otel.exporter.cloud-logging.timeout", "100")); + } + + private static Stream provideListProperties() { + return Stream.of(of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.EXCLUDE_NAMES, + "otel.exporter.cloud-logging.metrics.exclude.names", "metric1,metric2"), + of(ExtensionConfigurations.EXPORTER.CLOUD_LOGGING.METRICS.INCLUDE_NAMES, + "otel.exporter.cloud-logging.metrics.include.names", "metric1,metric2"), + of(ExtensionConfigurations.EXPORTER.DYNATRACE.METRICS.EXCLUDE_NAMES, + "otel.exporter.dynatrace.metrics.exclude.names", "metric1,metric2"), + of(ExtensionConfigurations.EXPORTER.DYNATRACE.METRICS.INCLUDE_NAMES, + "otel.exporter.dynatrace.metrics.include.names", "metric1,metric2")); + } + + @ParameterizedTest + @MethodSource("provideStringProperties") + void retrievesStringValueCorrectly(ConfigProperty property, String key, String expectedValue) { + DefaultConfigProperties config = createFromMap(Collections.singletonMap(key, expectedValue)); + String actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(expectedValue); + } + + @ParameterizedTest + @MethodSource("provideStringProperties") + void retrievesStringDefaultValueCorrectly(ConfigProperty property, String key, String expectedValue) { + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + String actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(property.getDefaultValue()); + } + + @ParameterizedTest + @MethodSource("provideBooleanProperties") + void retrievesBooleanValueCorrectly(ConfigProperty property, String key, String value) { + DefaultConfigProperties config = createFromMap(Collections.singletonMap(key, value)); + Boolean actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(Boolean.parseBoolean(value)); + } + + @ParameterizedTest + @MethodSource("provideBooleanProperties") + void retrievesBooleanDefaultValueCorrectly(ConfigProperty property, String key, String expectedValue) { + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + Boolean actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(property.getDefaultValue()); + } + + @ParameterizedTest + @MethodSource("provideDurationProperties") + void retrievesDurationValueCorrectrly(ConfigProperty property, String key, String value) { + DefaultConfigProperties config = createFromMap(Collections.singletonMap(key, value)); + Duration actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(Duration.ofMillis(Long.parseLong(value))); + } + + @ParameterizedTest + @MethodSource("provideDurationProperties") + void retrievesDurationDefaultValueCorrectly(ConfigProperty property, String key, String expectedValue) { + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + Duration actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(property.getDefaultValue()); + } + + @ParameterizedTest + @MethodSource("provideListProperties") + void retrievesListValueCorrectly(ConfigProperty> property, String key, String value) { + DefaultConfigProperties config = createFromMap(Collections.singletonMap(key, value)); + List actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(Arrays.asList(value.split(","))); + } + + @ParameterizedTest + @MethodSource("provideListProperties") + void retrievesListDefaultValueCorrectly(ConfigProperty> property, String key, String expectedValue) { + DefaultConfigProperties config = createFromMap(Collections.emptyMap()); + List actualValue = property.getValue(config); + assertThat(actualValue).isEqualTo(property.getDefaultValue()); + } + + private DefaultConfigProperties createFromMap(Map properties) { + return DefaultConfigProperties.createFromMap(properties); + } +} diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/FilteringMetricExporterTest.java b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/FilteringMetricExporterTest.java index bafd60ca..eec228d7 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/FilteringMetricExporterTest.java +++ b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/FilteringMetricExporterTest.java @@ -1,5 +1,6 @@ package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter; +import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ConfigProperty; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.common.export.MemoryMode; @@ -22,7 +23,7 @@ import static io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties.createFromMap; import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.verify; @@ -43,6 +44,12 @@ class FilteringMetricExporterTest { @Mock(strictness = LENIENT) private MetricData anotherMetric; + @Mock + private ConfigProperty> includedMetricNames; + + @Mock + private ConfigProperty> excludedMetricNames; + @Captor ArgumentCaptor> exported; @@ -53,9 +60,14 @@ void setUp() { when(anotherMetric.getName()).thenReturn("another"); } + private FilteringMetricExporter.Builder configureMetricExporter() { + return FilteringMetricExporter.wrap(delegate).withIncludedNames(includedMetricNames) + .withExcludedNames(excludedMetricNames); + } + @Test void exportsAllWithoutConfig() { - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) { + try (MetricExporter exporter = configureMetricExporter().build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -65,7 +77,9 @@ void exportsAllWithoutConfig() { @Test void exportsAllWithEmptyConfig() { DefaultConfigProperties config = createFromMap(emptyMap()); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(includedMetricNames.getValue(config)).thenReturn(emptyList()); + when(excludedMetricNames.getValue(config)).thenReturn(emptyList()); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -75,7 +89,8 @@ void exportsAllWithEmptyConfig() { @Test void exportsOnlyIncluded() { ConfigProperties config = createConfig(MapEntry.entry("include.names", "included")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(includedMetricNames.getValue(config)).thenReturn(singletonList("included")); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -83,9 +98,10 @@ void exportsOnlyIncluded() { } @Test - void rejectsEncluded() { + void rejectsExcluded() { ConfigProperties config = createConfig(MapEntry.entry("exclude.names", "excluded")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(excludedMetricNames.getValue(config)).thenReturn(singletonList("excluded")); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -96,7 +112,9 @@ void rejectsEncluded() { void rejectsExcludedFromIncluded() { ConfigProperties config = createConfig(MapEntry.entry("include.names", "included,excluded"), MapEntry.entry("exclude.names", "excluded")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(includedMetricNames.getValue(config)).thenReturn(asList("included", "excluded")); + when(excludedMetricNames.getValue(config)).thenReturn(singletonList("excluded")); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -106,7 +124,8 @@ void rejectsExcludedFromIncluded() { @Test void supportsWildcardsOnIncluded() { ConfigProperties config = createConfig(MapEntry.entry("include.names", "incl*")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(includedMetricNames.getValue(config)).thenReturn(singletonList("incl*")); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); @@ -116,25 +135,14 @@ void supportsWildcardsOnIncluded() { @Test void supportsWildcardsOnEncluded() { ConfigProperties config = createConfig(MapEntry.entry("exclude.names", "excl*")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) { + when(excludedMetricNames.getValue(config)).thenReturn(singletonList("excl*")); + try (MetricExporter exporter = configureMetricExporter().withConfig(config).build()) { exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); } verify(delegate).export(exported.capture()); assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric, anotherMetric); } - @Test - void supportsConfigPrefixes() { - ConfigProperties config = createConfig(MapEntry.entry("config.include.names", "included,excluded"), - MapEntry.entry("config.exclude.names", "excluded")); - try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config) - .withPropertyPrefix("config").build()) { - exporter.export(asList(includedMetric, excludedMetric, anotherMetric)); - } - verify(delegate).export(exported.capture()); - assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric); - } - @SafeVarargs private static ConfigProperties createConfig(MapEntry... entries) { HashMap map = new HashMap<>(); diff --git a/pom.xml b/pom.xml index 657ad0a9..1d45a36e 100644 --- a/pom.xml +++ b/pom.xml @@ -139,6 +139,12 @@ ${junit.version} test + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + org.mockito mockito-core