From 49726340f4c70ccbddf7535b3228010c982bd4b7 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Fri, 25 Mar 2022 11:40:26 +0100 Subject: [PATCH] Library instrumentation should read its version from a file --- ...ntelemetry.instrumentation.base.gradle.kts | 29 +++++++ ...rumentation.javaagent-shadowing.gradle.kts | 6 ++ .../gradle/OtelInstrumentationExtension.kt | 10 +++ .../api/InstrumentationVersion.java | 6 ++ .../api/instrumenter/Instrumenter.java | 8 +- .../EmbeddedInstrumentationVersions.java | 75 +++++++++++++++++++ .../api/instrumenter/InstrumenterTest.java | 25 ------- .../ResourceInjectionInstrumentation.java | 28 ++++--- .../jdbc/OpenTelemetryDriver.java | 11 ++- .../jdbc/internal/JdbcSingletons.java | 2 +- .../okhttp-2.2/javaagent/build.gradle.kts | 4 + .../okhttp/v3_0/OkHttp3Test.groovy | 20 +++++ .../okhttp-3.0/library/build.gradle.kts | 6 ++ .../jpms/ExposeRmiModuleInstrumentation.java | 6 +- .../javaagent/bootstrap/HelperResources.java | 63 ++++++++++++---- .../tooling/AutoVersionResourceProvider.java | 9 ++- .../javaagent/tooling/VersionLogger.java | 6 +- .../InstrumentationLoader.java | 18 +++++ .../javaagent/tooling/HelperInjector.java | 24 ++++-- 19 files changed, 283 insertions(+), 73 deletions(-) create mode 100644 conventions/src/main/kotlin/io/opentelemetry/instrumentation/gradle/OtelInstrumentationExtension.kt create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationVersions.java 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 d29aa4acd96c..64980b71921a 100644 --- a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts +++ b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.base.gradle.kts @@ -1,3 +1,5 @@ +import io.opentelemetry.instrumentation.gradle.OtelInstrumentationExtension + /** Common setup for manual instrumentation of libraries and javaagent instrumentation. */ plugins { @@ -119,3 +121,30 @@ if (testLatestDeps) { } } } + +val otelInstrumentation = extensions.create("otelInstrumentation") + +tasks { + val generateInstrumentationVersionFile by registering { + val propertiesDir = File(project.buildDir, "generated/instrumentationVersion") + outputs.dir(propertiesDir) + + doLast { + if (!otelInstrumentation.name.isPresent) { + return@doLast + } + val name = otelInstrumentation.name.get() + val version = otelInstrumentation.version.getOrElse(project.version as String) + val metaInfDir = File(propertiesDir, "META-INF/io/opentelemetry/") + metaInfDir.mkdirs() + File(metaInfDir, "instrumentation.version") + .writeText("$name=$version") + } + } +} + +sourceSets { + main { + output.dir("build/generated/instrumentationVersion", "builtBy" to "generateInstrumentationVersionFile") + } +} diff --git a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts index 29561e17f235..6a51645c7067 100644 --- a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts +++ b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer plugins { id("com.github.johnrengelman.shadow") @@ -12,6 +13,11 @@ tasks.withType().configureEach { // service loader...) mergeServiceFiles("software/amazon/awssdk/global/handlers") + // merge the instrumentation.version files + transform(PropertiesFileTransformer::class.java) { + paths = listOf("META-INF/io/opentelemetry/instrumentation.version") + } + exclude("**/module-info.class") // Prevents conflict with other SLF4J instances. Important for premain. diff --git a/conventions/src/main/kotlin/io/opentelemetry/instrumentation/gradle/OtelInstrumentationExtension.kt b/conventions/src/main/kotlin/io/opentelemetry/instrumentation/gradle/OtelInstrumentationExtension.kt new file mode 100644 index 000000000000..cda62c44b765 --- /dev/null +++ b/conventions/src/main/kotlin/io/opentelemetry/instrumentation/gradle/OtelInstrumentationExtension.kt @@ -0,0 +1,10 @@ +package io.opentelemetry.instrumentation.gradle + +import org.gradle.api.provider.Property + +abstract class OtelInstrumentationExtension { + + abstract val name : Property + + abstract val version : Property +} \ No newline at end of file diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/InstrumentationVersion.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/InstrumentationVersion.java index 4b539b335fe8..245f3a6f056b 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/InstrumentationVersion.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/InstrumentationVersion.java @@ -5,6 +5,12 @@ package io.opentelemetry.instrumentation.api; +/** + * This class will be removed. + * + * @deprecated This class will be removed. + */ +@Deprecated public final class InstrumentationVersion { public static final String VERSION = InstrumentationVersion.class.getPackage().getImplementationVersion(); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index 5fc890baabf5..d00b76376e48 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -13,7 +13,7 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; +import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationVersions; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; import java.time.Instant; import java.util.ArrayList; @@ -59,7 +59,10 @@ public static InstrumenterBuilder builder String instrumentationName, SpanNameExtractor spanNameExtractor) { return new InstrumenterBuilder<>( - openTelemetry, instrumentationName, InstrumentationVersion.VERSION, spanNameExtractor); + openTelemetry, + instrumentationName, + EmbeddedInstrumentationVersions.findVersion(instrumentationName), + spanNameExtractor); } /** @@ -79,6 +82,7 @@ public static InstrumenterBuilder builder * different library versions it's easy to find out which instrumentations produced the telemetry * data. */ + // TODO: add a setInstrumentationVersion method to the builder instead public static InstrumenterBuilder builder( OpenTelemetry openTelemetry, String instrumentationName, diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationVersions.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationVersions.java new file mode 100644 index 000000000000..24b7f2b23e3f --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/EmbeddedInstrumentationVersions.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.list; +import static java.util.Collections.unmodifiableMap; +import static java.util.logging.Level.FINE; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class EmbeddedInstrumentationVersions { + + private static final Map versions = loadVersions(); + + private static Map loadVersions() { + Map versions = new HashMap<>(); + + // preload this once + try { + List urls = + list( + new ResourceLoaderProxy() + .getResources("META-INF/io/opentelemetry/instrumentation.version")); + Properties parsed = new Properties(); + + for (URL url : urls) { + try (InputStream in = url.openStream()) { + parsed.clear(); + parsed.load(in); + + for (String name : parsed.stringPropertyNames()) { + versions.put(name, parsed.getProperty(name)); + } + } + } + + } catch (IOException e) { + Logger.getLogger(EmbeddedInstrumentationVersions.class.getName()) + .log(FINE, "Failed to load embedded instrumentation version file(s)", e); + return emptyMap(); + } + + return unmodifiableMap(versions); + } + + @Nullable + public static String findVersion(String instrumentationName) { + return versions.get(instrumentationName); + } + + // works as a proxy for the bootstrap classloader (if agent) + private static final class ResourceLoaderProxy extends ClassLoader { + ResourceLoaderProxy() { + super(ResourceLoaderProxy.class.getClassLoader()); + } + } + + private EmbeddedInstrumentationVersions() {} +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 3dddb50ad2e7..84215a61e9e3 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -23,7 +23,6 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; @@ -735,30 +734,6 @@ void instrumentationTypeDetected_generic() { assertThat(SpanKey.PRODUCER.fromContextOrNull(context)).isNull(); } - @Test - void instrumentationVersion_default() { - InstrumenterBuilder, Map> builder = - Instrumenter.builder(otelTesting.getOpenTelemetry(), "test", name -> "span"); - - Instrumenter, Map> instrumenter = builder.newInstrumenter(); - - Context context = instrumenter.start(Context.root(), Collections.emptyMap()); - assertThat(Span.fromContext(context)).isNotNull(); - - instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null); - - otelTesting - .assertTraces() - .hasTracesSatisfyingExactly( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("span") - .hasInstrumentationLibraryInfo( - InstrumentationLibraryInfo.create( - "test", InstrumentationVersion.VERSION)))); - } - @Test void instrumentationVersion_custom() { InstrumenterBuilder, Map> builder = diff --git a/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ResourceInjectionInstrumentation.java b/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ResourceInjectionInstrumentation.java index 23e24520f3b0..df5e1ab9bbc5 100644 --- a/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ResourceInjectionInstrumentation.java +++ b/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ResourceInjectionInstrumentation.java @@ -58,7 +58,7 @@ public static void onExit( @Advice.Argument(0) String name, @Advice.Return(readOnly = false) URL resource) { - URL helper = HelperResources.load(classLoader, name); + URL helper = HelperResources.loadOne(classLoader, name); if (helper != null) { resource = helper; } @@ -73,26 +73,30 @@ public static void onExit( @Advice.This ClassLoader classLoader, @Advice.Argument(0) String name, @Advice.Return(readOnly = false) Enumeration resources) { - URL helper = HelperResources.load(classLoader, name); - if (helper == null) { + List helpers = HelperResources.loadAll(classLoader, name); + if (helpers.isEmpty()) { return; } if (!resources.hasMoreElements()) { - resources = Collections.enumeration(Collections.singleton(helper)); + resources = Collections.enumeration(helpers); return; } List result = Collections.list(resources); - boolean duplicate = false; - for (URL loadedUrl : result) { - if (helper.sameFile(loadedUrl)) { - duplicate = true; - break; + + // TODO: not sure if this is correct + for (URL helperUrl : helpers) { + boolean duplicate = false; + for (URL loadedUrl : result) { + if (helperUrl.sameFile(loadedUrl)) { + duplicate = true; + break; + } + } + if (!duplicate) { + result.add(helperUrl); } - } - if (!duplicate) { - result.add(helper); } resources = Collections.enumeration(result); diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java index 841261322d8e..f934a848d556 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java @@ -20,7 +20,9 @@ package io.opentelemetry.instrumentation.jdbc; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcSingletons.INSTRUMENTATION_NAME; + +import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationVersions; import io.opentelemetry.instrumentation.jdbc.internal.DbInfo; import io.opentelemetry.instrumentation.jdbc.internal.JdbcConnectionUrlParser; import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; @@ -122,7 +124,12 @@ private static String extractRealUrl(String url) { } private static int[] parseInstrumentationVersion() { - String[] parts = InstrumentationVersion.VERSION.split("\\."); + String version = EmbeddedInstrumentationVersions.findVersion(INSTRUMENTATION_NAME); + if (version == null) { + // return 0.0 as a fallback + return new int[] {0, 0}; + } + String[] parts = version.split("\\."); if (parts.length >= 2) { try { int majorVersion = Integer.parseInt(parts[0]); diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcSingletons.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcSingletons.java index 4776c37f58af..6d919947e4e9 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcSingletons.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcSingletons.java @@ -17,7 +17,7 @@ * any time. */ public final class JdbcSingletons { - private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; + public static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; private static final Instrumenter INSTRUMENTER; diff --git a/instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts b/instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts index 301aaab103fb..ee4085b8c231 100644 --- a/instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts +++ b/instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts @@ -15,6 +15,10 @@ muzzle { } } +otelInstrumentation { + name.set("io.opentelemetry.okhttp-2.2") +} + dependencies { library("com.squareup.okhttp:okhttp:2.2.0") diff --git a/instrumentation/okhttp/okhttp-3.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Test.groovy b/instrumentation/okhttp/okhttp-3.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Test.groovy index 6dba02b6f6d0..4a35e9054b88 100644 --- a/instrumentation/okhttp/okhttp-3.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Test.groovy +++ b/instrumentation/okhttp/okhttp-3.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Test.groovy @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.okhttp.v3_0 +import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.instrumentation.okhttp.v3_0.AbstractOkHttp3Test import io.opentelemetry.instrumentation.test.AgentTestTrait import okhttp3.Call @@ -38,4 +39,23 @@ class OkHttp3Test extends AbstractOkHttp3Test implements AgentTestTrait { newClient.interceptors().size() == 1 } + // just a sample test case that verifies that version is correctly set + def "test version"() { + when: + def request = buildRequest("GET", resolveAddress("/success"), [:]) + sendRequest(request, "GET", resolveAddress("/success"), [:]) + + then: + assertTraces(1) { + trace(0, 2) { + span(0) { + kind(SpanKind.CLIENT) + instrumentationLibraryVersion("42-test") + } + span(1) { + kind(SpanKind.SERVER) + } + } + } + } } diff --git a/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts b/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts index 4d262a4cf477..1f3e24b76593 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts +++ b/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts @@ -4,6 +4,12 @@ plugins { id("otel.animalsniffer-conventions") } +otelInstrumentation { + name.set("io.opentelemetry.okhttp-3.0") + // just for test + version.set("42-test") +} + dependencies { library("com.squareup.okhttp3:okhttp:3.0.0") diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java index 60863acc3e77..e5120f6bc103 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java @@ -8,10 +8,10 @@ import static java.util.logging.Level.FINE; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; @@ -57,9 +57,9 @@ public void transform(TypeTransformer transformer) { transformer.applyTransformer( (builder, typeDescription, classLoader, module) -> { if (module != null && module.isNamed()) { - // using InstrumentationVersion because it's in the unnamed module in the bootstrap + // using Java8BytecodeBridge because it's in the unnamed module in the bootstrap // loader, and that's where the rmi instrumentation helper classes will end up - JavaModule helperModule = JavaModule.ofType(InstrumentationVersion.class); + JavaModule helperModule = JavaModule.ofType(Java8BytecodeBridge.class); // expose sun.rmi.server package to unnamed module ClassInjector.UsingInstrumentation.redefineModule( InstrumentationHolder.getInstrumentation(), diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java index 56dbf8f749f1..8742e09caa6a 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java @@ -5,10 +5,18 @@ package io.opentelemetry.javaagent.bootstrap; +import static java.util.Collections.emptyList; +import static java.util.Collections.list; +import static java.util.Collections.unmodifiableList; + import io.opentelemetry.instrumentation.api.cache.Cache; import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; /** * A holder of resources needed by instrumentation. We store them in the bootstrap classloader so @@ -17,36 +25,59 @@ */ public final class HelperResources { - private static final Cache> RESOURCES = Cache.weak(); - private static final Map ALL_CLASSLOADERS_RESOURCES = new ConcurrentHashMap<>(); + private static final Cache>> RESOURCES = Cache.weak(); + private static final Map> ALL_CLASSLOADERS_RESOURCES = + new ConcurrentHashMap<>(); /** - * Registers the {@code url} to be available to instrumentation at {@code path}, when given {@code - * classLoader} attempts to load that resource. + * Registers the {@code urls} to be available to instrumentation at {@code path}, when given + * {@code classLoader} attempts to load that resource. */ - public static void register(ClassLoader classLoader, String path, URL url) { - RESOURCES.computeIfAbsent(classLoader, unused -> new ConcurrentHashMap<>()).put(path, url); + public static void register(ClassLoader classLoader, String path, Enumeration urls) { + RESOURCES + .computeIfAbsent(classLoader, unused -> new ConcurrentHashMap<>()) + .compute(path, (k, v) -> append(v, urls)); + } + + /** Registers the {@code urls} to be available to instrumentation at {@code path}. */ + public static void registerForAllClassLoaders(String path, Enumeration urls) { + ALL_CLASSLOADERS_RESOURCES.compute(path, (k, v) -> append(v, urls)); } - /** Registers the {@code url} to be available to instrumentation at {@code path}. */ - public static void registerForAllClassLoaders(String path, URL url) { - ALL_CLASSLOADERS_RESOURCES.putIfAbsent(path, url); + private static List append(@Nullable List resources, Enumeration toAdd) { + if (resources == null) { + return unmodifiableList(list(toAdd)); + } + List newResources = new ArrayList<>(resources); + while (toAdd.hasMoreElements()) { + newResources.add(toAdd.nextElement()); + } + return unmodifiableList(newResources); } /** * Returns a {@link URL} that can be used to retrieve the content of the resource at {@code path}, * or {@code null} if no resource could be found at {@code path}. */ - public static URL load(ClassLoader classLoader, String path) { - Map map = RESOURCES.get(classLoader); - URL resource = null; + public static URL loadOne(ClassLoader classLoader, String path) { + List resources = loadAll(classLoader, path); + return resources.isEmpty() ? null : resources.get(0); + } + + /** + * Returns all {@link URL}s that can be used to retrieve the content of the resource at {@code + * path}. + */ + public static List loadAll(ClassLoader classLoader, String path) { + Map> map = RESOURCES.get(classLoader); + List resources = null; if (map != null) { - resource = map.get(path); + resources = map.get(path); } - if (resource == null) { - resource = ALL_CLASSLOADERS_RESOURCES.get(path); + if (resources == null) { + resources = ALL_CLASSLOADERS_RESOURCES.get(path); } - return resource; + return resources == null ? emptyList() : resources; } private HelperResources() {} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AutoVersionResourceProvider.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AutoVersionResourceProvider.java index 327c3f1c2a91..33bbdcca64aa 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AutoVersionResourceProvider.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AutoVersionResourceProvider.java @@ -8,7 +8,6 @@ import com.google.auto.service.AutoService; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.resources.Resource; @@ -19,10 +18,14 @@ public class AutoVersionResourceProvider implements ResourceProvider { private static final AttributeKey TELEMETRY_AUTO_VERSION = AttributeKey.stringKey("telemetry.auto.version"); + @SuppressWarnings("deprecation") // InstrumentationVersion @Override public Resource createResource(ConfigProperties config) { - return InstrumentationVersion.VERSION == null + return io.opentelemetry.instrumentation.api.InstrumentationVersion.VERSION == null ? Resource.empty() - : Resource.create(Attributes.of(TELEMETRY_AUTO_VERSION, InstrumentationVersion.VERSION)); + : Resource.create( + Attributes.of( + TELEMETRY_AUTO_VERSION, + io.opentelemetry.instrumentation.api.InstrumentationVersion.VERSION)); } } diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/VersionLogger.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/VersionLogger.java index e9cc1abfa279..cdeb1467e93c 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/VersionLogger.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/VersionLogger.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.tooling; -import io.opentelemetry.instrumentation.api.InstrumentationVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,8 +12,11 @@ public final class VersionLogger { private static final Logger logger = LoggerFactory.getLogger(VersionLogger.class); + @SuppressWarnings("deprecation") // InstrumentationVersion public static void logAllVersions() { - logger.info("opentelemetry-javaagent - version: {}", InstrumentationVersion.VERSION); + logger.info( + "opentelemetry-javaagent - version: {}", + io.opentelemetry.instrumentation.api.InstrumentationVersion.VERSION); if (logger.isDebugEnabled()) { logger.debug( "Running on Java {}. JVM {} - {} - {}", diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationLoader.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationLoader.java index 318bc6fe0b22..72bd9b17614e 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationLoader.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationLoader.java @@ -8,9 +8,14 @@ import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.loadOrdered; import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.bootstrap.HelperResources; import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.tooling.AgentExtension; +import io.opentelemetry.javaagent.tooling.Utils; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; import net.bytebuddy.agent.builder.AgentBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +29,19 @@ public class InstrumentationLoader implements AgentExtension { @Override public AgentBuilder extend(AgentBuilder agentBuilder) { + try { + String instrumentationVersionPath = "META-INF/io/opentelemetry/instrumentation.version"; + Enumeration resources = + Utils.getExtensionsClassLoader().getResources(instrumentationVersionPath); + if (resources.hasMoreElements()) { + HelperResources.registerForAllClassLoaders(instrumentationVersionPath, resources); + } else { + logger.debug("No instrumentation.version files were found"); + } + } catch (IOException e) { + logger.error("Unexpected exception when registering instrumentation.version helper file", e); + } + int numberOfLoadedModules = 0; for (InstrumentationModule instrumentationModule : loadOrdered(InstrumentationModule.class)) { logger.debug( diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index e9c1154b08ec..d6f76beae67d 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -17,6 +17,7 @@ import java.security.SecureClassLoader; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -173,25 +174,34 @@ private void injectHelperResources(ClassLoader classLoader) { classLoader, cl -> { for (HelperResource helperResource : helperResources) { - URL resource = helpersSource.getResource(helperResource.getAgentPath()); - if (resource == null) { + Enumeration resources; + try { + resources = helpersSource.getResources(helperResource.getAgentPath()); + } catch (IOException e) { + logger.error( + "Unexpected exception occurred when loading resources {}; skipping", + helperResource.getAgentPath(), + e); + continue; + } + if (!resources.hasMoreElements()) { logger.debug( - "Helper resource {} requested but not found.", helperResource.getAgentPath()); + "Helper resources {} requested but not found.", helperResource.getAgentPath()); continue; } if (helperResource.allClassLoaders()) { logger.debug( - "Injecting resource onto all classloaders: {}", + "Injecting resources onto all classloaders: {}", helperResource.getApplicationPath()); HelperResources.registerForAllClassLoaders( - helperResource.getApplicationPath(), resource); + helperResource.getApplicationPath(), resources); } else { logger.debug( - "Injecting resource onto classloader {} -> {}", + "Injecting resources onto classloader {} -> {}", classLoader, helperResource.getApplicationPath()); - HelperResources.register(classLoader, helperResource.getApplicationPath(), resource); + HelperResources.register(classLoader, helperResource.getApplicationPath(), resources); } }