Skip to content

Extract common integration framework for custom span decorator #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.opentracing.contrib.specialagent;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InstrumentIntegration {
private static String GLOBAL_CLASSPATH_OPT = "sa.integration.classpath";

private static final Logger logger = Logger.getLogger(InstrumentIntegration.class);

private static InstrumentIntegration SINGLETON;

private URLClassLoader GLOBAL;

private Map<String, URLClassLoader> extensionClassLoaders = new HashMap<>();

public static void init(final Map<String, String[]> extensionClasspaths) {
if (SINGLETON != null) {
SINGLETON.clear();
}
SINGLETON = new InstrumentIntegration(extensionClasspaths);
}

public static InstrumentIntegration getInstance() {
if (SINGLETON == null) {
init(null);
}
return SINGLETON;
}

private InstrumentIntegration(final Map<String, String[]> extensionClasspaths) {
if (extensionClasspaths != null) {
final String[] globalClasspaths = extensionClasspaths.get(GLOBAL_CLASSPATH_OPT);
if (GLOBAL == null && globalClasspaths != null) {
GLOBAL = this.createClassLoader(globalClasspaths, null);
}

for (final Map.Entry<String, String[]> entry : extensionClasspaths.entrySet()) {
extensionClassLoaders.put(entry.getKey(), this.createClassLoader(entry.getValue(), GLOBAL));
}
}
}

private URLClassLoader createClassLoader(final String[] classpaths, ClassLoader parent) {
final URL[] urls = new URL[classpaths.length];
for (int i = 0; i < classpaths.length; ++i) {
final String part = classpaths[i];
try {
urls[i] = new URL("file", "", part.endsWith(".jar") || part.endsWith("/") ? part : part + "/");
} catch (final MalformedURLException e) {
logger.log(Level.WARNING, part + "is not a valid URL");
}
}
return new URLClassLoader(urls, parent);
}

public <T> List<T> getExtensionInstances(Class<T> clazz, String extensionClasspathOpt, String... classNames) {
final ClassLoader classLoader = this.extensionClassLoaders.get(extensionClasspathOpt);
final List<T> result = new ArrayList<>();
if (classNames != null) {
for (final String className : classNames) {
final T object = newInstance(classLoader, clazz, className);
if (object != null)
result.add(object);
}
}
return result;
}

private <T> T newInstance(final ClassLoader classLoader, final Class<T> clazz, final String className) {
try {
Class<?> decoratorClass = loadClass(classLoader, className);
if (decoratorClass == null)
decoratorClass = loadClass(clazz.getClassLoader(), className);

if (decoratorClass == null)
return null;

if (clazz.isAssignableFrom(decoratorClass))
return clazz.cast(decoratorClass.newInstance());

logger.log(Level.WARNING, className + " is not a subclass of " + clazz.getName());
} catch (final InstantiationException | IllegalAccessException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}

return null;
}

private Class<?> loadClass(ClassLoader classLoader, String className) {
if (classLoader == null)
return null;

try {
return classLoader.loadClass(className);
} catch (final ClassNotFoundException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}

return null;
}

private void clear() {
if (this.GLOBAL != null) {
try {
this.GLOBAL.close();
} catch (IOException e) {
logger.finer("Closing classLoader fail: " + e.getMessage());
}
this.GLOBAL = null;
}
for (URLClassLoader classLoader : this.extensionClassLoaders.values()) {
try {
classLoader.close();
} catch (IOException e) {
logger.finer("Closing classLoader fail: " + e.getMessage());
}
}
this.extensionClassLoaders.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.opentracing.contrib.specialagent;

import java.util.Collections;
import java.util.List;

public class SpanDecoratorLoader<SD> {
public static final String DECORATOR_SEPARATOR = ",";
public static final String SPAN_DECORATORS_OPT_PREFIX = "sa.integration.";
public static final String SPAN_DECORATORS_CLASSPATH_OPT_SURFIX = ".spanDecorators.classpath";
public static final String SPAN_DECORATORS_OPT_SURFIX = ".spanDecorators";

private String spanDecoratorClasspathOpt;
private String spanDecoratorsOpt;
private Class<SD> spanDecoratorClass;

public static <SD> SpanDecoratorLoader<SD> newInstance(String ruleName, Class<SD> spanDecoratorClass) {
return new SpanDecoratorLoader<>(ruleName, spanDecoratorClass);
}

private SpanDecoratorLoader(String ruleName, Class<SD> spanDecoratorClass) {
this.spanDecoratorClass = spanDecoratorClass;
this.spanDecoratorClasspathOpt = SPAN_DECORATORS_OPT_PREFIX + ruleName + SPAN_DECORATORS_CLASSPATH_OPT_SURFIX;
this.spanDecoratorsOpt = SPAN_DECORATORS_OPT_PREFIX + ruleName + SPAN_DECORATORS_OPT_SURFIX;
}

public List<SD> getSpanDecorators(SD defultSpanDecorator) {
final String spanDecoratorsArgs = System.getProperty(spanDecoratorsOpt);
String[] spanDecoratorNames = null;
if (spanDecoratorsArgs != null) {
spanDecoratorNames = spanDecoratorsArgs.split(DECORATOR_SEPARATOR);
}
final List<SD> spanDecorators = InstrumentIntegration.getInstance().getExtensionInstances(this.spanDecoratorClass,
this.spanDecoratorClasspathOpt, spanDecoratorNames);
if (defultSpanDecorator != null && (spanDecorators == null || spanDecorators.isEmpty())) {
return Collections.singletonList(defultSpanDecorator);
}
return spanDecorators;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ private static void load(final Manager manager, final File[] ruleFiles, final Is
final HashMap<String,Boolean> integrationRuleNameToEnable = new HashMap<>();
final HashMap<String,Boolean> traceExporterNameToEnable = new HashMap<>();
final File[] classPaths = SpecialAgentUtil.parseConfiguration(properties, verbosePluginNames, integrationRuleNameToEnable, traceExporterNameToEnable);
final Map<String, String[]> integrationClasspaths = SpecialAgentUtil.parseIntegrationConfiguration(properties);
InstrumentIntegration.init(integrationClasspaths);

final boolean allIntegrationsEnabled = !integrationRuleNameToEnable.containsKey("*") || integrationRuleNameToEnable.remove("*");
if (logger.isLoggable(Level.FINER))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.security.CodeSource;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -81,6 +82,20 @@ else if (key.equals("sa.include")) {
return includedPlugins;
}

static Map<String, String[]> parseIntegrationConfiguration(final Map<String,String> properties) {
final HashMap<String,String[]> integrationClasspaths = new HashMap<>();
for (final Map.Entry<String,String> property : properties.entrySet()) {
final String key = property.getKey();
final String value = property.getValue();

if (key.contains("integration") && key.endsWith("classpath")) {
final String[] classpaths = value.split(File.pathSeparator);
integrationClasspaths.put(key, classpaths);
}
}
return integrationClasspaths;
}

static JarFile createTempJarFile(final File dir) throws IOException {
final Path dirPath = dir.toPath();
final Path zipPath = Files.createTempFile("specialagent", ".jar");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,77 +15,15 @@

package io.opentracing.contrib.specialagent.rule.apache.httpclient;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import io.opentracing.contrib.specialagent.Level;
import io.opentracing.contrib.specialagent.Logger;
import io.opentracing.contrib.specialagent.SpanDecoratorLoader;

public class Configuration {
public static final Logger logger = Logger.getLogger(Configuration.class);
public static final String SPAN_DECORATORS = "sa.integration.apache:httpclient.spanDecorators";
public static final String SPAN_DECORATORS_CLASSPATH = "sa.integration.apache:httpclient.spanDecorators.classpath";
public static final String DECORATOR_SEPARATOR = ",";
public static final List<ApacheClientSpanDecorator> spanDecorators = parseSpanDecorators();

public static final List<ApacheClientSpanDecorator> spanDecorators = parseSpanDecorators(System.getProperty(SPAN_DECORATORS));
private static ClassLoader decoratorClassLoader;

static List<ApacheClientSpanDecorator> parseSpanDecorators(final String spanDecoratorsArgs) {
final List<ApacheClientSpanDecorator> result = new ArrayList<>();
if (spanDecoratorsArgs != null) {
final String[] parts = spanDecoratorsArgs.split(DECORATOR_SEPARATOR);
for (final String part : parts) {
final ApacheClientSpanDecorator decorator = newSpanDecoratorInstance(part);
if (decorator != null)
result.add(decorator);
}
}

if (result.isEmpty())
result.add(new ApacheClientSpanDecorator.StandardTags());

return result;
}

private static ApacheClientSpanDecorator newSpanDecoratorInstance(final String className) {
try {
final Class<?> decoratorClass = getDecoratorClassLoader().loadClass(className);
if (ApacheClientSpanDecorator.class.isAssignableFrom(decoratorClass))
return (ApacheClientSpanDecorator)decoratorClass.newInstance();

logger.log(Level.WARNING, className + " is not a subclass of " + ApacheClientSpanDecorator.class.getName());
}
catch (final ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}

return null;
}

private static ClassLoader getDecoratorClassLoader() {
if (decoratorClassLoader != null)
return decoratorClassLoader;

final String spanDecoratorsClassPath = System.getProperty(SPAN_DECORATORS_CLASSPATH);
if (spanDecoratorsClassPath == null || spanDecoratorsClassPath.isEmpty())
return decoratorClassLoader = ApacheClientSpanDecorator.class.getClassLoader();

final String[] parts = spanDecoratorsClassPath.split(File.pathSeparator);
final URL[] urls = new URL[parts.length];
for (int i = 0; i < parts.length; ++i) {
final String part = parts[i];
try {
urls[i] = new URL("file", "", part.endsWith(".jar") || part.endsWith("/") ? part : part + "/");
}
catch (final MalformedURLException e) {
logger.log(Level.WARNING, part + "is not a valid URL");
}
}

return decoratorClassLoader = new URLClassLoader(urls, ApacheClientSpanDecorator.class.getClassLoader());
static List<ApacheClientSpanDecorator> parseSpanDecorators() {
return SpanDecoratorLoader.newInstance("apache:httpclient", ApacheClientSpanDecorator.class)
.getSpanDecorators(new ApacheClientSpanDecorator.StandardTags());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ public void testImplicitSpanDecorators() {
}

private static void testDecorators(final String spanDecoratorsArgs, final Class<?> ... expecteds) {
final List<ApacheClientSpanDecorator> decorators = Configuration.parseSpanDecorators(spanDecoratorsArgs);
System.clearProperty("sa.integration.apache:httpclient.spanDecorators");
if(spanDecoratorsArgs != null)
System.setProperty("sa.integration.apache:httpclient.spanDecorators", spanDecoratorsArgs);
final List<ApacheClientSpanDecorator> decorators = Configuration.parseSpanDecorators();
assertEquals(expecteds.length, decorators.size());
final List<Class<?>> list = Arrays.asList(expecteds);
for (final ApacheClientSpanDecorator decorator : decorators)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public void before(final MockTracer tracer) {

@Test
public void test(final MockTracer tracer) {
System.setProperty(Configuration.SPAN_DECORATORS, "io.opentracing.contrib.specialagent.rule.apache.httpclient.ApacheClientSpanDecorator$StandardTags,io.opentracing.contrib.specialagent.rule.apache.httpclient.MockSpanDecorator");
System.setProperty("sa.integration.apache:httpclient.spanDecorators",
"io.opentracing.contrib.specialagent.rule.apache.httpclient.ApacheClientSpanDecorator$StandardTags,io.opentracing.contrib.specialagent.rule.apache.httpclient.MockSpanDecorator");

final CloseableHttpClient httpClient = HttpClients.createDefault();
final String url = "http://localhost:12345";
Expand Down