diff --git a/cf-java-logging-support-opentelemetry-agent-extension/README.md b/cf-java-logging-support-opentelemetry-agent-extension/README.md index 62c2554c..43cc6e08 100644 --- a/cf-java-logging-support-opentelemetry-agent-extension/README.md +++ b/cf-java-logging-support-opentelemetry-agent-extension/README.md @@ -1,22 +1,29 @@ # OpenTelemetry Java Agent Extension for SAP BTP Observability -This module provides an extension for the [OpenTelemetry Java Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/). -The extension scans the service bindings of an application for [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) and [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring). +This module provides an extension for +the [OpenTelemetry Java Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/). +The extension scans the service bindings of an application +for [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) +and [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring). If such a binding is found, the OpenTelemetry Java Agent is configured to ship observability data to those services. Thus, this extension provides a convenient auto-instrumentation for Java applications running on SAP BTP. The extension provides the following main features: -* additional exporters for logs, metrics and traces for [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) -* additional exporter for metrics for [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring) -* auto-configuration of the generic OpenTelemetry connection to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) +* additional exporters for logs, metrics and traces + for [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) +* additional exporter for metrics + for [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring) +* auto-configuration of the generic OpenTelemetry connection + to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) * adding resource attributes describing the CF application See the section on [configuration](#configuration) for further details. ## Quickstart Guide -Any Java application can be instrumented with the OpenTelemetry Java Agent and this extension by adding the following arguments to the java command: +Any Java application can be instrumented with the OpenTelemetry Java Agent and this extension by adding the following +arguments to the java command: ```sh java -javaagent:/path/to/opentelemetry-javaagent-.jar \ @@ -26,7 +33,8 @@ java -javaagent:/path/to/opentelemetry-javaagent-.jar \ If you are using Spring Boot, you can bundle both dependencies with the application. See the Maven pom of the [Spring Boot sample application](../sample-spring-boot/pom.xml) for details. -When deployed to a Cloud Foundry runtime environment, the Spring Boot jar is expanded, so that the agent and extension jar are available during application start. +When deployed to a Cloud Foundry runtime environment, the Spring Boot jar is expanded, so that the agent and extension +jar are available during application start. In that case, the following Java arguments are required: ```sh @@ -35,14 +43,17 @@ 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. +See the [example manifest](../sample-spring-boot/manifest-otel-javaagent.yml), how this translates into a deployment +description. -Once the agent is attached to the JVM with the extension in place, there are two ways, which can be used to send data to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging): +Once the agent is attached to the JVM with the extension in place, there are two ways, which can be used to send data +to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging): 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: + This can be achieved via system properties or environment variables: + ```sh -Dotel.logs.exporter=cloud-logging \ -Dotel.metrics.exporter=cloud-logging,dynatrace \ @@ -72,52 +83,72 @@ java #... ``` Note, that the OpenTelemetry Java Agent currently sends traces and metrics by default using the `otlp` exporter. -That means, without any configuration the agent with the extension will forward metrics and traces to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging). -The difference between `cloud-logging` and `otlp` exporters are explained in an own [section](#implementation-differences-between-cloud-logging-and-otlp-exporter). -The benefit of the `cloud-logging` exporter is, that it can be combined with a different configuration of the `otlp` exporter. - -For the instrumentation to send observability data to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) or [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring), the application needs to be bound to a corresponding service instances. +That means, without any configuration the agent with the extension will forward metrics and traces +to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging). +The difference between `cloud-logging` and `otlp` exporters are explained in an +own [section](#implementation-differences-between-cloud-logging-and-otlp-exporter). +The benefit of the `cloud-logging` exporter is, that it can be combined with a different configuration of the `otlp` +exporter. + +For the instrumentation to send observability data +to [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) +or [Dynatrace](https://docs.dynatrace.com/docs/setup-and-configuration/setup-on-container-platforms/cloud-foundry/deploy-oneagent-on-sap-cloud-platform-for-application-only-monitoring), +the application needs to be bound to a corresponding service instances. The service instances can be either managed or [user-provided](#using-user-provided-service-instances). ## Configuration -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. +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. +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 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 | Default Value | Comment | +|--------------------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `sap.cloud-logging.cf.binding.label.value` | `cloud-logging` | The label of the managed service binding to bind to. | +| `sap.cloud-logging.cf.binding.tag.value` | `Cloud Logging` | The tag of any service binding (managed or user-provided) to bind to. | +| `sap.dynatrace.cf.binding.label.value` | `dynatrace` | The label of the managed service binding to bind to. | +| `sap.dynatrace.cf.binding.tag.value` | `dynatrace` | The tag of any service binding (managed or user-provided) to bind to. | +| `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` | `true` | Whether to add CF resource attributes to all events. | +| `sap.cloudfoundry.otel.resources.format` | `SAP` | The semantic convention to follow for the CF resource attributes. Supported values are `SAP` and `OTEL`. | + +> 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 `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_*`. 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). +User-provided bindings will take precedence over managed bindings of the configured label ("cloud-logging" or " +dynatrace" by default). All matching bindings are filtered for the configured tag ("Cloud Logging" od "dynatrace" by default). The first Cloud Logging binding will be taken for configuration for the standard OpenTelemetry (otlp) exporter. -Preferring user-provided services over managed service instances allows better control of the binding properties, e.g. syslog drains. +Preferring user-provided services over managed service instances allows better control of the binding properties, e.g. +syslog drains. ### Recommended Agent Configuration @@ -140,7 +171,10 @@ java -javaagent:/path/to/opentelemetry-javaagent-.jar \ -Dotel.resource.disabled-keys=process.command_line,process.command_args,process.executable.path ``` -The [OpenTelemetry Java Instrumentation project](https://github.com/open-telemetry/opentelemetry-java-instrumentation) provides detailed documentation on the configuration properties for [Logback](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/javaagent) and [Log4j](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/log4j/log4j-appender-2.17/javaagent). +The [OpenTelemetry Java Instrumentation project](https://github.com/open-telemetry/opentelemetry-java-instrumentation) +provides detailed documentation on the configuration properties +for [Logback](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/javaagent) +and [Log4j](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/log4j/log4j-appender-2.17/javaagent). ### Filtering Metrics @@ -148,8 +182,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,24 +191,62 @@ 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` | +| `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` | +| `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` | +| `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 -The extension provides support not only for managed service instance of [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) but also for user-provided service instances. +The extension provides support not only for managed service instance +of [SAP Cloud Logging](https://discovery-center.cloud.sap/serviceCatalog/cloud-logging) but also for user-provided +service instances. This helps to fine-tune the configuration, e.g. leave out or reconfigure the syslog drain. Furthermore, this helps on sharing service instances across CF orgs or landscapes. -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). +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: +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: ```bash cf service-key cls test \ @@ -197,35 +269,38 @@ 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 [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 +`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 #... ``` ## Implementation Differences between Cloud-Logging and OTLP Exporter -The `cloud-logging` exporter provided by this extension is a facade for the `OtlpGrpcExporter` provided by the OpenTelemetry Java Agent, just like the `otlp` exporter. +The `cloud-logging` exporter provided by this extension is a facade for the `OtlpGrpcExporter` provided by the +OpenTelemetry Java Agent, just like the `otlp` exporter. 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 `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. +* The `cloud-logging` exporter needs to be configured explicitly as does the `otlp` exporter for logs. 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