From 385fb424db432c54907cece12ad936b96bf066d1 Mon Sep 17 00:00:00 2001 From: tbradellis Date: Tue, 14 Mar 2023 21:19:51 -0700 Subject: [PATCH] updates for spring-jms-3 jakarta changes formatting and license updates --- instrumentation/spring-jms-3/build.gradle | 29 ++++ .../springjms3/InboundWrapper.java | 51 +++++++ .../springjms3/JmsMetricUtil.java | 142 ++++++++++++++++++ .../listener/SessionAwareMessageListener.java | 30 ++++ settings.gradle | 1 + 5 files changed, 253 insertions(+) create mode 100644 instrumentation/spring-jms-3/build.gradle create mode 100644 instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/InboundWrapper.java create mode 100644 instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/JmsMetricUtil.java create mode 100644 instrumentation/spring-jms-3/src/main/java/org/springframework/jms/listener/SessionAwareMessageListener.java diff --git a/instrumentation/spring-jms-3/build.gradle b/instrumentation/spring-jms-3/build.gradle new file mode 100644 index 0000000000..7fc4232fa7 --- /dev/null +++ b/instrumentation/spring-jms-3/build.gradle @@ -0,0 +1,29 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("jakarta.jms:jakarta.jms-api:3.1.0") + implementation("org.springframework:spring-jms:6.0.6") + +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.spring-jms-3' } +} +verifyInstrumentation { + passesOnly ('org.springframework:spring-jms:[6.0.0,)'){ + implementation('jakarta.jms:jakarta.jms-api:3.1.0') + } + + excludeRegex 'org.springframework:spring-jms:6.0.0.(RC)[0-9]*$' + +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +site { + title 'Spring JMS' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/InboundWrapper.java b/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/InboundWrapper.java new file mode 100644 index 0000000000..43ed81412b --- /dev/null +++ b/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/InboundWrapper.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.springjms3; + +import com.newrelic.api.agent.ExtendedInboundHeaders; +import com.newrelic.api.agent.HeaderType; +import com.newrelic.api.agent.NewRelic; +import jakarta.jms.JMSException; +import jakarta.jms.Message; + +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; + +public class InboundWrapper extends ExtendedInboundHeaders { + private final Message delegate; + + public InboundWrapper(Message message) { + super(); + this.delegate = message; + } + + @Override + public String getHeader(String name) { + try { + return delegate.getStringProperty(name); + } catch (JMSException e) { + NewRelic.getAgent().getLogger().log(Level.FINE, e, "Error getting property ({0}) from JMS message.", name); + } + return null; + } + + @Override + public List getHeaders(String name) { + String result = getHeader(name); + if (result == null) { + return null; + } + return Collections.singletonList(result); + } + + @Override + public HeaderType getHeaderType() { + return HeaderType.MESSAGE; + } +} diff --git a/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/JmsMetricUtil.java b/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/JmsMetricUtil.java new file mode 100644 index 0000000000..d807dc6e64 --- /dev/null +++ b/instrumentation/spring-jms-3/src/main/java/com/nr/agent/instrumentation/springjms3/JmsMetricUtil.java @@ -0,0 +1,142 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.springjms3; + +import com.newrelic.agent.bridge.TracedMethod; +import com.newrelic.api.agent.DestinationType; +import com.newrelic.api.agent.MessageConsumeParameters; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.TransactionNamePriority; +import jakarta.jms.Destination; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Queue; +import jakarta.jms.TemporaryQueue; +import jakarta.jms.TemporaryTopic; +import jakarta.jms.Topic; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +public abstract class JmsMetricUtil { + private static final String CATEGORY = "Message"; + + /** + * Get the message properties as a map of String to Object + * + * @param msg the message holding 0 or more properties + * @return the map, may be empty, never null + */ + public static Map getMessageParameters(Message msg) { + + Map result = new LinkedHashMap<>(1); + + try { + Enumeration parameterEnum = msg.getPropertyNames(); + if (parameterEnum == null || !parameterEnum.hasMoreElements()) { + return Collections.emptyMap(); + } + + while (parameterEnum.hasMoreElements()) { + String key = (String) parameterEnum.nextElement(); + Object val = msg.getObjectProperty(key); + result.put(key, ((val == null) ? null : val.toString())); + } + } catch (JMSException e) { + NewRelic.getAgent().getLogger().log(Level.FINE, e, "Unable to capture JMS message property"); + } + + return result; + } + + public static void processConsume(Message message, TracedMethod tracer) { + try { + DestinationType destinationType = getDestinationType(message.getJMSDestination()); + String destinationName = getDestinationName(message.getJMSDestination()); + tracer.reportAsExternal(MessageConsumeParameters + .library("JMS") + .destinationType(destinationType) + .destinationName(destinationName) + .inboundHeaders(new InboundWrapper(message)) + .build()); + } catch (JMSException exception) { + NewRelic.getAgent().getLogger().log(Level.FINE, exception, + "Unable to record metrics for JMS message consume."); + } + } + + private static String getDestinationName(Destination destination) throws JMSException { + if (destination instanceof TemporaryQueue || destination instanceof TemporaryTopic) { + return "Temp"; + } + + if (destination instanceof Queue) { + Queue queue = (Queue) destination; + return queue.getQueueName(); + } + + if (destination instanceof Topic) { + Topic topic = (Topic) destination; + return topic.getTopicName(); + } + + return "Unknown"; + } + + private static DestinationType getDestinationType(Destination destination) { + if (destination instanceof TemporaryQueue) { + return DestinationType.TEMP_QUEUE; + } else if (destination instanceof TemporaryTopic) { + return DestinationType.TEMP_TOPIC; + } else if (destination instanceof Queue) { + return DestinationType.NAMED_QUEUE; + } else { + return DestinationType.NAMED_TOPIC; + } + } + + public static Message nameTransaction(Message msg) { + if (msg != null) { + try { + Destination dest = msg.getJMSDestination(); + if (dest instanceof Queue) { + Queue queue = (Queue) dest; + if (queue instanceof TemporaryQueue) { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_LOW, + false, CATEGORY, "JMS/Queue/Temp"); + } else { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, + false, CATEGORY, "JMS/Queue/Named", queue.getQueueName()); + } + } else if (dest instanceof Topic) { + Topic topic = (Topic) dest; + if (topic instanceof TemporaryTopic) { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_LOW, + false, CATEGORY, "JMS/Topic/Temp"); + } else { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, + false, CATEGORY, "JMS/Topic/Named", topic.getTopicName()); + } + } else { + NewRelic.getAgent().getLogger().log(Level.FINE, + "Error naming JMS transaction: Invalid Message Type."); + } + } catch (JMSException e) { + NewRelic.getAgent().getLogger().log(Level.FINE, e, "Error naming JMS transaction"); + } + } else { + // Not a useful transaction. + NewRelic.getAgent().getTransaction().ignore(); + } + return msg; + } + +} diff --git a/instrumentation/spring-jms-3/src/main/java/org/springframework/jms/listener/SessionAwareMessageListener.java b/instrumentation/spring-jms-3/src/main/java/org/springframework/jms/listener/SessionAwareMessageListener.java new file mode 100644 index 0000000000..70f8363bdb --- /dev/null +++ b/instrumentation/spring-jms-3/src/main/java/org/springframework/jms/listener/SessionAwareMessageListener.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.springframework.jms.listener; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.springjms3.JmsMetricUtil; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; + +@Weave(type = MatchType.Interface, originalName = "org.springframework.jms.listener.SessionAwareMessageListener" ) +public abstract class SessionAwareMessageListener { + + @Trace(dispatcher = true) + public void onMessage(M message, Session session) throws JMSException { + JmsMetricUtil.nameTransaction(message); + JmsMetricUtil.processConsume(message, AgentBridge.getAgent().getTracedMethod()); + AgentBridge.getAgent().getTransaction().saveMessageParameters(JmsMetricUtil.getMessageParameters(message)); + Weaver.callOriginal(); + } +} diff --git a/settings.gradle b/settings.gradle index 89d92ff838..4555d7d562 100644 --- a/settings.gradle +++ b/settings.gradle @@ -314,6 +314,7 @@ include 'instrumentation:spring-4.2.0' include 'instrumentation:spring-4.3.0' include 'instrumentation:spring-aop-2' include 'instrumentation:spring-jms-2' +include 'instrumentation:spring-jms-3' include 'instrumentation:spring-ws-2.0' include 'instrumentation:spring-webflux-5.0.0' include 'instrumentation:spring-webflux-5.1.0'