You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2024/12/07 21:02:50 UTC
(logging-log4j2) branch 2.x updated: Add a Jakarta JMS Appender #2295 (#3247)
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/2.x by this push:
new 43a0e29781 Add a Jakarta JMS Appender #2295 (#3247)
43a0e29781 is described below
commit 43a0e29781d1ca0be9306124d360ab451e531ddd
Author: Gary Gregory <ga...@users.noreply.github.com>
AuthorDate: Sat Dec 7 16:02:44 2024 -0500
Add a Jakarta JMS Appender #2295 (#3247)
---
.github/dependabot.yaml | 1 +
.../log4j/core/appender/mom/JmsAppender.java | 11 +-
.../log4j/core/appender/mom/JmsManager.java | 6 +-
.../log4j/core/appender/mom/package-info.java | 2 +-
.../.log4j-plugin-processing-activator | 1 +
log4j-jakarta-jms/pom.xml | 71 ++++++++
.../core/appender/mom/jakarta}/JmsAppender.java | 60 ++-----
.../core/appender/mom/jakarta}/JmsManager.java | 51 +++---
.../core/appender/mom/jakarta}/package-info.java | 8 +-
.../core/appender/mom/jakarta/JmsAppenderTest.java | 196 +++++++++++++++++++++
.../src/test/resources/JmsJakartaAppenderTest.xml | 46 +++++
pom.xml | 7 +
.../.2.x.x/2295_add_JMS_Jakarta_Appender.xml | 8 +
.../ROOT/pages/manual/appenders/message-queue.adoc | 12 +-
.../antora/modules/ROOT/pages/manual/messages.adoc | 2 +-
15 files changed, 385 insertions(+), 97 deletions(-)
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index ee3d096332..6471296876 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -59,6 +59,7 @@ updates:
- "/log4j-docker"
- "/log4j-fuzz-test"
- "/log4j-iostreams"
+ - "/log4j-jakarta-jms"
- "/log4j-jakarta-smtp"
- "/log4j-jakarta-web"
- "/log4j-jcl"
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
index 8d2c87c4b8..b6df7dd08d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
@@ -37,11 +37,14 @@ import org.apache.logging.log4j.core.config.plugins.validation.constraints.Requi
import org.apache.logging.log4j.core.net.JndiManager;
/**
- * Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However,
- * configurations set up for the 2.0 version of the JMS appenders will still work.
+ * Javax JMS Appender plugin. This Appender replaces the previous split classes.
+ * Configurations set up for the 2.0 version of the JMS appenders will still work.
+ *
+ * @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsAppender}.
*/
-@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
-@PluginAliases({"JMSQueue", "JMSTopic"})
+@Deprecated
+@Plugin(name = "JMS-Javax", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
+@PluginAliases({"JMS", "JMSQueue", "JMSTopic"})
public class JmsAppender extends AbstractAppender {
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
index 3d2872a9fd..178d1f428a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
@@ -42,10 +42,12 @@ import org.apache.logging.log4j.status.StatusLogger;
* Consider this class <b>private</b>; it is only <b>public</b> for access by integration tests.
*
* <p>
- * JMS connection and session manager. Can be used to access MessageProducer, MessageConsumer, and Message objects
- * involving a configured ConnectionFactory and Destination.
+ * JMS connection and destination manager. Uses a MessageProducer to send log events to a JMS Destination.
* </p>
+ *
+ * @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsManager}.
*/
+@Deprecated
public class JmsManager extends AbstractManager {
public static class JmsManagerConfiguration {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java
index 6ed35287fd..c12d834375 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java
@@ -21,7 +21,7 @@
* @since 2.1
*/
@Export
-@Version("2.20.1")
+@Version("2.25.0")
package org.apache.logging.log4j.core.appender.mom;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-jakarta-jms/.log4j-plugin-processing-activator b/log4j-jakarta-jms/.log4j-plugin-processing-activator
new file mode 100644
index 0000000000..ba133f3696
--- /dev/null
+++ b/log4j-jakarta-jms/.log4j-plugin-processing-activator
@@ -0,0 +1 @@
+This file is here to activate the `plugin-processing` Maven profile.
diff --git a/log4j-jakarta-jms/pom.xml b/log4j-jakarta-jms/pom.xml
new file mode 100644
index 0000000000..57b7b2c14b
--- /dev/null
+++ b/log4j-jakarta-jms/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${revision}</version>
+ <relativePath>../log4j-parent</relativePath>
+ </parent>
+ <artifactId>log4j-jakarta-jms</artifactId>
+ <name>Apache Log4j Jakarta JMS</name>
+ <description>Apache Log4j Java Message Service (JMS), version for Jakarta.</description>
+ <properties>
+ <!-- This is a new module in 2.25.0, remove this property after the release. -->
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ <!-- OSGi and JPMS options -->
+ <bnd-module-name>org.apache.logging.log4j.jakarta.jms</bnd-module-name>
+ <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>jakarta.jms</groupId>
+ <artifactId>jakarta.jms-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppender.java
similarity index 80%
copy from log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
copy to log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppender.java
index 8d2c87c4b8..661cfd0329 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
+++ b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppender.java
@@ -14,19 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.core.appender.mom;
+package org.apache.logging.log4j.core.appender.mom.jakarta;
+import jakarta.jms.JMSException;
import java.io.Serializable;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
-import javax.jms.JMSException;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AbstractManager;
-import org.apache.logging.log4j.core.appender.mom.JmsManager.JmsManagerConfiguration;
+import org.apache.logging.log4j.core.appender.mom.jakarta.JmsManager.JmsManagerConfiguration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
@@ -37,14 +37,12 @@ import org.apache.logging.log4j.core.config.plugins.validation.constraints.Requi
import org.apache.logging.log4j.core.net.JndiManager;
/**
- * Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However,
- * configurations set up for the 2.0 version of the JMS appenders will still work.
+ * Jakarta JMS Appender plugin for both queues and topics.
*/
-@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
-@PluginAliases({"JMSQueue", "JMSTopic"})
-public class JmsAppender extends AbstractAppender {
+@Plugin(name = "JMS-Jakarta", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
+public final class JmsAppender extends AbstractAppender {
- public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
+ public static final class Builder extends AbstractAppender.Builder<Builder>
implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {
public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;
@@ -65,12 +63,12 @@ public class JmsAppender extends AbstractAppender {
private String securityCredentials;
@PluginBuilderAttribute
- @Required(message = "A javax.jms.ConnectionFactory JNDI name must be specified")
+ @Required(message = "A jakarta.jms.ConnectionFactory JNDI name must be specified")
private String factoryBindingName;
@PluginBuilderAttribute
@PluginAliases({"queueBindingName", "topicBindingName"})
- @Required(message = "A javax.jms.Destination JNDI name must be specified")
+ @Required(message = "A jakarta.jms.Destination JNDI name must be specified")
private String destinationBindingName;
@PluginBuilderAttribute
@@ -104,7 +102,7 @@ public class JmsAppender extends AbstractAppender {
destinationBindingName,
userName,
password,
- false,
+ immediateFail,
reconnectIntervalMillis);
actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration);
}
@@ -156,15 +154,6 @@ public class JmsAppender extends AbstractAppender {
return this;
}
- /**
- * @deprecated Use setPassword(char[])
- */
- @Deprecated
- public Builder setPassword(final String password) {
- this.password = password == null ? null : password.toCharArray();
- return this;
- }
-
public Builder setProviderUrl(final String providerUrl) {
this.providerUrl = providerUrl;
return this;
@@ -190,15 +179,6 @@ public class JmsAppender extends AbstractAppender {
return this;
}
- /**
- * @deprecated Use {@link #setUserName(String)}.
- */
- @Deprecated
- public Builder setUsername(final String username) {
- this.userName = username;
- return this;
- }
-
public Builder setUserName(final String userName) {
this.userName = userName;
return this;
@@ -230,7 +210,7 @@ public class JmsAppender extends AbstractAppender {
*
* @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
*/
- protected JmsAppender(
+ private JmsAppender(
final String name,
final Filter filter,
final Layout<? extends Serializable> layout,
@@ -242,24 +222,6 @@ public class JmsAppender extends AbstractAppender {
this.manager = manager;
}
- /**
- * Constructs a new instance.
- *
- * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
- * @deprecated Use {@link #JmsAppender(String, Filter, Layout, boolean, Property[], JmsManager)}.
- */
- @Deprecated
- protected JmsAppender(
- final String name,
- final Filter filter,
- final Layout<? extends Serializable> layout,
- final boolean ignoreExceptions,
- final JmsManager manager)
- throws JMSException {
- super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
- this.manager = manager;
- }
-
@Override
public void append(final LogEvent event) {
this.manager.send(event, toSerializable(event));
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsManager.java
similarity index 92%
copy from log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
copy to log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsManager.java
index 3d2872a9fd..81b915dafe 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
+++ b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsManager.java
@@ -14,21 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.core.appender.mom;
-
+package org.apache.logging.log4j.core.appender.mom.jakarta;
+
+import jakarta.jms.Connection;
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.Destination;
+import jakarta.jms.JMSException;
+import jakarta.jms.MapMessage;
+import jakarta.jms.Message;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.Session;
import java.io.Serializable;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import javax.jms.Connection;
-import javax.jms.ConnectionFactory;
-import javax.jms.Destination;
-import javax.jms.JMSException;
-import javax.jms.MapMessage;
-import javax.jms.Message;
-import javax.jms.MessageConsumer;
-import javax.jms.MessageProducer;
-import javax.jms.Session;
import javax.naming.NamingException;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractManager;
@@ -46,9 +45,9 @@ import org.apache.logging.log4j.status.StatusLogger;
* involving a configured ConnectionFactory and Destination.
* </p>
*/
-public class JmsManager extends AbstractManager {
+public final class JmsManager extends AbstractManager {
- public static class JmsManagerConfiguration {
+ public static final class JmsManagerConfiguration {
private final Properties jndiProperties;
private final String connectionFactoryName;
private final String destinationName;
@@ -121,7 +120,7 @@ public class JmsManager extends AbstractManager {
}
}
- private static class JmsManagerFactory implements ManagerFactory<JmsManager, JmsManagerConfiguration> {
+ private static final class JmsManagerFactory implements ManagerFactory<JmsManager, JmsManagerConfiguration> {
@Override
public JmsManager createManager(final String name, final JmsManagerConfiguration data) {
@@ -378,13 +377,13 @@ public class JmsManager extends AbstractManager {
* @return A new JMS message containing the provided object.
* @throws JMSException if the JMS provider fails to create this message due to some internal error.
*/
- public Message createMessage(final Serializable object) throws JMSException {
+ private Message createMessage(final Serializable object) throws JMSException {
if (object instanceof String) {
- return this.session.createTextMessage((String) object);
+ return session.createTextMessage((String) object);
} else if (object instanceof org.apache.logging.log4j.message.MapMessage) {
- return map((org.apache.logging.log4j.message.MapMessage<?, ?>) object, this.session.createMapMessage());
+ return map((org.apache.logging.log4j.message.MapMessage<?, ?>) object, session.createMapMessage());
}
- return this.session.createObjectMessage(object);
+ return session.createObjectMessage(object);
}
private void createMessageAndSend(final LogEvent event, final Serializable serializable) throws JMSException {
@@ -393,16 +392,6 @@ public class JmsManager extends AbstractManager {
messageProducer.send(message);
}
- /**
- * Creates a MessageConsumer on this Destination using the current Session.
- *
- * @return A MessageConsumer on this Destination.
- * @throws JMSException if the session fails to create a consumer due to some internal error.
- */
- public MessageConsumer createMessageConsumer() throws JMSException {
- return this.session.createConsumer(this.destination);
- }
-
/**
* Creates a MessageProducer on this Destination using the current Session.
*
@@ -413,7 +402,7 @@ public class JmsManager extends AbstractManager {
* @return A MessageProducer on this Destination.
* @throws JMSException if the session fails to create a MessageProducer due to some internal error.
*/
- public MessageProducer createMessageProducer(final Session session, final Destination destination)
+ private MessageProducer createMessageProducer(final Session session, final Destination destination)
throws JMSException {
return session.createProducer(destination);
}
@@ -438,7 +427,7 @@ public class JmsManager extends AbstractManager {
}
<T> T lookup(final String destinationName) throws NamingException {
- return this.jndiManager.lookup(destinationName);
+ return jndiManager.lookup(destinationName);
}
private MapMessage map(
@@ -470,7 +459,7 @@ public class JmsManager extends AbstractManager {
closed &= closeMessageProducer();
closed &= closeSession();
closed &= closeConnection();
- return closed && this.jndiManager.stop(timeout, timeUnit);
+ return closed && jndiManager.stop(timeout, timeUnit);
}
void send(final LogEvent event, final Serializable serializable) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/package-info.java
similarity index 87%
copy from log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java
copy to log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/package-info.java
index 6ed35287fd..a5651b6275 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java
+++ b/log4j-jakarta-jms/src/main/java/org/apache/logging/log4j/core/appender/mom/jakarta/package-info.java
@@ -16,13 +16,11 @@
*/
/**
- * Message oriented middleware appenders.
- *
- * @since 2.1
+ * Jakarta-based JMS Appender.
*/
@Export
-@Version("2.20.1")
-package org.apache.logging.log4j.core.appender.mom;
+@Version("2.25.0")
+package org.apache.logging.log4j.core.appender.mom.jakarta;
import org.osgi.annotation.bundle.Export;
import org.osgi.annotation.versioning.Version;
diff --git a/log4j-jakarta-jms/src/test/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppenderTest.java b/log4j-jakarta-jms/src/test/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppenderTest.java
new file mode 100644
index 0000000000..5d730fc47b
--- /dev/null
+++ b/log4j-jakarta-jms/src/test/java/org/apache/logging/log4j/core/appender/mom/jakarta/JmsAppenderTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.appender.mom.jakarta;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+
+import jakarta.jms.Connection;
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.Destination;
+import jakarta.jms.MapMessage;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.ObjectMessage;
+import jakarta.jms.Session;
+import jakarta.jms.TextMessage;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.test.categories.Appenders;
+import org.apache.logging.log4j.core.test.junit.JndiRule;
+import org.apache.logging.log4j.core.test.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.message.StringMapMessage;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+@Category(Appenders.Jms.class)
+public class JmsAppenderTest {
+
+ private static final String CONNECTION_FACTORY_NAME = "jms/connectionFactory";
+ private static final String QUEUE_FACTORY_NAME = "jms/queues";
+ private static final String TOPIC_FACTORY_NAME = "jms/topics";
+ private static final String DESTINATION_NAME = "jms/destination";
+ private static final String DESTINATION_NAME_ML = "jms/destination-ml";
+ private static final String QUEUE_NAME = "jms/queue";
+ private static final String TOPIC_NAME = "jms/topic";
+ private static final String LOG_MESSAGE = "Hello, world!";
+
+ private final ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
+ private final Connection connection = mock(Connection.class);
+ private final Session session = mock(Session.class);
+ private final Destination destination = mock(Destination.class);
+ private final Destination destinationMl = mock(Destination.class);
+ private final MessageProducer messageProducer = mock(MessageProducer.class);
+ private final MessageProducer messageProducerMl = mock(MessageProducer.class);
+ private final TextMessage textMessage = mock(TextMessage.class);
+ private final ObjectMessage objectMessage = mock(ObjectMessage.class);
+ private final MapMessage mapMessage = mock(MapMessage.class);
+
+ private final JndiRule jndiRule = new JndiRule(createBindings());
+ private final LoggerContextRule ctx = new LoggerContextRule("JmsJakartaAppenderTest.xml");
+
+ @Rule
+ public RuleChain rules = RuleChain.outerRule(jndiRule).around(ctx);
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ System.clearProperty("log4j2.enableJndiJms");
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ System.setProperty("log4j2.enableJndiJms", "true");
+ }
+
+ public JmsAppenderTest() throws Exception {
+ // this needs to set up before LoggerContextRule
+ given(connectionFactory.createConnection()).willReturn(connection);
+ given(connectionFactory.createConnection(anyString(), anyString())).willThrow(IllegalArgumentException.class);
+ given(connection.createSession(eq(false), eq(Session.AUTO_ACKNOWLEDGE))).willReturn(session);
+ given(session.createProducer(eq(destination))).willReturn(messageProducer);
+ given(session.createProducer(eq(destinationMl))).willReturn(messageProducerMl);
+ given(session.createTextMessage(anyString())).willReturn(textMessage);
+ given(session.createObjectMessage(isA(Serializable.class))).willReturn(objectMessage);
+ given(session.createMapMessage()).willReturn(mapMessage);
+ }
+
+ private Map<String, Object> createBindings() {
+ final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
+ map.put(CONNECTION_FACTORY_NAME, connectionFactory);
+ map.put(DESTINATION_NAME, destination);
+ map.put(DESTINATION_NAME_ML, destinationMl);
+ map.put(QUEUE_FACTORY_NAME, connectionFactory);
+ map.put(QUEUE_NAME, destination);
+ map.put(TOPIC_FACTORY_NAME, connectionFactory);
+ map.put(TOPIC_NAME, destination);
+ return map;
+ }
+
+ private Log4jLogEvent createLogEvent() {
+ return createLogEvent(new SimpleMessage(LOG_MESSAGE));
+ }
+
+ private Log4jLogEvent createLogEvent(final Message message) {
+ // @formatter:off
+ return Log4jLogEvent.newBuilder()
+ .setLoggerName(JmsAppenderTest.class.getName())
+ .setLoggerFqcn(JmsAppenderTest.class.getName())
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+ // @formatter:on
+ }
+
+ private Log4jLogEvent createMapMessageLogEvent() {
+ final StringMapMessage mapMessage = new StringMapMessage();
+ return createLogEvent(mapMessage.with("testMesage", LOG_MESSAGE));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // we have 4 appenders all connecting to the same ConnectionFactory
+ then(connection).should(times(4)).start();
+ }
+
+ @Test
+ public void testAppendToQueue() throws Exception {
+ final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsAppender");
+ final LogEvent event = createLogEvent();
+ appender.append(event);
+ then(session).should().createTextMessage(eq(LOG_MESSAGE));
+ then(textMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducer).should().send(textMessage);
+ appender.stop();
+ then(session).should().close();
+ then(connection).should().close();
+ }
+
+ @Test
+ public void testAppendToQueueWithMessageLayout() throws Exception {
+ final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsAppender-MessageLayout");
+ final LogEvent event = createMapMessageLogEvent();
+ appender.append(event);
+ then(session).should().createMapMessage();
+ then(mapMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducerMl).should().send(mapMessage);
+ appender.stop();
+ then(session).should().close();
+ then(connection).should().close();
+ }
+
+ @Test
+ public void testJmsQueueAppenderCompatibility() throws Exception {
+ final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsQueueAppender");
+ final LogEvent expected = createLogEvent();
+ appender.append(expected);
+ then(session).should().createObjectMessage(eq(expected));
+ then(objectMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducer).should().send(objectMessage);
+ appender.stop();
+ then(session).should().close();
+ then(connection).should().close();
+ }
+
+ @Test
+ public void testJmsTopicAppenderCompatibility() throws Exception {
+ final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsTopicAppender");
+ final LogEvent expected = createLogEvent();
+ appender.append(expected);
+ then(session).should().createObjectMessage(eq(expected));
+ then(objectMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducer).should().send(objectMessage);
+ appender.stop();
+ then(session).should().close();
+ then(connection).should().close();
+ }
+}
diff --git a/log4j-jakarta-jms/src/test/resources/JmsJakartaAppenderTest.xml b/log4j-jakarta-jms/src/test/resources/JmsJakartaAppenderTest.xml
new file mode 100644
index 0000000000..e1d660d1c1
--- /dev/null
+++ b/log4j-jakarta-jms/src/test/resources/JmsJakartaAppenderTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<Configuration name="JmsAppenderTest" status="OFF">
+ <Appenders>
+ <JMS-Jakarta name="JmsAppender"
+ factoryBindingName="jms/connectionFactory"
+ destinationBindingName="jms/destination">
+ <PatternLayout pattern="%m"/>
+ </JMS-Jakarta>
+ <JMS-Jakarta name="JmsAppender-MessageLayout"
+ factoryBindingName="jms/connectionFactory"
+ destinationBindingName="jms/destination-ml">
+ <MessageLayout />
+ </JMS-Jakarta>
+ <JMS-Jakarta name="JmsQueueAppender"
+ factoryBindingName="jms/queues"
+ queueBindingName="jms/queue">
+ <SerializedLayout/>
+ </JMS-Jakarta>
+ <JMS-Jakarta name="JmsTopicAppender"
+ factoryBindingName="jms/topics"
+ topicBindingName="jms/topic">
+ <SerializedLayout/>
+ </JMS-Jakarta>
+ </Appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="JmsQueueAppender"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/pom.xml b/pom.xml
index cc0fed3561..74394de40c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -249,6 +249,7 @@
<module>log4j-docker</module>
<module>log4j-fuzz-test</module>
<module>log4j-iostreams</module>
+ <module>log4j-jakarta-jms</module>
<module>log4j-jakarta-smtp</module>
<module>log4j-jakarta-web</module>
<module>log4j-jcl</module>
@@ -446,6 +447,12 @@
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jakarta-jms</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jakarta-smtp</artifactId>
diff --git a/src/changelog/.2.x.x/2295_add_JMS_Jakarta_Appender.xml b/src/changelog/.2.x.x/2295_add_JMS_Jakarta_Appender.xml
new file mode 100644
index 0000000000..fd40164e1b
--- /dev/null
+++ b/src/changelog/.2.x.x/2295_add_JMS_Jakarta_Appender.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="fixed">
+ <issue id="2995" link="https://github.com/apache/logging-log4j2/issues/2995"/>
+ <description format="asciidoc">Add a Jakarta-based JMS Appender module log4j-jakarta-jms and deprecate the Javax version.</description>
+</entry>
diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders/message-queue.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders/message-queue.adoc
index 3adb9f8b90..d032c53de3 100644
--- a/src/site/antora/modules/ROOT/pages/manual/appenders/message-queue.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/appenders/message-queue.adoc
@@ -423,8 +423,8 @@ This example cannot be configured using Java properties.
[#JmsAppender]
== JMS Appender
-The JMS Appender sends the formatted log event to a
-https://jakarta.ee/specifications/messaging/2.0/[Jakarta Messaging API]
+The JMS Appender sends a formatted log event to a
+https://jakarta.ee/specifications/messaging/3.0/[Jakarta] or https://jakarta.ee/specifications/messaging/2.0/[Javax] Messaging API
destination.
[IMPORTANT]
@@ -432,10 +432,12 @@ destination.
As of Log4j `2.17.0` you need to enable the JMS Appender **explicitly** by setting the
xref:manual/systemproperties.adoc#log4j2.enableJndiJms[`log4j2.enableJndiJms`]
configuration property to `true`.
-
-Due to breaking changes in the underlying API, the JMS Appender cannot be used with Jakarta Messaging API 3.0 or later.
====
+For Jakarta, use the `JMS-Jakarta` element name in the `log4j-jakarta-jms` Maven module.
+
+For Javax, use the `JMS-Javax` element name; the names `JMS`, `JMSQueue`, and `JMSTopic` are provided for backward compatibility.
+
[#JmsAppender-attributes]
.JMS Appender configuration attributes
[cols="1m,1,1,5"]
@@ -601,6 +603,8 @@ https://jakarta.ee/specifications/platform/8/apidocs/javax/jms/objectmessage[`Ob
[#JmsAppender-examples]
=== Configuration examples
+In the examples below, to use Jakarta, replace `JMS` with `JMS-Jakarta`.
+
Here is a sample JMS Appender configuration:
[tabs]
diff --git a/src/site/antora/modules/ROOT/pages/manual/messages.adoc b/src/site/antora/modules/ROOT/pages/manual/messages.adoc
index 2e123a8022..d678fe8e11 100644
--- a/src/site/antora/modules/ROOT/pages/manual/messages.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/messages.adoc
@@ -233,7 +233,7 @@ It supports following formats:
Some appenders handle ``MapMessage``s differently when there is no layout:
-* JMS Appender converts to a JMS `javax.jms.MapMessage`
+* JMS Appender converts to a JMS `javax.jms.MapMessage` or `jakarta.jms.MapMessage`
* xref:manual/appenders/database.adoc#JdbcAppender[JDBC Appender] converts to values in an `SQL INSERT` statement
* xref:manual/appenders/database.adoc#MongoDbProvider[MongoDB NoSQL provider] converts to fields in a MongoDB object