diff --git a/instrumentation-api-incubator/build.gradle.kts b/instrumentation-api-incubator/build.gradle.kts index 38a046e25a..707fd2217c 100644 --- a/instrumentation-api-incubator/build.gradle.kts +++ b/instrumentation-api-incubator/build.gradle.kts @@ -13,7 +13,7 @@ group = "io.opentelemetry.instrumentation" dependencies { api("io.opentelemetry.semconv:opentelemetry-semconv") api(project(":instrumentation-api")) - implementation("io.opentelemetry:opentelemetry-api-incubator") + api("io.opentelemetry:opentelemetry-api-incubator") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java index 8ce777b270..61ebef57ca 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.api.incubator.config.internal; import static java.util.Collections.emptyList; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import java.time.Duration; import java.util.List; import java.util.Map; @@ -107,4 +108,18 @@ public interface InstrumentationConfig { * {@code key=value,anotherKey=anotherValue}. The returned map is unmodifiable. */ Map getMap(String name, Map defaultValue); + + /** + * Returns a {@link DeclarativeConfigProperties} for the given instrumentation name, or {@code + * null} if no declarative configuration is available for that instrumentation. + * + *

Declarative configuration is used to configure instrumentation properties in a declarative + * way, such as through YAML or JSON files. + * + * @param instrumentationName the name of the instrumentation + * @return the declarative configuration properties for the given instrumentation name, or {@code + * null} if not available + */ + @Nullable + DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName); } diff --git a/instrumentation/methods/javaagent/build.gradle.kts b/instrumentation/methods/javaagent/build.gradle.kts index 7061717ce1..a63c298966 100644 --- a/instrumentation/methods/javaagent/build.gradle.kts +++ b/instrumentation/methods/javaagent/build.gradle.kts @@ -13,8 +13,30 @@ dependencies { compileOnly(project(":instrumentation-annotations-support")) } -tasks.withType().configureEach { +tasks.test { jvmArgs( "-Dotel.instrumentation.methods.include=io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCallable[call];io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCompletableFuture[getResult];javax.naming.directory.InitialDirContext[search]" ) } + +testing { + suites { + val declarativeConfigTest by registering(JvmTestSuite::class) { + targets { + all { + testTask.configure { + jvmArgs( + "-Dotel.experimental.config.file=$projectDir/src/declarativeConfigTest/resources/declarative-config.yaml" + ) + } + } + } + } + } +} + +tasks { + check { + dependsOn(testing.suites) + } +} diff --git a/instrumentation/methods/javaagent/src/declarativeConfigTest/java/io/opentelemetry/javaagent/instrumentation/methods/MethodTest.java b/instrumentation/methods/javaagent/src/declarativeConfigTest/java/io/opentelemetry/javaagent/instrumentation/methods/MethodTest.java new file mode 100644 index 0000000000..5badafa3d2 --- /dev/null +++ b/instrumentation/methods/javaagent/src/declarativeConfigTest/java/io/opentelemetry/javaagent/instrumentation/methods/MethodTest.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.methods; + +import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.Callable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@SuppressWarnings("deprecation") // using deprecated semconv +class MethodTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void methodTraced() { + assertThat(new ConfigTracedCallable().call()).isEqualTo("Hello!"); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("ConfigTracedCallable.call") + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions(ConfigTracedCallable.class, "call")))); + } + + static class ConfigTracedCallable implements Callable { + + @Override + public String call() { + return "Hello!"; + } + } +} diff --git a/instrumentation/methods/javaagent/src/declarativeConfigTest/resources/declarative-config.yaml b/instrumentation/methods/javaagent/src/declarativeConfigTest/resources/declarative-config.yaml new file mode 100644 index 0000000000..999166ad58 --- /dev/null +++ b/instrumentation/methods/javaagent/src/declarativeConfigTest/resources/declarative-config.yaml @@ -0,0 +1,33 @@ +file_format: "0.4" +tracer_provider: + processors: + - simple: + exporter: + test: + - simple: + exporter: + console: + +logger_provider: + processors: + - simple: + exporter: + test: + +meter_provider: + readers: + - periodic: + # Set really long interval. We'll call forceFlush when we need the metrics + # instead of collecting them periodically. + interval: 1000000 + exporter: + test: + +instrumentation/development: + java: + methods: + include: + - class: io.opentelemetry.javaagent.instrumentation.methods.MethodTest$ConfigTracedCallable + methods: + - name: call + span_kind: SERVER diff --git a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodAndType.java b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodAndType.java new file mode 100644 index 0000000000..dfe3fa27fd --- /dev/null +++ b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodAndType.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.methods; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; + +public class MethodAndType { + private final ClassAndMethod classAndMethod; + private final SpanKind spanKind; + + private MethodAndType(ClassAndMethod classAndMethod, SpanKind spanKind) { + this.classAndMethod = classAndMethod; + this.spanKind = spanKind; + } + + public static MethodAndType create(ClassAndMethod classAndMethod, SpanKind spanKind) { + return new MethodAndType(classAndMethod, spanKind); + } + + public ClassAndMethod getClassAndMethod() { + return classAndMethod; + } + + public SpanKind getSpanKind() { + return spanKind; + } +} diff --git a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentation.java b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentation.java index 4b6bc67b47..20f055e0b4 100644 --- a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentation.java +++ b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentation.java @@ -10,33 +10,41 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers. import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; import static io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons.getBootstrapLoader; import static io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.any; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.none; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport; import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.Set; +import java.util.Collection; +import java.util.Map; import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.enumeration.EnumerationDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.matcher.ElementMatcher; public class MethodInstrumentation implements TypeInstrumentation { private final String className; - private final Set methodNames; + private final Map> methodNames; - public MethodInstrumentation(String className, Set methodNames) { + public MethodInstrumentation(String className, Map> methodNames) { this.className = className; this.methodNames = methodNames; } @Override public ElementMatcher classLoaderOptimization() { + if (className == null) { + return any(); + } ElementMatcher delegate = hasClassesNamed(className); return target -> { // hasClassesNamed does not support null class loader, so we provide a custom loader that @@ -50,37 +58,51 @@ public class MethodInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { - return hasSuperType(named(className)); + return className == null ? none() : hasSuperType(named(className)); } @Override public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - namedOneOf(methodNames.toArray(new String[0])).and(isMethod()), - mapping -> - mapping.bind( - MethodReturnType.class, - (instrumentedType, instrumentedMethod, assigner, argumentHandler, sort) -> - Advice.OffsetMapping.Target.ForStackManipulation.of( - instrumentedMethod.getReturnType().asErasure())), - MethodInstrumentation.class.getName() + "$MethodAdvice"); + for (Map.Entry> entry : methodNames.entrySet()) { + SpanKind spanKind = entry.getKey(); + Collection names = entry.getValue(); + transformer.applyAdviceToMethod( + namedOneOf(names.toArray(new String[0])).and(isMethod()), + mapping -> + mapping + .bind( + MethodReturnType.class, + (instrumentedType, instrumentedMethod, assigner, argumentHandler, sort) -> + Advice.OffsetMapping.Target.ForStackManipulation.of( + instrumentedMethod.getReturnType().asErasure())) + .bind( + MethodSpanKind.class, + new EnumerationDescription.ForLoadedEnumeration(spanKind)), + MethodInstrumentation.class.getName() + "$MethodAdvice"); + } } // custom annotation that represents the return type of the method @interface MethodReturnType {} + // custom annotation that represents the SpanKind of the method + @interface MethodSpanKind {} + @SuppressWarnings("unused") public static class MethodAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( + @MethodSpanKind SpanKind spanKind, @Advice.Origin("#t") Class declaringClass, @Advice.Origin("#m") String methodName, - @Advice.Local("otelMethod") ClassAndMethod classAndMethod, + @Advice.Local("otelMethod") MethodAndType classAndMethod, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - classAndMethod = ClassAndMethod.create(declaringClass, methodName); + classAndMethod = + MethodAndType.create(ClassAndMethod.create(declaringClass, methodName), spanKind); + if (!instrumenter().shouldStart(parentContext, classAndMethod)) { return; } @@ -92,7 +114,7 @@ public class MethodInstrumentation implements TypeInstrumentation { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( @MethodReturnType Class methodReturnType, - @Advice.Local("otelMethod") ClassAndMethod classAndMethod, + @Advice.Local("otelMethod") MethodAndType classAndMethod, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope, @Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue, diff --git a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentationModule.java b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentationModule.java index 57169dd0de..67cff18413 100644 --- a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentationModule.java +++ b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentationModule.java @@ -6,13 +6,16 @@ package io.opentelemetry.javaagent.instrumentation.methods; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.config.MethodsConfigurationParser; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,27 +30,35 @@ public class MethodInstrumentationModule extends InstrumentationModule { public MethodInstrumentationModule() { super("methods"); + typeInstrumentations = createInstrumentations(); + } + private static List createInstrumentations() { + DeclarativeConfigProperties methods = + AgentInstrumentationConfig.get().getDeclarativeConfig("methods"); + List list = + methods != null ? MethodsConfig.parseDeclarativeConfig(methods) : parseConfigProperties(); + // ensure that there is at least one instrumentation so that muzzle reference collection could + // work + if (list.isEmpty()) { + return singletonList( + new MethodInstrumentation(null, singletonMap(SpanKind.INTERNAL, emptyList()))); + } + return list; + } + + private static List parseConfigProperties() { Map> classMethodsToTrace = MethodsConfigurationParser.parse( AgentInstrumentationConfig.get().getString(TRACE_METHODS_CONFIG)); - typeInstrumentations = - classMethodsToTrace.entrySet().stream() - .filter(e -> !e.getValue().isEmpty()) - .map(e -> new MethodInstrumentation(e.getKey(), e.getValue())) - .collect(Collectors.toList()); - } - - // the default configuration has empty "otel.instrumentation.methods.include", and so doesn't - // generate any TypeInstrumentation for muzzle to analyze - @Override - public List getAdditionalHelperClassNames() { - return typeInstrumentations.isEmpty() - ? emptyList() - : Arrays.asList( - "io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons", - "io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons$BootstrapLoader"); + return classMethodsToTrace.entrySet().stream() + .filter(e -> !e.getValue().isEmpty()) + .map( + e -> + new MethodInstrumentation( + e.getKey(), singletonMap(SpanKind.INTERNAL, e.getValue()))) + .collect(Collectors.toList()); } @Override diff --git a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodSingletons.java b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodSingletons.java index 3fbf40d66b..9f55ebdba3 100644 --- a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodSingletons.java +++ b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodSingletons.java @@ -9,30 +9,41 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; -import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import javax.annotation.Nullable; public final class MethodSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.methods"; - private static final Instrumenter INSTRUMENTER; + private static final Instrumenter INSTRUMENTER; private static final ClassLoader bootstrapLoader = new BootstrapLoader(); static { - CodeAttributesGetter codeAttributesGetter = - ClassAndMethod.codeAttributesGetter(); + CodeAttributesGetter codeAttributesGetter = + new CodeAttributesGetter() { + @Nullable + @Override + public Class getCodeClass(MethodAndType methodAndType) { + return methodAndType.getClassAndMethod().declaringClass(); + } + + @Nullable + @Override + public String getMethodName(MethodAndType methodAndType) { + return methodAndType.getClassAndMethod().methodName(); + } + }; INSTRUMENTER = - Instrumenter.builder( + Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(codeAttributesGetter)) .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter)) - .buildInstrumenter(SpanKindExtractor.alwaysInternal()); + .buildInstrumenter(MethodAndType::getSpanKind); } - public static Instrumenter instrumenter() { + public static Instrumenter instrumenter() { return INSTRUMENTER; } diff --git a/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodsConfig.java b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodsConfig.java new file mode 100644 index 0000000000..692ede8564 --- /dev/null +++ b/instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodsConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.methods; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class MethodsConfig { + + private static final Logger logger = Logger.getLogger(MethodsConfig.class.getName()); + + private MethodsConfig() {} + + static List parseDeclarativeConfig(DeclarativeConfigProperties methods) { + return methods.getStructuredList("include", emptyList()).stream() + .flatMap(MethodsConfig::parseMethodInstrumentation) + .collect(Collectors.toList()); + } + + private static Stream parseMethodInstrumentation( + DeclarativeConfigProperties config) { + String clazz = config.getString("class"); + if (isNullOrEmpty(clazz)) { + logger.log(Level.WARNING, "Invalid methods configuration - class name missing: {0}", config); + return Stream.empty(); + } + + Map> methodNames = new EnumMap<>(SpanKind.class); + for (DeclarativeConfigProperties method : config.getStructuredList("methods", emptyList())) { + String methodName = method.getString("name"); + if (isNullOrEmpty(methodName)) { + logger.log( + Level.WARNING, "Invalid methods configuration - method name missing: {0}", method); + continue; + } + String spanKind = method.getString("span_kind", "INTERNAL"); + try { + methodNames + .computeIfAbsent( + SpanKind.valueOf(spanKind.toUpperCase(Locale.ROOT)), unused -> new ArrayList<>()) + .add(methodName); + } catch (IllegalArgumentException e) { + logger.log( + Level.WARNING, + "Invalid methods configuration - unknown span_kind: {0} for method: {1}", + new Object[] {spanKind, methodName}); + } + } + + if (methodNames.isEmpty()) { + logger.log(Level.WARNING, "Invalid methods configuration - no methods defined: {0}", config); + return Stream.empty(); + } + + return Stream.of(new MethodInstrumentation(clazz, methodNames)); + } + + private static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java index 8305aef6a4..200bd30c73 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; @@ -108,4 +109,11 @@ public final class ConfigPropertiesBridge implements InstrumentationConfig { return defaultValue; } } + + @Nullable + @Override + public DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName) { + // create a spring boot bridge for DeclarativeConfigProperties + return null; + } } diff --git a/javaagent-extension-api/build.gradle.kts b/javaagent-extension-api/build.gradle.kts index 8e3a426784..96852314dc 100644 --- a/javaagent-extension-api/build.gradle.kts +++ b/javaagent-extension-api/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") api("net.bytebuddy:byte-buddy-dep") - implementation("io.opentelemetry:opentelemetry-api-incubator") implementation(project(":instrumentation-api")) implementation(project(":instrumentation-api-incubator")) diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/EmptyInstrumentationConfig.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/EmptyInstrumentationConfig.java index 303931c5b6..8a6c8110da 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/EmptyInstrumentationConfig.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/EmptyInstrumentationConfig.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.bootstrap.internal; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import java.time.Duration; import java.util.List; @@ -58,4 +59,10 @@ final class EmptyInstrumentationConfig implements InstrumentationConfig { public Map getMap(String name, Map defaultValue) { return defaultValue; } + + @Nullable + @Override + public DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName) { + return null; + } } diff --git a/javaagent-tooling/build.gradle.kts b/javaagent-tooling/build.gradle.kts index 059bae7e58..c44a7c4c0a 100644 --- a/javaagent-tooling/build.gradle.kts +++ b/javaagent-tooling/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { implementation(project(":sdk-autoconfigure-support")) implementation("io.opentelemetry:opentelemetry-api") - testImplementation("io.opentelemetry:opentelemetry-api-incubator") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-extension-kotlin") implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index 927111b602..830c2de21c 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -48,6 +48,7 @@ import io.opentelemetry.javaagent.tooling.muzzle.AgentTooling; import io.opentelemetry.javaagent.tooling.util.Trie; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.SdkAutoconfigureAccess; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -166,7 +167,9 @@ public class AgentInstaller { installOpenTelemetrySdk(extensionClassLoader); ConfigProperties sdkConfig = AgentListener.resolveConfigProperties(autoConfiguredSdk); - AgentInstrumentationConfig.internalInitializeConfig(new ConfigPropertiesBridge(sdkConfig)); + AgentInstrumentationConfig.internalInitializeConfig( + new ConfigPropertiesBridge( + sdkConfig, AutoConfigureUtil.getConfigProvider(autoConfiguredSdk))); copyNecessaryConfigToSystemProperties(sdkConfig); setBootstrapPackages(sdkConfig, extensionClassLoader); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java index de6b3baca6..788aaf7cde 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java @@ -5,6 +5,9 @@ package io.opentelemetry.javaagent.tooling.config; +import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.incubator.config.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; @@ -16,9 +19,12 @@ import javax.annotation.Nullable; public final class ConfigPropertiesBridge implements InstrumentationConfig { private final ConfigProperties configProperties; + @Nullable private final ConfigProvider configProvider; - public ConfigPropertiesBridge(ConfigProperties configProperties) { + public ConfigPropertiesBridge( + ConfigProperties configProperties, @Nullable ConfigProvider configProvider) { this.configProperties = configProperties; + this.configProvider = configProvider; } @Nullable @@ -102,4 +108,12 @@ public final class ConfigPropertiesBridge implements InstrumentationConfig { return defaultValue; } } + + @Nullable + @Override + public DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName) { + return configProvider != null + ? InstrumentationConfigUtil.javaInstrumentationConfig(configProvider, instrumentationName) + : null; + } } diff --git a/testing-common/build.gradle.kts b/testing-common/build.gradle.kts index 00a742f5f2..76f57246a4 100644 --- a/testing-common/build.gradle.kts +++ b/testing-common/build.gradle.kts @@ -43,7 +43,6 @@ dependencies { api("org.junit.jupiter:junit-jupiter-params") api("io.opentelemetry:opentelemetry-api") - compileOnly("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-sdk") api("io.opentelemetry:opentelemetry-sdk-testing") api("io.opentelemetry.semconv:opentelemetry-semconv-incubating") @@ -69,6 +68,7 @@ dependencies { implementation("org.slf4j:jul-to-slf4j") implementation("io.opentelemetry:opentelemetry-exporter-logging") implementation("io.opentelemetry.contrib:opentelemetry-baggage-processor") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") api(project(":instrumentation-api-incubator")) annotationProcessor("com.google.auto.service:auto-service") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java index b97ecbd8cc..d2aa42279d 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java @@ -10,6 +10,7 @@ import static java.util.Arrays.asList; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.incubator.config.GlobalConfigProvider; import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.TracerBuilder; @@ -21,6 +22,9 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.contrib.baggage.processor.BaggageSpanProcessor; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.instrumentation.testing.internal.MetaDataCollector; +import io.opentelemetry.instrumentation.testing.provider.TestLogRecordExporterComponentProvider; +import io.opentelemetry.instrumentation.testing.provider.TestMetricExporterComponentProvider; +import io.opentelemetry.instrumentation.testing.provider.TestSpanExporterComponentProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; @@ -63,10 +67,14 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { static { GlobalOpenTelemetry.resetForTest(); + GlobalConfigProvider.resetForTest(); testSpanExporter = InMemorySpanExporter.create(); testMetricExporter = InMemoryMetricExporter.create(AggregationTemporality.DELTA); testLogRecordExporter = InMemoryLogRecordExporter.create(); + TestSpanExporterComponentProvider.setSpanExporter(testSpanExporter); + TestMetricExporterComponentProvider.setMetricExporter(testMetricExporter); + TestLogRecordExporterComponentProvider.setLogRecordExporter(testLogRecordExporter); metricReader = PeriodicMetricReader.builder(testMetricExporter) @@ -116,6 +124,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { public void beforeTestClass() { // just in case: if there was any test that modified the global instance, reset it GlobalOpenTelemetry.resetForTest(); + GlobalConfigProvider.resetForTest(); GlobalOpenTelemetry.set(openTelemetrySdk); } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java index b417b7d380..4c7bb78415 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java @@ -95,6 +95,7 @@ public abstract class InstrumentationExtension } @SafeVarargs + @SuppressWarnings("varargs") public final void waitAndAssertMetrics( String instrumentationName, Consumer... assertions) { testRunner.waitAndAssertMetrics(instrumentationName, assertions); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestLogRecordExporterComponentProvider.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestLogRecordExporterComponentProvider.java new file mode 100644 index 0000000000..749f485bc9 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestLogRecordExporterComponentProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import java.util.Objects; + +public class TestLogRecordExporterComponentProvider + implements ComponentProvider { + + private static LogRecordExporter logRecordExporter; + + @Override + public Class getType() { + return LogRecordExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public LogRecordExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(logRecordExporter, "logRecordExporter must not be null"); + } + + public static void setLogRecordExporter(LogRecordExporter logRecordExporter) { + TestLogRecordExporterComponentProvider.logRecordExporter = logRecordExporter; + } +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestMetricExporterComponentProvider.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestMetricExporterComponentProvider.java new file mode 100644 index 0000000000..f661c29059 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestMetricExporterComponentProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.util.Objects; + +public class TestMetricExporterComponentProvider implements ComponentProvider { + + private static MetricExporter metricExporter; + + @Override + public Class getType() { + return MetricExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public MetricExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(metricExporter, "metricExporter must not be null"); + } + + public static void setMetricExporter(MetricExporter metricExporter) { + TestMetricExporterComponentProvider.metricExporter = metricExporter; + } +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestSpanExporterComponentProvider.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestSpanExporterComponentProvider.java new file mode 100644 index 0000000000..4f5d64df59 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/provider/TestSpanExporterComponentProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.Objects; + +public class TestSpanExporterComponentProvider implements ComponentProvider { + + private static SpanExporter spanExporter; + + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public SpanExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(spanExporter, "spanExporter must not be null"); + } + + public static void setSpanExporter(SpanExporter spanExporter) { + TestSpanExporterComponentProvider.spanExporter = spanExporter; + } +} diff --git a/testing-common/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/testing-common/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 0000000000..d5c32afe95 --- /dev/null +++ b/testing-common/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,4 @@ +io.opentelemetry.instrumentation.testing.provider.TestSpanExporterComponentProvider +io.opentelemetry.instrumentation.testing.provider.TestMetricExporterComponentProvider +io.opentelemetry.instrumentation.testing.provider.TestLogRecordExporterComponentProvider + diff --git a/testing/agent-exporter/build.gradle.kts b/testing/agent-exporter/build.gradle.kts index f42ab50557..55bb237eac 100644 --- a/testing/agent-exporter/build.gradle.kts +++ b/testing/agent-exporter/build.gradle.kts @@ -17,5 +17,6 @@ dependencies { compileOnly(project(":javaagent-tooling")) implementation("io.opentelemetry:opentelemetry-exporter-otlp-common") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") } diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterFactory.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterFactory.java index e9d88c3faf..a7a8dd6d02 100644 --- a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterFactory.java +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterFactory.java @@ -5,6 +5,9 @@ package io.opentelemetry.javaagent.testing.exporter; +import io.opentelemetry.javaagent.testing.provider.TestLogRecordExporterComponentProvider; +import io.opentelemetry.javaagent.testing.provider.TestMetricExporterComponentProvider; +import io.opentelemetry.javaagent.testing.provider.TestSpanExporterComponentProvider; import java.util.List; import java.util.concurrent.TimeUnit; @@ -14,6 +17,12 @@ public final class AgentTestingExporterFactory { static final OtlpInMemoryMetricExporter metricExporter = new OtlpInMemoryMetricExporter(); static final OtlpInMemoryLogRecordExporter logExporter = new OtlpInMemoryLogRecordExporter(); + static { + TestSpanExporterComponentProvider.setSpanExporter(spanExporter); + TestMetricExporterComponentProvider.setMetricExporter(metricExporter); + TestLogRecordExporterComponentProvider.setLogRecordExporter(logExporter); + } + public static List getSpanExportRequests() { return spanExporter.getCollectedExportRequests(); } diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestLogRecordExporterComponentProvider.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestLogRecordExporterComponentProvider.java new file mode 100644 index 0000000000..3d43ee7092 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestLogRecordExporterComponentProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import java.util.Objects; + +public class TestLogRecordExporterComponentProvider + implements ComponentProvider { + + private static LogRecordExporter logRecordExporter; + + @Override + public Class getType() { + return LogRecordExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public LogRecordExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(logRecordExporter, "logRecordExporter must not be null"); + } + + public static void setLogRecordExporter(LogRecordExporter logRecordExporter) { + TestLogRecordExporterComponentProvider.logRecordExporter = logRecordExporter; + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestMetricExporterComponentProvider.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestMetricExporterComponentProvider.java new file mode 100644 index 0000000000..6e1059cced --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestMetricExporterComponentProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.util.Objects; + +public class TestMetricExporterComponentProvider implements ComponentProvider { + + private static MetricExporter metricExporter; + + @Override + public Class getType() { + return MetricExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public MetricExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(metricExporter, "metricExporter must not be null"); + } + + public static void setMetricExporter(MetricExporter metricExporter) { + TestMetricExporterComponentProvider.metricExporter = metricExporter; + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestSpanExporterComponentProvider.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestSpanExporterComponentProvider.java new file mode 100644 index 0000000000..40be3a821e --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/provider/TestSpanExporterComponentProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.provider; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.Objects; + +public class TestSpanExporterComponentProvider implements ComponentProvider { + + private static SpanExporter spanExporter; + + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public SpanExporter create(DeclarativeConfigProperties config) { + return Objects.requireNonNull(spanExporter, "spanExporter must not be null"); + } + + public static void setSpanExporter(SpanExporter spanExporter) { + TestSpanExporterComponentProvider.spanExporter = spanExporter; + } +} diff --git a/testing/agent-exporter/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/testing/agent-exporter/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 0000000000..238cacba53 --- /dev/null +++ b/testing/agent-exporter/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,4 @@ +io.opentelemetry.javaagent.testing.provider.TestSpanExporterComponentProvider +io.opentelemetry.javaagent.testing.provider.TestMetricExporterComponentProvider +io.opentelemetry.javaagent.testing.provider.TestLogRecordExporterComponentProvider +