diff --git a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts index 6326362e7817..68afeb985282 100644 --- a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts +++ b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts @@ -249,33 +249,3 @@ if (otelProps.testLatestDeps) { } } } - -tasks { - val generateInstrumentationVersionFile by registering { - val name = computeInstrumentationName() - val version = project.version as String - inputs.property("instrumentation.name", name) - inputs.property("instrumentation.version", version) - - val propertiesDir = layout.buildDirectory.dir("generated/instrumentationVersion/META-INF/io/opentelemetry/instrumentation/") - outputs.dir(propertiesDir) - - doLast { - File(propertiesDir.get().asFile, "$name.properties").writeText("version=$version") - } - } -} - -fun computeInstrumentationName(): String { - val name = when (projectDir.name) { - "javaagent", "library", "library-autoconfigure" -> projectDir.parentFile.name - else -> project.name - } - return "io.opentelemetry.$name" -} - -sourceSets { - main { - output.dir("build/generated/instrumentationVersion", "builtBy" to "generateInstrumentationVersionFile") - } -} diff --git a/conventions/src/main/kotlin/otel.instrumentation-version.gradle.kts b/conventions/src/main/kotlin/otel.instrumentation-version.gradle.kts new file mode 100644 index 000000000000..4b79fc4593d7 --- /dev/null +++ b/conventions/src/main/kotlin/otel.instrumentation-version.gradle.kts @@ -0,0 +1,93 @@ +plugins { + `java-library` +} + +dependencies { + compileOnly("javax.annotation:javax.annotation-api:1.3.2") +} + +tasks { + val generateInstrumentationVersionFile by registering { + val name = computeInstrumentationName() + val version = project.version as String + inputs.property("instrumentation.name", name) + inputs.property("instrumentation.version", version) + + val propertiesDir = layout.buildDirectory.dir("generated/instrumentationVersion/META-INF/io/opentelemetry/instrumentation/") + outputs.dir(propertiesDir) + + doLast { + val outputDir = propertiesDir.get().asFile + outputDir.mkdirs() + File(outputDir, "$name.properties").writeText("version=$version") + } + } + + val generateInstrumentationVersionClass by registering { + val name = computeInstrumentationName() + val version = project.version as String + inputs.property("instrumentation.name", name) + inputs.property("instrumentation.version", version) + + // The same logic is duplicated in EmbeddedInstrumentationProperties + // Strip trailing version suffix and remove dashes. + // If the module name contains a non-trailing version number e.g. jaxrs-3.0-jersey-3.0 replace + // the dot with underscore. This is needed to turn the module name into valid package name, java + // package name segments cannot start with a number. + val moduleName = name.replace("(-[0-9.]*)$".toRegex(), "") + .replace("-", "") + .replace("([0-9]+)\\.([0-9]+)".toRegex(), "$1_$2") + // Extract trailing version number and replace dots with underscores so it could be used as a + // package name segment. + val baseVersion = name.replace(".*?([0-9.]*)$".toRegex(), "$1") + .replace(".", "_") + val packageName = moduleName + if (baseVersion.isNotEmpty()) ".v$baseVersion" else "" + + val classDir = layout.buildDirectory.dir("generated/instrumentationVersionClass/${packageName.replace('.', '/')}/internal") + outputs.dir(classDir) + + doLast { + val outputDir = classDir.get().asFile + outputDir.mkdirs() + File(outputDir, "InstrumentationVersion.java").writeText(""" + package $packageName.internal; + + import javax.annotation.Generated; + + /** Autogenerated class do not edit. */ + @Generated("otel.instrumentation-version") + public final class InstrumentationVersion { + public static final String VERSION = "$version"; + + private InstrumentationVersion() {} + } + """.trimIndent()) + } + } + + project.tasks.matching { it.name == "compileJava" || it.name == "sourcesJar" + || it.name == "compileKotlin" || it.name == "kotlinSourcesJar" }.configureEach { + dependsOn(generateInstrumentationVersionFile, generateInstrumentationVersionClass) + } + + named("checkstyleMain") { + exclude("**/InstrumentationVersion.java") + } +} + +fun computeInstrumentationName(): String { + val name = when (projectDir.name) { + "javaagent", "library", "library-autoconfigure" -> projectDir.parentFile.name + else -> project.name + } + return "io.opentelemetry.$name" +} + +sourceSets { + main { + output.dir("build/generated/instrumentationVersion", "builtBy" to "generateInstrumentationVersionFile") + java { + srcDir(layout.buildDirectory.dir("generated/instrumentationVersionClass")) + } + } +} diff --git a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts index 2fe5adb596bf..20e248b55116 100644 --- a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts @@ -1,8 +1,9 @@ plugins { + id("io.opentelemetry.instrumentation.javaagent-instrumentation") + id("otel.javaagent-testing") id("otel.publish-conventions") - - id("io.opentelemetry.instrumentation.javaagent-instrumentation") + id("otel.instrumentation-version") } extra["mavenGroupId"] = "io.opentelemetry.javaagent.instrumentation" diff --git a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts index 92e1cbfc5c96..68a5eb969b05 100644 --- a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts @@ -4,6 +4,7 @@ plugins { id("otel.jacoco-conventions") id("otel.java-conventions") id("otel.publish-conventions") + id("otel.instrumentation-version") } extra["mavenGroupId"] = "io.opentelemetry.instrumentation" diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index f22e94e0a33e..6149a3fcc0e1 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-exporter-common") + // used for testing embedded instrumentation properties + testImplementation(project(":instrumentation:okhttp:okhttp-3.0:library")) jmhImplementation(project(":instrumentation-api-incubator")) } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index 6b6a2914bf83..305ae1a33db0 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -89,8 +89,6 @@ public final class InstrumenterBuilder { this.openTelemetry = openTelemetry; this.instrumentationName = instrumentationName; this.spanNameExtractor = spanNameExtractor; - this.instrumentationVersion = - EmbeddedInstrumentationProperties.findVersion(instrumentationName); } /** @@ -302,6 +300,10 @@ private Instrumenter buildInstrumenter( } Tracer buildTracer() { + if (instrumentationVersion == null) { + instrumentationVersion = EmbeddedInstrumentationProperties.findVersion(instrumentationName); + } + TracerBuilder tracerBuilder = openTelemetry.getTracerProvider().tracerBuilder(instrumentationName); if (instrumentationVersion != null) { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationProperties.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationProperties.java index 70002f426220..fc9612cd664f 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationProperties.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationProperties.java @@ -13,6 +13,7 @@ import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -54,6 +55,46 @@ public static String findVersion(String instrumentationName) { @Nullable private static String loadVersion(String instrumentationName) { + String version = loadVersionFromClass(instrumentationName); + if (version == null) { + version = loadVersionFromProperties(instrumentationName); + } + return version; + } + + private static final Pattern STRIP_VERSION_SUFFIX = Pattern.compile("(-[0-9.]*)$"); + private static final Pattern NORMALIZE_VERSION = Pattern.compile("([0-9]+)\\.([0-9]+)"); + private static final Pattern EXTRACT_VERSION = Pattern.compile(".*?([0-9.]*)$"); + + // visible for testing + @Nullable + static String loadVersionFromClass(String instrumentationName) { + // The same logic is duplicated in otel.instrumentation-version + // Strip trailing version suffix and remove dashes + String moduleName = + STRIP_VERSION_SUFFIX.matcher(instrumentationName).replaceAll("").replace("-", ""); + // If the module name contains a non-trailing version number e.g. jaxrs-3.0-jersey-3.0 replace + // the dot with underscore. This is needed to turn the module name into valid package name, java + // package name segments cannot start with a number. + moduleName = NORMALIZE_VERSION.matcher(moduleName).replaceAll("$1_$2"); + // Extract trailing version number and replace dots with underscores so it could be used as a + // package name segment. + String baseVersion = + EXTRACT_VERSION.matcher(instrumentationName).replaceAll("$1").replace(".", "_"); + String packageName = moduleName + (baseVersion.isEmpty() ? "" : ".v" + baseVersion); + + try { + Class clazz = + Class.forName(packageName + ".internal.InstrumentationVersion", false, loader); + return clazz.getField("VERSION").get(null).toString(); + } catch (ReflectiveOperationException e) { + return null; + } + } + + // visible for testing + @Nullable + static String loadVersionFromProperties(String instrumentationName) { String path = "META-INF/io/opentelemetry/instrumentation/" + instrumentationName + ".properties"; try (InputStream in = loader.getResourceAsStream(path)) { diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationPropertiesTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationPropertiesTest.java new file mode 100644 index 000000000000..67d075d421cf --- /dev/null +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationPropertiesTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class EmbeddedInstrumentationPropertiesTest { + + @Test + void loadVersionFromClass() { + assertThat( + EmbeddedInstrumentationProperties.loadVersionFromClass("io.opentelemetry.okhttp-3.0")) + .isNotEmpty(); + assertThat(EmbeddedInstrumentationProperties.loadVersionFromClass("does-not-exist")).isNull(); + } + + @Test + void loadVersionFromProperties() { + assertThat( + EmbeddedInstrumentationProperties.loadVersionFromProperties( + "io.opentelemetry.okhttp-3.0")) + .isNotEmpty(); + assertThat(EmbeddedInstrumentationProperties.loadVersionFromProperties("does-not-exist")) + .isNull(); + } +} diff --git a/instrumentation/jdbc/library/build.gradle.kts b/instrumentation/jdbc/library/build.gradle.kts index 4531a2f11823..3fa56c603271 100644 --- a/instrumentation/jdbc/library/build.gradle.kts +++ b/instrumentation/jdbc/library/build.gradle.kts @@ -53,6 +53,7 @@ tasks { into("build/extracted/shadow-javaagent") exclude("META-INF/**") exclude("io/opentelemetry/javaagent/bootstrap/**") + exclude("**/InstrumentationVersion.class") } // this will be included in bootstrap module diff --git a/instrumentation/r2dbc-1.0/library-instrumentation-shaded/build.gradle.kts b/instrumentation/r2dbc-1.0/library-instrumentation-shaded/build.gradle.kts index c750072d17ab..c7a08949bfdd 100644 --- a/instrumentation/r2dbc-1.0/library-instrumentation-shaded/build.gradle.kts +++ b/instrumentation/r2dbc-1.0/library-instrumentation-shaded/build.gradle.kts @@ -34,6 +34,7 @@ tasks { dependsOn(shadowJar) from(zipTree(shadowJar.get().archiveFile)) exclude("META-INF/**") + exclude("**/InstrumentationVersion.class") into("build/extracted/shadow") } diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index dd23b26027d1..9d3131f166b1 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -186,6 +186,9 @@ tasks { filesMatching("META-INF/io/opentelemetry/instrumentation/**") { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } + filesMatching("**/InstrumentationVersion.class") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } exclude("META-INF/LICENSE") exclude("META-INF/NOTICE") exclude("META-INF/maven/**")