Skip to content

Commit c771141

Browse files
committed
Map basic OpenTelemetry instrumentation types to their Datadog equivalents
1 parent 86c104f commit c771141

9 files changed

Lines changed: 315 additions & 1 deletion

File tree

dd-java-agent/agent-builder/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ excludedClassesCoverage += ['datadog.trace.agent.tooling.*']
77
dependencies {
88
api project(':dd-java-agent:agent-tooling')
99

10+
implementation project(':dd-java-agent:agent-otel:otel-tooling')
11+
1012
testImplementation project(':dd-java-agent:testing')
1113
}
1214

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/ExtensionFinder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.agent.tooling;
22

3+
import static datadog.opentelemetry.tooling.OtelExtensionHandler.OPENTELEMETRY;
34
import static datadog.trace.agent.tooling.ExtensionHandler.DATADOG;
45

56
import de.thetaphi.forbiddenapis.SuppressForbidden;
@@ -23,7 +24,7 @@
2324
public final class ExtensionFinder {
2425
private static final Logger log = LoggerFactory.getLogger(ExtensionFinder.class);
2526

26-
private static final ExtensionHandler[] handlers = {DATADOG};
27+
private static final ExtensionHandler[] handlers = {OPENTELEMETRY, DATADOG};
2728

2829
/**
2930
* Discovers extensions on the configured path and creates a classloader for each extension.

dd-java-agent/agent-otel/otel-tooling/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ minimumBranchCoverage = 0.0
66
dependencies {
77
api deps.bytebuddy
88
api deps.bytebuddyagent
9+
10+
compileOnly project(':dd-java-agent:agent-tooling')
911
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import datadog.trace.agent.tooling.ExtensionHandler;
4+
import java.net.URL;
5+
import java.net.URLConnection;
6+
import java.util.jar.JarEntry;
7+
import java.util.jar.JarFile;
8+
9+
/** Handles OpenTelemetry instrumentations, so they can be loaded into the Datadog tracer. */
10+
public final class OtelExtensionHandler extends ExtensionHandler {
11+
12+
/** Handler for loading externally built OpenTelemetry extensions. */
13+
public static final OtelExtensionHandler OPENTELEMETRY = new OtelExtensionHandler();
14+
15+
private static final String OPENTELEMETRY_MODULE_DESCRIPTOR =
16+
"META-INF/services/io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule";
17+
18+
private static final String DATADOG_MODULE_DESCRIPTOR =
19+
"META-INF/services/datadog.trace.agent.tooling.InstrumenterModule";
20+
21+
@Override
22+
public JarEntry mapEntry(JarFile jar, String file) {
23+
if (DATADOG_MODULE_DESCRIPTOR.equals(file)) {
24+
// redirect request to include OpenTelemetry instrumentations
25+
return super.mapEntry(jar, OPENTELEMETRY_MODULE_DESCRIPTOR);
26+
} else {
27+
return super.mapEntry(jar, file);
28+
}
29+
}
30+
31+
@Override
32+
public URLConnection mapContent(URL url, JarFile jar, JarEntry entry) {
33+
if (entry.getName().endsWith(".class")) {
34+
return new ClassMappingConnection(url, jar, entry, OtelInstrumentationMapper::new);
35+
} else {
36+
return new JarFileConnection(url, jar, entry);
37+
}
38+
}
39+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import static datadog.trace.agent.tooling.ExtensionHandler.MAP_LOGGING;
4+
5+
import datadog.trace.agent.tooling.InstrumenterModule;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.HashMap;
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
import net.bytebuddy.jar.asm.ClassVisitor;
14+
import net.bytebuddy.jar.asm.MethodVisitor;
15+
import net.bytebuddy.jar.asm.commons.ClassRemapper;
16+
import net.bytebuddy.jar.asm.commons.Remapper;
17+
18+
/** Maps OpenTelemetry instrumentations to use the Datadog {@link InstrumenterModule} API. */
19+
public final class OtelInstrumentationMapper extends ClassRemapper {
20+
21+
private static final Set<String> UNSUPPORTED_TYPES =
22+
new HashSet<>(
23+
Arrays.asList("io/opentelemetry/javaagent/tooling/muzzle/InstrumentationModuleMuzzle"));
24+
25+
private static final Set<String> UNSUPPORTED_METHODS =
26+
new HashSet<>(
27+
Arrays.asList(
28+
"getMuzzleReferences", "getMuzzleHelperClassNames", "registerMuzzleVirtualFields"));
29+
30+
public OtelInstrumentationMapper(ClassVisitor classVisitor) {
31+
super(classVisitor, Renamer.INSTANCE);
32+
}
33+
34+
@Override
35+
public void visit(
36+
int version,
37+
int access,
38+
String name,
39+
String signature,
40+
String superName,
41+
String[] interfaces) {
42+
super.visit(version, access, name, signature, superName, removeUnsupportedTypes(interfaces));
43+
}
44+
45+
@Override
46+
public MethodVisitor visitMethod(
47+
int access, String name, String descriptor, String signature, String[] exceptions) {
48+
if (!UNSUPPORTED_METHODS.contains(name)) {
49+
return super.visitMethod(access, name, descriptor, signature, exceptions);
50+
} else {
51+
return null; // remove unsupported method
52+
}
53+
}
54+
55+
private String[] removeUnsupportedTypes(String[] interfaces) {
56+
List<String> filtered = null;
57+
for (int i = interfaces.length - 1; i >= 0; i--) {
58+
if (UNSUPPORTED_TYPES.contains(interfaces[i])) {
59+
if (null == filtered) {
60+
filtered = new ArrayList<>(Arrays.asList(interfaces));
61+
}
62+
filtered.remove(i); // remove unsupported interface
63+
}
64+
}
65+
return null != filtered ? filtered.toArray(new String[0]) : interfaces;
66+
}
67+
68+
static final class Renamer extends Remapper {
69+
static final Renamer INSTANCE = new Renamer();
70+
71+
private static final String OTEL_JAVAAGENT_SHADED_PREFIX =
72+
"io/opentelemetry/javaagent/shaded/io/opentelemetry/";
73+
74+
/** Datadog equivalent of OpenTelemetry instrumentation classes. */
75+
private static final Map<String, String> RENAMED_TYPES = new HashMap<>();
76+
77+
static {
78+
RENAMED_TYPES.put(
79+
"io/opentelemetry/javaagent/extension/instrumentation/InstrumentationModule",
80+
"datadog/opentelemetry/tooling/OtelInstrumenterModule");
81+
RENAMED_TYPES.put(
82+
"io/opentelemetry/javaagent/extension/instrumentation/TypeInstrumentation",
83+
"datadog/opentelemetry/tooling/OtelInstrumenter");
84+
RENAMED_TYPES.put(
85+
"io/opentelemetry/javaagent/extension/instrumentation/TypeTransformer",
86+
"datadog/opentelemetry/tooling/OtelTransformer");
87+
RENAMED_TYPES.put(
88+
"io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge",
89+
"datadog/trace/bootstrap/otel/Java8BytecodeBridge");
90+
RENAMED_TYPES.put(
91+
"io/opentelemetry/javaagent/extension/matcher/AgentElementMatchers",
92+
"datadog/trace/agent/tooling/bytebuddy/matcher/HierarchyMatchers");
93+
}
94+
95+
@Override
96+
public String map(String internalName) {
97+
String rename = RENAMED_TYPES.get(internalName);
98+
if (null != rename) {
99+
return rename;
100+
}
101+
// map OpenTelemetry's shaded API to our embedded copy
102+
if (internalName.startsWith(OTEL_JAVAAGENT_SHADED_PREFIX)) {
103+
return "datadog/trace/bootstrap/otel/"
104+
+ internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length());
105+
}
106+
return MAP_LOGGING.apply(internalName);
107+
}
108+
}
109+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import datadog.trace.agent.tooling.Instrumenter;
4+
import net.bytebuddy.description.type.TypeDescription;
5+
import net.bytebuddy.matcher.ElementMatcher;
6+
7+
/** Replaces OpenTelemetry's {@code TypeInstrumentation} callback when mapping extensions. */
8+
public interface OtelInstrumenter
9+
extends Instrumenter.ForTypeHierarchy,
10+
Instrumenter.HasMethodAdvice,
11+
Instrumenter.HasTypeAdvice {
12+
13+
@Override
14+
default String hierarchyMarkerType() {
15+
return null; // no hint available
16+
}
17+
18+
@Override
19+
default ElementMatcher<TypeDescription> hierarchyMatcher() {
20+
return typeMatcher();
21+
}
22+
23+
@Override
24+
default void methodAdvice(MethodTransformer methodTransformer) {
25+
OtelTransformerState.capture(this).with(methodTransformer);
26+
}
27+
28+
@Override
29+
default void typeAdvice(TypeTransformer typeTransformer) {
30+
OtelTransformerState.capture(this).with(typeTransformer);
31+
}
32+
33+
ElementMatcher<TypeDescription> typeMatcher();
34+
35+
/**
36+
* Once both transformers have been captured in {@link #methodAdvice} and {@link #typeAdvice} the
37+
* {@code #transform} method will be called. This allows the extension to register method and type
38+
* advice at the same time, using the single interface expected by OpenTelemetry.
39+
*/
40+
void transform(OtelTransformer transformer);
41+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import datadog.trace.agent.tooling.InstrumenterModule;
4+
import datadog.trace.api.InstrumenterConfig;
5+
6+
/**
7+
* Replaces OpenTelemetry's {@code InstrumentationModule} when mapping extensions.
8+
*
9+
* <p>Original instrumentation names and aliases are prefixed with {@literal "otel."}.
10+
*/
11+
public abstract class OtelInstrumenterModule extends InstrumenterModule.Tracing {
12+
13+
public OtelInstrumenterModule(String instrumentationName, String... additionalNames) {
14+
super(namespace(instrumentationName), namespace(additionalNames));
15+
}
16+
17+
@Override
18+
protected boolean defaultEnabled() {
19+
return InstrumenterConfig.get().isTraceOtelEnabled() && super.defaultEnabled();
20+
}
21+
22+
private static String namespace(String name) {
23+
return "otel." + name;
24+
}
25+
26+
private static String[] namespace(String[] names) {
27+
String[] namespaced = new String[names.length];
28+
for (int i = 0; i < names.length; i++) {
29+
namespaced[i] = namespace(names[i]);
30+
}
31+
return namespaced;
32+
}
33+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import net.bytebuddy.agent.builder.AgentBuilder;
4+
import net.bytebuddy.description.method.MethodDescription;
5+
import net.bytebuddy.matcher.ElementMatcher;
6+
7+
/** Replaces OpenTelemetry's {@code TypeTransformer} callback when mapping extensions. */
8+
public interface OtelTransformer {
9+
10+
void applyAdviceToMethod(
11+
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName);
12+
13+
void applyTransformer(AgentBuilder.Transformer transformer);
14+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import datadog.trace.agent.tooling.Instrumenter;
4+
import net.bytebuddy.agent.builder.AgentBuilder;
5+
import net.bytebuddy.description.method.MethodDescription;
6+
import net.bytebuddy.matcher.ElementMatcher;
7+
8+
/**
9+
* {@link OtelTransformer} state captured when processing OpenTelemetry extensions with {@code
10+
* CombiningTransformerBuilder}. Assumes that the builder is single threaded.
11+
*
12+
* <p>OpenTelemetry has a single transformer callback with methods to register method advice and
13+
* type transformations at the same time, whereas the Datadog tracer has separate {@link
14+
* Instrumenter.MethodTransformer} and {@link Instrumenter.TypeTransformer} callbacks.
15+
*
16+
* <p>To map between the two we capture the Datadog method and type transformers here, from calls to
17+
* {@link OtelInstrumenter}. Once we have captured both transformers we trigger the single transform
18+
* request through the mapped OpenTelemetry callback.
19+
*/
20+
final class OtelTransformerState implements OtelTransformer {
21+
private static final OtelTransformerState CURRENT = new OtelTransformerState();
22+
23+
private OtelInstrumenter instrumenter;
24+
private Instrumenter.MethodTransformer methodTransformer;
25+
private Instrumenter.TypeTransformer typeTransformer;
26+
27+
static OtelTransformerState capture(OtelInstrumenter instrumenter) {
28+
if (instrumenter != CURRENT.instrumenter) {
29+
CURRENT.reset();
30+
CURRENT.instrumenter = instrumenter;
31+
}
32+
return CURRENT;
33+
}
34+
35+
void with(Instrumenter.MethodTransformer methodTransformer) {
36+
this.methodTransformer = methodTransformer;
37+
if (null != this.typeTransformer) {
38+
triggerTransform();
39+
}
40+
}
41+
42+
void with(Instrumenter.TypeTransformer typeTransformer) {
43+
this.typeTransformer = typeTransformer;
44+
if (null != this.methodTransformer) {
45+
triggerTransform();
46+
}
47+
}
48+
49+
private void triggerTransform() {
50+
try {
51+
instrumenter.transform(this);
52+
} finally {
53+
reset();
54+
}
55+
}
56+
57+
private void reset() {
58+
this.instrumenter = null;
59+
this.methodTransformer = null;
60+
this.typeTransformer = null;
61+
}
62+
63+
@Override
64+
public void applyAdviceToMethod(
65+
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName) {
66+
methodTransformer.applyAdvice(methodMatcher, adviceClassName);
67+
}
68+
69+
@Override
70+
public void applyTransformer(AgentBuilder.Transformer transformer) {
71+
typeTransformer.applyAdvice(transformer::transform);
72+
}
73+
}

0 commit comments

Comments
 (0)