Skip to content

Commit

Permalink
Support Spring JMS 6.0 (#7438)
Browse files Browse the repository at this point in the history
Part of #7203

The instrumentation is 100% copy-pasted, tests are rewritten from
scratch in Java because of way too many class name changes.

Depends on
#7418

Co-authored-by: Trask Stalnaker <[email protected]>
  • Loading branch information
Mateusz Rzeszutek and trask authored Jan 12, 2023
1 parent 18cffb7 commit fb5cf9f
Show file tree
Hide file tree
Showing 21 changed files with 593 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ muzzle {
pass {
group.set("org.springframework")
module.set("spring-jms")
versions.set("[2.0,)")
versions.set("[2.0,6)")
extraDependency("javax.jms:jms-api:1.1-rev-1")
excludeInstrumentationName("jms")
assertInverse.set(true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,30 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms;
package io.opentelemetry.javaagent.instrumentation.spring.jms.v2_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
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;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class SpringJmsInstrumentationModule extends InstrumentationModule {

public SpringJmsInstrumentationModule() {
super("spring-jms", "spring-jms-2.0");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// introduced in 2.0, removed in 6.0
return hasClassesNamed("org.springframework.jms.remoting.JmsInvokerProxyFactoryBean");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new SpringJmsMessageListenerInstrumentation());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms;
package io.opentelemetry.javaagent.instrumentation.spring.jms.v2_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.spring.jms.SpringJmsSingletons.listenerInstrumenter;
import static io.opentelemetry.javaagent.instrumentation.spring.jms.v2_0.SpringJmsSingletons.listenerInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms;
package io.opentelemetry.javaagent.instrumentation.spring.jms.v2_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.springframework")
module.set("spring-jms")
versions.set("[6.0.0,)")
extraDependency("jakarta.jms:jakarta.jms-api:3.0.0")
excludeInstrumentationName("jms")
assertInverse.set(true)
}
}

dependencies {
implementation(project(":instrumentation:jms:jms-common:javaagent"))
implementation(project(":instrumentation:jms:jms-3.0:javaagent"))

library("org.springframework:spring-jms:6.0.0")
compileOnly("jakarta.jms:jakarta.jms-api:3.0.0")

testInstrumentation(project(":instrumentation:jms:jms-3.0:javaagent"))

testImplementation("org.apache.activemq:artemis-jakarta-client:2.27.1")

testLibrary("org.springframework.boot:spring-boot-starter-test:3.0.0")
testLibrary("org.springframework.boot:spring-boot-starter:3.0.0")
}

// spring 6 requires java 17
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}

tasks {
test {
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)

jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Collections.singletonList;
import static net.bytebuddy.matcher.ElementMatchers.not;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class SpringJmsInstrumentationModule extends InstrumentationModule {

public SpringJmsInstrumentationModule() {
super("spring-jms", "spring-jms-6.0");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// removed in 6.0
return not(hasClassesNamed("org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"));
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new SpringJmsMessageListenerInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0.SpringJmsSingletons.listenerInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
import io.opentelemetry.javaagent.instrumentation.jms.v3_0.JakartaMessageAdapter;
import jakarta.jms.Message;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class SpringJmsMessageListenerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("org.springframework.jms.listener.SessionAwareMessageListener");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(
named("org.springframework.jms.listener.SessionAwareMessageListener"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("onMessage")
.and(isPublic())
.and(takesArguments(2))
.and(takesArgument(0, named("jakarta.jms.Message"))),
SpringJmsMessageListenerInstrumentation.class.getName() + "$MessageListenerAdvice");
}

@SuppressWarnings("unused")
public static class MessageListenerAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) Message message,
@Advice.Local("otelRequest") MessageWithDestination request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

Context parentContext = Java8BytecodeBridge.currentContext();
request = MessageWithDestination.create(JakartaMessageAdapter.create(message), null);

if (!listenerInstrumenter().shouldStart(parentContext, request)) {
return;
}

context = listenerInstrumenter().start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Local("otelRequest") MessageWithDestination request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
if (scope == null) {
return;
}
scope.close();
listenerInstrumenter().end(context, request, null, throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
import io.opentelemetry.javaagent.instrumentation.jms.JmsInstrumenterFactory;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;

public final class SpringJmsSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-jms-6.0";

private static final Instrumenter<MessageWithDestination, Void> LISTENER_INSTRUMENTER =
new JmsInstrumenterFactory(GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME)
.setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders())
.createConsumerProcessInstrumenter();

public static Instrumenter<MessageWithDestination, Void> listenerInstrumenter() {
return LISTENER_INSTRUMENTER;
}

private SpringJmsSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import jakarta.jms.ConnectionFactory;
import java.util.concurrent.CompletableFuture;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;

abstract class AbstractConfig {

@Bean
public ConnectionFactory connectionFactory(@Value("${test.broker-url}") String artemisBrokerUrl) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(artemisBrokerUrl);
connectionFactory.setUser("test");
connectionFactory.setPassword("test");
return connectionFactory;
}

@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
return factory;
}

@Bean
public CompletableFuture<String> receivedMessage() {
return new CompletableFuture<>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.jms.annotation.EnableJms;

@ComponentScan
@EnableJms
public class AnnotatedListenerConfig extends AbstractConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan;

import java.util.concurrent.CompletableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class AnnotatedTestListener {

private final CompletableFuture<String> receivedMessage;

@Autowired
public AnnotatedTestListener(CompletableFuture<String> receivedMessage) {
this.receivedMessage = receivedMessage;
}

@JmsListener(destination = "spring-jms-listener")
public void receiveMessage(String message) {
runWithSpan("consumer", () -> receivedMessage.complete(message));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.jms.v6_0;

import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan;

import jakarta.jms.TextMessage;
import java.util.concurrent.CompletableFuture;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListenerConfigurer;
import org.springframework.jms.config.JmsListenerEndpoint;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.MessageListenerContainer;
import org.springframework.jms.listener.SessionAwareMessageListener;

@EnableJms
public class ManualListenerConfig extends AbstractConfig {

@Bean
public JmsListenerConfigurer jmsListenerConfigurer(CompletableFuture<String> receivedMessage) {
return registrar ->
registrar.registerEndpoint(
new JmsListenerEndpoint() {
@Override
public String getId() {
return "testid";
}

@Override
public void setupListenerContainer(MessageListenerContainer listenerContainer) {
AbstractMessageListenerContainer container =
(AbstractMessageListenerContainer) listenerContainer;
container.setDestinationName("spring-jms-listener");
container.setupMessageListener(
(SessionAwareMessageListener<TextMessage>)
(message, session) ->
runWithSpan(
"consumer", () -> receivedMessage.complete(message.getText())));
}
});
}
}
Loading

0 comments on commit fb5cf9f

Please sign in to comment.