When loading exporter factories via SPI from an external jar, look only inside that jar, and not any parent classloader. (#569)
This commit is contained in:
parent
918d3913e3
commit
d08a1a9c5c
|
@ -68,7 +68,9 @@ public class ExporterClassLoader extends URLClassLoader {
|
||||||
// A small hack to prevent other exporters from being loaded by this classloader if they
|
// A small hack to prevent other exporters from being loaded by this classloader if they
|
||||||
// should happen to appear on the classpath.
|
// should happen to appear on the classpath.
|
||||||
if (name.equals(
|
if (name.equals(
|
||||||
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory")) {
|
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory")
|
||||||
|
|| name.equals(
|
||||||
|
"META-INF/services/io.opentelemetry.sdk.contrib.auto.config.MetricExporterFactory")) {
|
||||||
return findResources(name);
|
return findResources(name);
|
||||||
}
|
}
|
||||||
return super.getResources(name);
|
return super.getResources(name);
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.opentelemetry.auto.tooling
|
||||||
|
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.contrib.auto.config.Config
|
||||||
|
import io.opentelemetry.sdk.contrib.auto.config.MetricExporterFactory
|
||||||
|
import io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory
|
||||||
|
import io.opentelemetry.sdk.metrics.export.MetricExporter
|
||||||
|
import io.opentelemetry.sdk.trace.export.SpanExporter
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.jar.JarEntry
|
||||||
|
import java.util.jar.JarOutputStream
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class ExporterClassLoaderTest extends Specification {
|
||||||
|
|
||||||
|
// Verifies https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/542
|
||||||
|
def "does not look in parent classloader for metric exporters"() {
|
||||||
|
setup:
|
||||||
|
def parentClassloader = new URLClassLoader([createJarWithClasses(MetricExporterFactoryParent)] as URL[])
|
||||||
|
def childClassloader = new ExporterClassLoader(createJarWithClasses(MetricExporterFactoryChild), parentClassloader)
|
||||||
|
|
||||||
|
when:
|
||||||
|
ServiceLoader<MetricExporterFactory> serviceLoader = ServiceLoader.load(MetricExporterFactory, childClassloader)
|
||||||
|
|
||||||
|
then:
|
||||||
|
serviceLoader.size() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def "does not look in parent classloader for span exporters"() {
|
||||||
|
setup:
|
||||||
|
def parentClassloader = new URLClassLoader([createJarWithClasses(SpanExporterFactoryParent)] as URL[])
|
||||||
|
def childClassloader = new ExporterClassLoader(createJarWithClasses(SpanExporterFactoryChild), parentClassloader)
|
||||||
|
|
||||||
|
when:
|
||||||
|
ServiceLoader<SpanExporterFactory> serviceLoader = ServiceLoader.load(SpanExporterFactory, childClassloader)
|
||||||
|
|
||||||
|
then:
|
||||||
|
serviceLoader.size() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MetricExporterFactoryParent implements MetricExporterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
MetricExporter fromConfig(Config config) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MetricExporterFactoryChild implements MetricExporterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
MetricExporter fromConfig(Config config) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SpanExporterFactoryParent implements SpanExporterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SpanExporter fromConfig(Config config) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SpanExporterFactoryChild implements SpanExporterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SpanExporter fromConfig(Config config) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL createJarWithClasses(final Class<?>... classes)
|
||||||
|
throws IOException {
|
||||||
|
final File tmpJar = File.createTempFile(UUID.randomUUID().toString() + "-", ".jar")
|
||||||
|
tmpJar.deleteOnExit()
|
||||||
|
|
||||||
|
final JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpJar))
|
||||||
|
for (final Class<?> clazz : classes) {
|
||||||
|
addToJar(clazz, clazz.getInterfaces()[0], target)
|
||||||
|
}
|
||||||
|
target.close()
|
||||||
|
|
||||||
|
return tmpJar.toURI().toURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is mostly copy-pasted from IntegrationTestUtils, but we need to save service files as well
|
||||||
|
private static void addToJar(final Class<?> clazz, final Class<?> serviceInterface, final JarOutputStream jarOutputStream)
|
||||||
|
throws IOException {
|
||||||
|
String resourceName = getResourceName(clazz.getName())
|
||||||
|
|
||||||
|
ClassLoader loader = clazz.getClassLoader()
|
||||||
|
if (null == loader) {
|
||||||
|
// bootstrap resources can be fetched through the system loader
|
||||||
|
loader = ClassLoader.getSystemClassLoader()
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream inputStream = null
|
||||||
|
try {
|
||||||
|
final JarEntry entry = new JarEntry(resourceName)
|
||||||
|
jarOutputStream.putNextEntry(entry)
|
||||||
|
inputStream = loader.getResourceAsStream(resourceName)
|
||||||
|
|
||||||
|
final byte[] buffer = new byte[1024]
|
||||||
|
while (true) {
|
||||||
|
final int count = inputStream.read(buffer)
|
||||||
|
if (count == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
jarOutputStream.write(buffer, 0, count)
|
||||||
|
}
|
||||||
|
jarOutputStream.closeEntry()
|
||||||
|
|
||||||
|
final JarEntry serviceEntry = new JarEntry("META-INF/services/" + serviceInterface.getName())
|
||||||
|
jarOutputStream.putNextEntry(serviceEntry)
|
||||||
|
jarOutputStream.write(clazz.getName().getBytes(StandardCharsets.UTF_8))
|
||||||
|
jarOutputStream.closeEntry()
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
inputStream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** com.foo.Bar -> com/foo/Bar.class */
|
||||||
|
private static String getResourceName(final String className) {
|
||||||
|
return className.replace('.', '/') + ".class"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue