Span kind for method instrumentation / Declarative configuration tooling (#14014)

Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
This commit is contained in:
Gregor Zeitlinger 2025-06-28 06:43:18 +02:00 committed by GitHub
parent 7689228d47
commit 4251217436
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 585 additions and 48 deletions

View File

@ -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")

View File

@ -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<String, String> getMap(String name, Map<String, String> defaultValue);
/**
* Returns a {@link DeclarativeConfigProperties} for the given instrumentation name, or {@code
* null} if no declarative configuration is available for that instrumentation.
*
* <p>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);
}

View File

@ -13,8 +13,30 @@ dependencies {
compileOnly(project(":instrumentation-annotations-support"))
}
tasks.withType<Test>().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)
}
}

View File

@ -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<String> {
@Override
public String call() {
return "Hello!";
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<String> methodNames;
private final Map<SpanKind, Collection<String>> methodNames;
public MethodInstrumentation(String className, Set<String> methodNames) {
public MethodInstrumentation(String className, Map<SpanKind, Collection<String>> methodNames) {
this.className = className;
this.methodNames = methodNames;
}
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
if (className == null) {
return any();
}
ElementMatcher<ClassLoader> 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<TypeDescription> 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<SpanKind, Collection<String>> entry : methodNames.entrySet()) {
SpanKind spanKind = entry.getKey();
Collection<String> 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,

View File

@ -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<TypeInstrumentation> createInstrumentations() {
DeclarativeConfigProperties methods =
AgentInstrumentationConfig.get().getDeclarativeConfig("methods");
List<TypeInstrumentation> 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<TypeInstrumentation> parseConfigProperties() {
Map<String, Set<String>> 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<String> 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

View File

@ -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<ClassAndMethod, Void> INSTRUMENTER;
private static final Instrumenter<MethodAndType, Void> INSTRUMENTER;
private static final ClassLoader bootstrapLoader = new BootstrapLoader();
static {
CodeAttributesGetter<ClassAndMethod> codeAttributesGetter =
ClassAndMethod.codeAttributesGetter();
CodeAttributesGetter<MethodAndType> codeAttributesGetter =
new CodeAttributesGetter<MethodAndType>() {
@Nullable
@Override
public Class<?> getCodeClass(MethodAndType methodAndType) {
return methodAndType.getClassAndMethod().declaringClass();
}
@Nullable
@Override
public String getMethodName(MethodAndType methodAndType) {
return methodAndType.getClassAndMethod().methodName();
}
};
INSTRUMENTER =
Instrumenter.<ClassAndMethod, Void>builder(
Instrumenter.<MethodAndType, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
CodeSpanNameExtractor.create(codeAttributesGetter))
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
.buildInstrumenter(SpanKindExtractor.alwaysInternal());
.buildInstrumenter(MethodAndType::getSpanKind);
}
public static Instrumenter<ClassAndMethod, Void> instrumenter() {
public static Instrumenter<MethodAndType, Void> instrumenter() {
return INSTRUMENTER;
}

View File

@ -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<TypeInstrumentation> parseDeclarativeConfig(DeclarativeConfigProperties methods) {
return methods.getStructuredList("include", emptyList()).stream()
.flatMap(MethodsConfig::parseMethodInstrumentation)
.collect(Collectors.toList());
}
private static Stream<MethodInstrumentation> 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<SpanKind, Collection<String>> 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();
}
}

View File

@ -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;
}
}

View File

@ -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"))

View File

@ -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<String, String> getMap(String name, Map<String, String> defaultValue) {
return defaultValue;
}
@Nullable
@Override
public DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName) {
return null;
}
}

View File

@ -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")

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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")

View File

@ -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);
}

View File

@ -95,6 +95,7 @@ public abstract class InstrumentationExtension
}
@SafeVarargs
@SuppressWarnings("varargs")
public final void waitAndAssertMetrics(
String instrumentationName, Consumer<MetricAssert>... assertions) {
testRunner.waitAndAssertMetrics(instrumentationName, assertions);

View File

@ -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<LogRecordExporter> {
private static LogRecordExporter logRecordExporter;
@Override
public Class<LogRecordExporter> 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;
}
}

View File

@ -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<MetricExporter> {
private static MetricExporter metricExporter;
@Override
public Class<MetricExporter> 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;
}
}

View File

@ -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<SpanExporter> {
private static SpanExporter spanExporter;
@Override
public Class<SpanExporter> 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;
}
}

View File

@ -0,0 +1,4 @@
io.opentelemetry.instrumentation.testing.provider.TestSpanExporterComponentProvider
io.opentelemetry.instrumentation.testing.provider.TestMetricExporterComponentProvider
io.opentelemetry.instrumentation.testing.provider.TestLogRecordExporterComponentProvider

View File

@ -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")
}

View File

@ -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<byte[]> getSpanExportRequests() {
return spanExporter.getCollectedExportRequests();
}

View File

@ -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<LogRecordExporter> {
private static LogRecordExporter logRecordExporter;
@Override
public Class<LogRecordExporter> 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;
}
}

View File

@ -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<MetricExporter> {
private static MetricExporter metricExporter;
@Override
public Class<MetricExporter> 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;
}
}

View File

@ -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<SpanExporter> {
private static SpanExporter spanExporter;
@Override
public Class<SpanExporter> 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;
}
}

View File

@ -0,0 +1,4 @@
io.opentelemetry.javaagent.testing.provider.TestSpanExporterComponentProvider
io.opentelemetry.javaagent.testing.provider.TestMetricExporterComponentProvider
io.opentelemetry.javaagent.testing.provider.TestLogRecordExporterComponentProvider