Skip to content

Commit

Permalink
Improve config mapping for OpenTelemetry extensions (#7193)
Browse files Browse the repository at this point in the history
* Support arbitrary lookup of 'otel.' config from extensions
* Use property for OTel API version
* Don't embed unnecessary classes
* Use includes to control which areas of the OTel API we embed
* Rework OTel package remapper to remap both shaded and unshaded references
* Embed OTel instrumentation API, removing classes already covered by existing mappings as well as experimental packages
* Embed OTel javaagent-extension API, removing classes already covered by existing mappings as well as experimental packages
  As part of this we redirect CallDepth requests to our own CallDepthThreadLocalMap
* Embed opentelemetry-javaagent-servlet-common-bootstrap
* Redirect InstrumentationConfig requests to our own ConfigProvider
* Ignore partial build class-path when checking for forbidden APIs in otel-bootstrap
  • Loading branch information
mcculls committed Jun 17, 2024
1 parent 8f7a318 commit 19753bb
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 53 deletions.
40 changes: 38 additions & 2 deletions dd-java-agent/agent-otel/otel-bootstrap/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ plugins {
id "com.github.johnrengelman.shadow"
}

def otelApiVersion = '1.38.0'
def otelInstrumentationApiVersion = '2.4.0'

apply from: "$rootDir/gradle/java.gradle"
apply plugin: 'instrument'

Expand All @@ -25,6 +28,7 @@ minimumInstructionCoverage = 0.0
minimumBranchCoverage = 0.0

forbiddenApis {
failOnMissingClasses = false
ignoreFailures = true
}
spotbugs {
Expand All @@ -33,8 +37,14 @@ spotbugs {

dependencies {
// latest OpenTelemetry API for drop-in support; instrumented at build-time with our shim
embeddedClasspath group: 'io.opentelemetry', name: 'opentelemetry-api', version: '1.38.0'
embeddedClasspath group: 'io.opentelemetry', name: 'opentelemetry-api', version: otelApiVersion
embeddedClasspath group: 'io.opentelemetry.instrumentation', name: 'opentelemetry-instrumentation-api', version: otelInstrumentationApiVersion
embeddedClasspath group: 'io.opentelemetry.javaagent', name: 'opentelemetry-javaagent-extension-api', version: "$otelInstrumentationApiVersion-alpha"

// selected bootstrap types shared across multiple OpenTelemetry instrumentations
embeddedClasspath group: 'io.opentelemetry.javaagent.instrumentation', name: 'opentelemetry-javaagent-servlet-common-bootstrap', version: "$otelInstrumentationApiVersion-alpha"

compileOnly project(':dd-java-agent:agent-bootstrap')
implementation project(':dd-java-agent:agent-otel:otel-shim')

instrumentPluginClasspath project(path: ':dd-java-agent:agent-otel:otel-tooling', configuration: 'instrumentPluginClasspath')
Expand All @@ -44,6 +54,8 @@ dependencies {
tasks.register('unpackJars', Copy) {
dependsOn configurations.embeddedClasspath
exclude 'META-INF/'
exclude '**/module-info.class'
exclude '**/package-info.class'
from {
configurations.embeddedClasspath.collect { zipTree(it) }
}
Expand All @@ -56,9 +68,33 @@ tasks.named('compileJava') {
shadowJar {
dependencies deps.excludeShared

include '*.jar'
include 'io/opentelemetry/api/**'
exclude 'io/opentelemetry/api/incubator/**'
include 'io/opentelemetry/context/**'
exclude 'io/opentelemetry/context/internal/shaded/**'
include 'io/opentelemetry/semconv/**'
include 'io/opentelemetry/instrumentation/api/**'
exclude 'io/opentelemetry/instrumentation/api/incubator/**'
exclude 'io/opentelemetry/instrumentation/api/internal/cache/**'
exclude 'io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier*'
exclude 'io/opentelemetry/instrumentation/api/util/VirtualField*'
include 'io/opentelemetry/javaagent/bootstrap/**'
exclude 'io/opentelemetry/javaagent/bootstrap/CallDepth*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/ClassLoaderMatcherCacheHolder*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/ConfiguredResourceAttributesHolder*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/EmptyInstrumentationConfig*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/InClassLoaderMatcher*'
exclude 'io/opentelemetry/javaagent/bootstrap/internal/InstrumentationConfig*'
include 'datadog/opentelemetry/shim/**'
include 'datadog/trace/bootstrap/otel/**'

relocate 'io.opentelemetry', 'datadog.trace.bootstrap.otel'
relocate 'io.opentelemetry.api', 'datadog.trace.bootstrap.otel.api'
relocate 'io.opentelemetry.context', 'datadog.trace.bootstrap.otel.context'
relocate 'io.opentelemetry.semconv', 'datadog.trace.bootstrap.otel.semconv'
relocate 'io.opentelemetry.instrumentation.api', 'datadog.trace.bootstrap.otel.instrumentation.api'
relocate 'io.opentelemetry.javaagent.bootstrap', 'datadog.trace.bootstrap.otel.instrumentation'
relocate 'datadog.opentelemetry.shim', 'datadog.trace.bootstrap.otel.shim'
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package datadog.trace.bootstrap.otel.instrumentation;

import datadog.trace.bootstrap.CallDepthThreadLocalMap;

/** Redirects requests to our own {@link CallDepthThreadLocalMap}. */
public final class CallDepth {
private final Class<?> clazz;

private CallDepth(final Class<?> clazz) {
this.clazz = clazz;
}

public static CallDepth forClass(Class<?> clazz) {
return new CallDepth(clazz);
}

public int getAndIncrement() {
return CallDepthThreadLocalMap.incrementCallDepth(clazz);
}

public int decrementAndGet() {
return CallDepthThreadLocalMap.decrementCallDepth(clazz);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package datadog.trace.bootstrap.otel.instrumentation.internal;

import datadog.trace.bootstrap.config.provider.ConfigProvider;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Redirects requests to our own {@link ConfigProvider}. */
public final class InstrumentationConfig {
private static final InstrumentationConfig INSTANCE = new InstrumentationConfig();

private static final Pattern DURATION_PATTERN = Pattern.compile("(\\d+)(ms|[DdHhMmSs]?)");

private static final ConfigProvider delegate = ConfigProvider.getInstance();

public static InstrumentationConfig get() {
return INSTANCE;
}

public String getString(String name) {
return delegate.getString(name);
}

public String getString(String name, String defaultValue) {
return delegate.getString(name, defaultValue);
}

public boolean getBoolean(String name, boolean defaultValue) {
return delegate.getBoolean(name, defaultValue);
}

public int getInt(String name, int defaultValue) {
return delegate.getInteger(name, defaultValue);
}

public long getLong(String name, long defaultValue) {
return delegate.getLong(name, defaultValue);
}

public double getDouble(String name, double defaultValue) {
return delegate.getDouble(name, defaultValue);
}

public Duration getDuration(String name, Duration defaultValue) {
String durationString = delegate.getString(name);
if (null == durationString) {
return defaultValue;
}
Matcher matcher = DURATION_PATTERN.matcher(durationString);
if (matcher.matches()) {
long value = Integer.parseInt(matcher.group(1));
String unit = matcher.group(2);
if ("D".equalsIgnoreCase(unit)) {
return Duration.ofDays(value);
} else if ("H".equalsIgnoreCase(unit)) {
return Duration.ofHours(value);
} else if ("M".equalsIgnoreCase(unit)) {
return Duration.ofMinutes(value);
} else if ("S".equalsIgnoreCase(unit)) {
return Duration.ofSeconds(value);
} else {
return Duration.ofMillis(value); // already in ms
}
} else {
throw new IllegalArgumentException(
"Invalid duration property " + name + "=" + durationString);
}
}

public List<String> getList(String name) {
return getList(name, Collections.emptyList());
}

public List<String> getList(String name, List<String> defaultValue) {
return delegate.getList(name, defaultValue);
}

public Set<String> getSet(String name, Set<String> defaultValue) {
return delegate.getSet(name, defaultValue);
}

public Map<String, String> getMap(String name, Map<String, String> defaultValue) {
Map<String, String> map = delegate.getMergedMap(name);
if (map.isEmpty()) {
map = defaultValue;
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ private String[] removeUnsupportedTypes(String[] interfaces) {
static final class Renamer extends Remapper {
static final Renamer INSTANCE = new Renamer();

private static final String OTEL_JAVAAGENT_SHADED_PREFIX =
"io/opentelemetry/javaagent/shaded/io/opentelemetry/";

private static final String ASM_PREFIX = "org/objectweb/asm/";

/** Datadog equivalent of OpenTelemetry instrumentation classes. */
private static final Map<String, String> RENAMED_TYPES = new HashMap<>();

Expand Down Expand Up @@ -111,9 +106,25 @@ static final class Renamer extends Remapper {
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Source",
Type.getInternalName(OtelMuzzleRefBuilder.Source.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge",
"datadog/trace/bootstrap/otel/Java8BytecodeBridge");
}

/** OpenTelemetry and related packages shaded inside the tracer. */
private static final Map<String, String> RENAMED_PACKAGES = new HashMap<>();

static {
RENAMED_PACKAGES.put(
"io/opentelemetry/javaagent/shaded/io/opentelemetry/", "datadog/trace/bootstrap/otel/");

RENAMED_PACKAGES.put("io/opentelemetry/api/", "datadog/trace/bootstrap/otel/api/");
RENAMED_PACKAGES.put("io/opentelemetry/context/", "datadog/trace/bootstrap/otel/context/");
RENAMED_PACKAGES.put("io/opentelemetry/semconv/", "datadog/trace/bootstrap/otel/semconv/");

RENAMED_PACKAGES.put(
"io/opentelemetry/instrumentation/", "datadog/trace/bootstrap/otel/instrumentation/");
RENAMED_PACKAGES.put(
"io/opentelemetry/javaagent/bootstrap/", "datadog/trace/bootstrap/otel/instrumentation/");

RENAMED_PACKAGES.put("org/objectweb/asm/", "net/bytebuddy/jar/asm/");
}

@Override
Expand All @@ -122,14 +133,10 @@ public String map(String internalName) {
if (null != rename) {
return rename;
}
// map OpenTelemetry's shaded API to our embedded copy
if (internalName.startsWith(OTEL_JAVAAGENT_SHADED_PREFIX)) {
return "datadog/trace/bootstrap/otel/"
+ internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length());
}
// map unshaded ASM types to the shaded copy in byte-buddy
if (internalName.startsWith(ASM_PREFIX)) {
return "net/bytebuddy/jar/asm/" + internalName.substring(ASM_PREFIX.length());
for (Map.Entry<String, String> mapping : RENAMED_PACKAGES.entrySet()) {
if (internalName.startsWith(mapping.getKey())) {
return mapping.getValue() + internalName.substring(mapping.getKey().length());
}
}
return MAP_LOGGING.apply(internalName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
final class OtelEnvironmentConfigSource extends ConfigProvider.Source {
private static final Logger log = LoggerFactory.getLogger(OtelEnvironmentConfigSource.class);

private final boolean enabled;

private final Map<String, String> otelEnvironment = new HashMap<>();

private final Properties otelConfigFile = loadOtelConfigFile();
Expand All @@ -44,7 +46,15 @@ final class OtelEnvironmentConfigSource extends ConfigProvider.Source {

@Override
protected String get(String key) {
return otelEnvironment.get(key);
if (!enabled) {
return null;
}

String value = otelEnvironment.get(key);
if (null == value && key.startsWith("otel.")) {
value = getOtelProperty(key);
}
return value;
}

@Override
Expand All @@ -57,9 +67,10 @@ public ConfigOrigin origin() {
}

OtelEnvironmentConfigSource(Properties datadogConfigFile) {
this.enabled = traceOtelEnabled();
this.datadogConfigFile = datadogConfigFile;

if (traceOtelEnabled()) {
if (enabled) {
setupOteEnvironment();
}
}
Expand Down

0 comments on commit 19753bb

Please sign in to comment.