diff --git a/instrumentation/cloudfoundry/cloudfoundry-resources/library/build.gradle.kts b/instrumentation/cloudfoundry/cloudfoundry-resources/library/build.gradle.kts new file mode 100644 index 000000000000..6417f7c627c7 --- /dev/null +++ b/instrumentation/cloudfoundry/cloudfoundry-resources/library/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("otel.sdk-extension") +} + +dependencies { + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + + annotationProcessor("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") + testCompileOnly("com.google.auto.service:auto-service-annotations") + + implementation("org.snakeyaml:snakeyaml-engine") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") +} diff --git a/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResource.java b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResource.java new file mode 100644 index 000000000000..de2f63513f44 --- /dev/null +++ b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResource.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.SchemaUrls; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import org.snakeyaml.engine.v2.api.Load; +import org.snakeyaml.engine.v2.api.LoadSettings; + +public final class CloudFoundryResource { + + private static final String ENV_VCAP_APPLICATION = "VCAP_APPLICATION"; + + // copied from CloudfoundryIncubatingAttributes + private static final AttributeKey CLOUDFOUNDRY_APP_ID = + AttributeKey.stringKey("cloudfoundry.app.id"); + private static final AttributeKey CLOUDFOUNDRY_APP_INSTANCE_ID = + AttributeKey.stringKey("cloudfoundry.app.instance.id"); + private static final AttributeKey CLOUDFOUNDRY_APP_NAME = + AttributeKey.stringKey("cloudfoundry.app.name"); + private static final AttributeKey CLOUDFOUNDRY_ORG_ID = + AttributeKey.stringKey("cloudfoundry.org.id"); + private static final AttributeKey CLOUDFOUNDRY_ORG_NAME = + AttributeKey.stringKey("cloudfoundry.org.name"); + private static final AttributeKey CLOUDFOUNDRY_PROCESS_ID = + AttributeKey.stringKey("cloudfoundry.process.id"); + private static final AttributeKey CLOUDFOUNDRY_PROCESS_TYPE = + AttributeKey.stringKey("cloudfoundry.process.type"); + private static final AttributeKey CLOUDFOUNDRY_SPACE_ID = + AttributeKey.stringKey("cloudfoundry.space.id"); + private static final AttributeKey CLOUDFOUNDRY_SPACE_NAME = + AttributeKey.stringKey("cloudfoundry.space.name"); + private static final Resource INSTANCE = buildResource(System::getenv); + + private CloudFoundryResource() {} + + public static Resource get() { + return INSTANCE; + } + + static Resource buildResource(Function getenv) { + String vcapAppRaw = getenv.apply(ENV_VCAP_APPLICATION); + // If there is no VCAP_APPLICATION in the environment, we are likely not running in CloudFoundry + if (vcapAppRaw == null || vcapAppRaw.isEmpty()) { + return Resource.empty(); + } + CloudFoundryAttributesBuilder attributes = new CloudFoundryAttributesBuilder(vcapAppRaw); + attributes + .putString(CLOUDFOUNDRY_APP_ID, "application_id") + .putString(CLOUDFOUNDRY_APP_NAME, "application_name") + .putNumberAsString(CLOUDFOUNDRY_APP_INSTANCE_ID, "instance_index") + .putString(CLOUDFOUNDRY_ORG_ID, "organization_id") + .putString(CLOUDFOUNDRY_ORG_NAME, "organization_name") + .putString(CLOUDFOUNDRY_PROCESS_ID, "process_id") + .putString(CLOUDFOUNDRY_PROCESS_TYPE, "process_type") + .putString(CLOUDFOUNDRY_SPACE_ID, "space_id") + .putString(CLOUDFOUNDRY_SPACE_NAME, "space_name"); + return Resource.create(attributes.build(), SchemaUrls.V1_24_0); + } + + private static class CloudFoundryAttributesBuilder { + private final AttributesBuilder builder = Attributes.builder(); + private final Map parsedData; + + @SuppressWarnings("unchecked") + private CloudFoundryAttributesBuilder(String rawData) { + Load load = new Load(LoadSettings.builder().build()); + try { + this.parsedData = (Map) load.loadFromString(rawData); + } catch (ClassCastException ex) { + this.parsedData = Collections.emptyMap(); + } + } + + @CanIgnoreReturnValue + private CloudFoundryAttributesBuilder putString(AttributeKey key, String name) { + Object value = parsedData.get(name); + if (value instanceof String) { + builder.put(key, (String) value); + } + return this; + } + + @CanIgnoreReturnValue + private CloudFoundryAttributesBuilder putNumberAsString(AttributeKey key, String name) { + Object value = parsedData.get(name); + if (value instanceof Number) { + builder.put(key, value.toString()); + } + return this; + } + + private Attributes build() { + return builder.build(); + } + } +} diff --git a/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceProvider.java b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceProvider.java new file mode 100644 index 000000000000..70475259f8da --- /dev/null +++ b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/main/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceProvider.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources; + +import com.google.auto.service.AutoService; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.resources.Resource; + +@AutoService(ResourceProvider.class) +public class CloudFoundryResourceProvider implements ResourceProvider { + + @Override + public Resource createResource(ConfigProperties configProperties) { + return CloudFoundryResource.get(); + } +} diff --git a/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/test/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceTest.java b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/test/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceTest.java new file mode 100644 index 000000000000..25c1a96c76a2 --- /dev/null +++ b/instrumentation/cloudfoundry/cloudfoundry-resources/library/src/test/java/io/opentelemetry/javaagent/instrumentation/cloudfoundry/resources/CloudFoundryResourceTest.java @@ -0,0 +1,72 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.SchemaUrls; +import io.opentelemetry.semconv.incubating.CloudfoundryIncubatingAttributes; +import java.util.Collections; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class CloudFoundryResourceTest { + + private static final String FULL_VCAP_APPLICATION = + "{" + + "\"application_id\":\"0193a038-e615-7e5e-92ca-f4bcd7ba0a25\"," + + "\"application_name\":\"cf-app-name\"," + + "\"instance_index\":1," + + "\"organization_id\":\"0193a375-8d8e-7e0c-a832-01ce9ded40dc\"," + + "\"organization_name\":\"cf-org-name\"," + + "\"process_id\":\"0193a4e3-8fd3-71b9-9fe3-5640c53bf1e2\"," + + "\"process_type\":\"web\"," + + "\"space_id\":\"0193a7e7-da17-7ea4-8940-b1e07b401b16\"," + + "\"space_name\":\"cf-space-name\"}"; + + @Test + void noVcapApplication() { + Map env = Collections.emptyMap(); + Resource resource = CloudFoundryResource.buildResource(env::get); + assertThat(resource).isEqualTo(Resource.empty()); + } + + @Test + void emptyVcapApplication() { + Map env = ImmutableMap.of("VCAP_APPLICATION", ""); + Resource resource = CloudFoundryResource.buildResource(env::get); + assertThat(resource).isEqualTo(Resource.empty()); + } + + @Test + void fullVcapApplication() { + Map env = ImmutableMap.of("VCAP_APPLICATION", FULL_VCAP_APPLICATION); + + Resource resource = CloudFoundryResource.buildResource(env::get); + + assertThat(resource.getSchemaUrl()).isEqualTo(SchemaUrls.V1_24_0); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_ID)) + .isEqualTo("0193a038-e615-7e5e-92ca-f4bcd7ba0a25"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_INSTANCE_ID)) + .isEqualTo("1"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_NAME)) + .isEqualTo("cf-app-name"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_ORG_ID)) + .isEqualTo("0193a375-8d8e-7e0c-a832-01ce9ded40dc"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_ORG_NAME)) + .isEqualTo("cf-org-name"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_PROCESS_ID)) + .isEqualTo("0193a4e3-8fd3-71b9-9fe3-5640c53bf1e2"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_PROCESS_TYPE)) + .isEqualTo("web"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_SPACE_ID)) + .isEqualTo("0193a7e7-da17-7ea4-8940-b1e07b401b16"); + assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_SPACE_NAME)) + .isEqualTo("cf-space-name"); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 67ff589c2439..4956792e67c7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -231,6 +231,7 @@ include(":instrumentation:cassandra:cassandra-4.4:testing") include(":instrumentation:cassandra:cassandra-4-common:testing") include(":instrumentation:cdi-testing") include(":instrumentation:clickhouse-client-0.5:javaagent") +include(":instrumentation:cloudfoundry:cloudfoundry-resources:library") include(":instrumentation:couchbase:couchbase-2.0:javaagent") include(":instrumentation:couchbase:couchbase-2.6:javaagent") include(":instrumentation:couchbase:couchbase-2-common:javaagent")