-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Spring Integration javaagent instrumentation #3295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
66f5835
4e4de18
5bfe159
84bfb99
f9b2577
1e2803b
b5fa99f
5045e54
3bf3fbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.api.tracer; | ||
|
|
||
| import io.opentelemetry.api.trace.Span; | ||
| import io.opentelemetry.api.trace.SpanKind; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.ContextKey; | ||
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
|
||
| /** | ||
| * This class encapsulates the context key for storing the current {@link SpanKind#CONSUMER} span in | ||
| * the {@link Context}. | ||
| */ | ||
| public final class ConsumerSpan { | ||
| // Keeps track of the consumer span for the current trace. | ||
| private static final ContextKey<Span> KEY = | ||
| ContextKey.named("opentelemetry-traces-consumer-span-key"); | ||
|
|
||
| /** | ||
| * Returns true when a {@link SpanKind#CONSUMER} span is present in the passed {@code context}. | ||
| */ | ||
| public static boolean exists(Context context) { | ||
| return fromContextOrNull(context) != null; | ||
| } | ||
|
|
||
| /** | ||
| * Returns span of type {@link SpanKind#CONSUMER} from the given context or {@code null} if not | ||
| * found. | ||
| */ | ||
| @Nullable | ||
| public static Span fromContextOrNull(Context context) { | ||
| return context.get(KEY); | ||
| } | ||
|
|
||
| public static Context with(Context context, Span consumerSpan) { | ||
| return context.with(KEY, consumerSpan); | ||
| } | ||
|
|
||
| private ConsumerSpan() {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| apply from: "$rootDir/gradle/instrumentation.gradle" | ||
|
|
||
| ext { | ||
| // context "leak" here is intentional: spring-integration instrumentation will always override | ||
| // "local" span context with one extracted from the incoming message when it decides to start a | ||
| // CONSUMER span | ||
| failOnContextLeak = false | ||
| } | ||
|
Comment on lines
+3
to
+8
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a consequence of always using the context extracted from the incoming message; we completely ignore the "local" current context, but that's fine in that case.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... our
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's called for all spans, span kind does not matter.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but the logic of not failing the test should be the same for all |
||
|
|
||
| muzzle { | ||
| pass { | ||
| group = "org.springframework.integration" | ||
| module = "spring-integration-core" | ||
| versions = "[4.1.0.RELEASE,)" | ||
| assertInverse = true | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation project(':instrumentation:spring:spring-integration-4.1:library') | ||
|
|
||
| library 'org.springframework.integration:spring-integration-core:4.1.0.RELEASE' | ||
|
|
||
| testInstrumentation project(':instrumentation:rabbitmq-2.7:javaagent') | ||
|
|
||
| testImplementation project(':instrumentation:spring:spring-integration-4.1:testing') | ||
|
|
||
| testLibrary "org.springframework.boot:spring-boot-starter-test:1.5.22.RELEASE" | ||
| testLibrary "org.springframework.boot:spring-boot-starter:1.5.22.RELEASE" | ||
| testLibrary "org.springframework.cloud:spring-cloud-stream:2.2.1.RELEASE" | ||
| testLibrary "org.springframework.cloud:spring-cloud-stream-binder-rabbit:2.2.1.RELEASE" | ||
|
|
||
| testImplementation "javax.servlet:javax.servlet-api:3.1.0" | ||
| } | ||
|
|
||
| test { | ||
| filter { | ||
| excludeTestsMatching 'SpringIntegrationAndRabbitTest' | ||
| } | ||
| jvmArgs "-Dotel.instrumentation.rabbitmq.enabled=false" | ||
| } | ||
| test.finalizedBy(tasks.register("testWithRabbitInstrumentation", Test) { | ||
| filter { | ||
| includeTestsMatching 'SpringIntegrationAndRabbitTest' | ||
| } | ||
| jvmArgs "-Dotel.instrumentation.rabbitmq.enabled=true" | ||
| }) | ||
|
|
||
| tasks.withType(Test).configureEach { | ||
| systemProperty "testLatestDeps", testLatestDeps | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.javaagent.instrumentation.spring.integration; | ||
|
|
||
| import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass; | ||
| import static io.opentelemetry.javaagent.extension.matcher.ClassLoaderMatcher.hasClassesNamed; | ||
| import static net.bytebuddy.matcher.ElementMatchers.isMethod; | ||
| import static net.bytebuddy.matcher.ElementMatchers.named; | ||
| import static net.bytebuddy.matcher.ElementMatchers.takesArgument; | ||
| import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; | ||
|
|
||
| import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
| import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; | ||
| import net.bytebuddy.asm.Advice; | ||
| import net.bytebuddy.description.type.TypeDescription; | ||
| import net.bytebuddy.matcher.ElementMatcher; | ||
| import org.springframework.beans.factory.config.BeanDefinition; | ||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||
| import org.springframework.beans.factory.support.BeanDefinitionRegistry; | ||
| import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; | ||
|
|
||
| public class ApplicationContextInstrumentation implements TypeInstrumentation { | ||
| @Override | ||
| public ElementMatcher<ClassLoader> classLoaderOptimization() { | ||
| return hasClassesNamed("org.springframework.context.support.AbstractApplicationContext"); | ||
| } | ||
|
|
||
| @Override | ||
| public ElementMatcher<TypeDescription> typeMatcher() { | ||
| return extendsClass(named("org.springframework.context.support.AbstractApplicationContext")); | ||
| } | ||
|
|
||
| @Override | ||
| public void transform(TypeTransformer transformer) { | ||
| transformer.applyAdviceToMethod( | ||
| isMethod() | ||
| .and(named("postProcessBeanFactory")) | ||
| .and( | ||
| takesArgument( | ||
| 0, | ||
| named( | ||
| "org.springframework.beans.factory.config.ConfigurableListableBeanFactory"))), | ||
| ApplicationContextInstrumentation.class.getName() + "$PostProcessBeanFactoryAdvice"); | ||
| } | ||
|
|
||
| public static class PostProcessBeanFactoryAdvice { | ||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||
| public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) { | ||
| if (beanFactory instanceof BeanDefinitionRegistry | ||
| && !beanFactory.containsBean("otelGlobalChannelInterceptor")) { | ||
|
|
||
| BeanDefinition globalChannelInterceptorBean = | ||
| genericBeanDefinition(GlobalChannelInterceptorWrapper.class) | ||
| .addConstructorArgValue(SpringIntegrationSingletons.interceptor()) | ||
| .addPropertyValue("patterns", SpringIntegrationSingletons.patterns()) | ||
| .getBeanDefinition(); | ||
|
|
||
| ((BeanDefinitionRegistry) beanFactory) | ||
| .registerBeanDefinition("otelGlobalChannelInterceptor", globalChannelInterceptorBean); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.javaagent.instrumentation.spring.integration; | ||
|
|
||
| import static java.util.Collections.singletonList; | ||
|
|
||
| import com.google.auto.service.AutoService; | ||
| import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
| import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
| import java.util.List; | ||
|
|
||
| @AutoService(InstrumentationModule.class) | ||
| public class SpringIntegrationInstrumentationModule extends InstrumentationModule { | ||
| public SpringIntegrationInstrumentationModule() { | ||
| super("spring-integration", "spring-integration-4.1"); | ||
| } | ||
|
|
||
| @Override | ||
| public List<TypeInstrumentation> typeInstrumentations() { | ||
| return singletonList(new ApplicationContextInstrumentation()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SERVER and CONSUMER spans are now both treated as "local-root" type of spans; by default, you can't have both of them in a single process, in a single trace, since they both represent the receiving side.
This is a pretty significant change, please tell me WDYT.