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 2017/06/26 01:58:23 UTC

logging-log4j2 git commit: [LOG4J2-1934] JMS Appender does not know how to recover from a broken connection.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master f1778a02c -> 8573c8e56


[LOG4J2-1934] JMS Appender does not know how to recover from a broken
connection.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/8573c8e5
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/8573c8e5
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/8573c8e5

Branch: refs/heads/master
Commit: 8573c8e563c2b734f17e6c7f4a141b7cfb80a286
Parents: f1778a0
Author: Gary Gregory <gg...@apache.org>
Authored: Sun Jun 25 18:58:19 2017 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Sun Jun 25 18:58:19 2017 -0700

----------------------------------------------------------------------
 .../JmsAppenderConnectPostStartupIT.java        |  79 ++++++
 .../activemq/JmsAppenderConnectReConnectIT.java |   3 -
 .../mom/activemq/JmsAppenderReConnectionIT.java |  79 ------
 .../log4j/core/appender/mom/JmsAppender.java    | 275 ++++++++++++-------
 .../log4j/core/appender/mom/JmsManager.java     | 166 +++++------
 src/changes/changes.xml                         |   3 +
 6 files changed, 350 insertions(+), 255 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectPostStartupIT.java
----------------------------------------------------------------------
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectPostStartupIT.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectPostStartupIT.java
new file mode 100644
index 0000000..a7391ba
--- /dev/null
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectPostStartupIT.java
@@ -0,0 +1,79 @@
+/*
+ * 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.activemq;
+
+import org.apache.activemq.jndi.ActiveMQInitialContextFactory;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+/**
+ * Integration test for JmsAppender using an embedded ActiveMQ broker with in
+ * socket communications between clients and broker. This test manages a client
+ * connection to JMS like a Appender would. This test appender is managed at the
+ * class level by a JmsTestConfigRule.
+ * <p>
+ * Tests that a JMS appender can connect to a broker AFTER Log4j startup.
+ * </p>
+ * <p>
+ * LOG4J2-1934 JMS Appender does not know how to recover from a broken
+ * connection. See https://issues.apache.org/jira/browse/LOG4J2-1934
+ * </p>
+ */
+@Ignore
+@Category(Appenders.Jms.class)
+public class JmsAppenderConnectPostStartupIT extends AbstractJmsAppenderIT {
+
+	public static final AvailablePortSystemPropertyRule portRule = AvailablePortSystemPropertyRule
+			.create(ActiveMqBrokerServiceRule.PORT_PROPERTY_NAME);
+
+	@Rule
+	public final ActiveMqBrokerServiceRule activeMqBrokerServiceRule = new ActiveMqBrokerServiceRule(
+			JmsAppenderConnectPostStartupIT.class.getName(), portRule.getName());
+
+	// "admin"/"admin" are the default Apache Active MQ creds.
+	private static final JmsClientTestConfigRule jmsClientTestConfigRule = new JmsClientTestConfigRule(
+			ActiveMQInitialContextFactory.class.getName(), "tcp://localhost:" + portRule.getPort(), "admin", "admin");
+
+	/**
+	 * Assign the port and client ONCE for the whole test suite.
+	 */
+	@ClassRule
+	public static final RuleChain ruleChain = RuleChainFactory.create(portRule, jmsClientTestConfigRule);
+
+	@AfterClass
+	public static void afterClass() {
+		jmsClientTestConfigRule.getJmsClientTestConfig().stop();
+	}
+
+	@BeforeClass
+	public static void beforeClass() {
+		jmsClientTestConfigRule.getJmsClientTestConfig().start();
+	}
+
+	public JmsAppenderConnectPostStartupIT() {
+		super(jmsClientTestConfigRule);
+	}
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectReConnectIT.java
----------------------------------------------------------------------
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectReConnectIT.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectReConnectIT.java
index 7e53171..82b8f39 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectReConnectIT.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectReConnectIT.java
@@ -29,7 +29,6 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.layout.MessageLayout;
 import org.apache.logging.log4j.message.StringMapMessage;
 import org.apache.logging.log4j.test.AvailablePortFinder;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
@@ -41,7 +40,6 @@ import org.junit.experimental.categories.Category;
  * connection. See https://issues.apache.org/jira/browse/LOG4J2-1934
  * </p>
  */
-@Ignore
 @Category(Appenders.Jms.class)
 public class JmsAppenderConnectReConnectIT {
 
@@ -64,7 +62,6 @@ public class JmsAppenderConnectReConnectIT {
 	}
 
 	@Test
-	@Ignore
 	public void testConnectReConnect() throws Exception {
 		// Start broker
 		final int port = AvailablePortFinder.getNextAvailable();

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderReConnectionIT.java
----------------------------------------------------------------------
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderReConnectionIT.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderReConnectionIT.java
deleted file mode 100644
index 262a3da..0000000
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderReConnectionIT.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.activemq;
-
-import org.apache.activemq.jndi.ActiveMQInitialContextFactory;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-/**
- * Integration test for JmsAppender using an embedded ActiveMQ broker with in
- * socket communications between clients and broker. This test manages a client
- * connection to JMS like a Appender would. This test appender is managed at the
- * class level by a JmsTestConfigRule.
- * <p>
- * Tests that a JMS appender can connect to a broker AFTER Log4j startup.
- * </p>
- * <p>
- * LOG4J2-1934 JMS Appender does not know how to recover from a broken
- * connection. See https://issues.apache.org/jira/browse/LOG4J2-1934
- * </p>
- */
-@Ignore
-@Category(Appenders.Jms.class)
-public class JmsAppenderReConnectionIT extends AbstractJmsAppenderIT {
-
-	public static final AvailablePortSystemPropertyRule portRule = AvailablePortSystemPropertyRule
-			.create(ActiveMqBrokerServiceRule.PORT_PROPERTY_NAME);
-
-	@Rule
-	public final ActiveMqBrokerServiceRule activeMqBrokerServiceRule = new ActiveMqBrokerServiceRule(
-			JmsAppenderReConnectionIT.class.getName(), portRule.getName());
-
-	// "admin"/"admin" are the default Apache Active MQ creds.
-	private static final JmsClientTestConfigRule jmsClientTestConfigRule = new JmsClientTestConfigRule(
-			ActiveMQInitialContextFactory.class.getName(), "tcp://localhost:" + portRule.getPort(), "admin", "admin");
-
-	/**
-	 * Assign the port and client ONCE for the whole test suite.
-	 */
-	@ClassRule
-	public static final RuleChain ruleChain = RuleChainFactory.create(portRule, jmsClientTestConfigRule);
-
-	@AfterClass
-	public static void afterClass() {
-		jmsClientTestConfigRule.getJmsClientTestConfig().stop();
-	}
-
-	@BeforeClass
-	public static void beforeClass() {
-		jmsClientTestConfigRule.getJmsClientTestConfig().start();
-	}
-
-	public JmsAppenderReConnectionIT() {
-		super(jmsClientTestConfigRule);
-	}
-}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java
----------------------------------------------------------------------
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 6ca0296..737d24a 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
@@ -18,18 +18,20 @@
 package org.apache.logging.log4j.core.appender.mom;
 
 import java.io.Serializable;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 import javax.jms.JMSException;
 import javax.jms.Message;
-import javax.jms.MessageProducer;
 
 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.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.mom.JmsManager.JmsManagerConfiguration;
 import org.apache.logging.log4j.core.config.Node;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
@@ -39,51 +41,16 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
 import org.apache.logging.log4j.core.layout.SerializedLayout;
 import org.apache.logging.log4j.core.net.JndiManager;
+import org.apache.logging.log4j.status.StatusLogger;
 
 /**
  * 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.
  */
 @Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
-@PluginAliases({"JMSQueue", "JMSTopic"})
+@PluginAliases({ "JMSQueue", "JMSTopic" })
 public class JmsAppender extends AbstractAppender {
 
-    private final JmsManager manager;
-    private final MessageProducer producer;
-
-    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);
-        this.manager = manager;
-        this.producer = this.manager.createMessageProducer();
-    }
-
-    @Override
-    public void append(final LogEvent event) {
-        try {
-            final Message message = this.manager.createMessage(getLayout().toSerializable(event));
-            message.setJMSTimestamp(event.getTimeMillis());
-            this.producer.send(message);
-        } catch (final JMSException e) {
-            throw new AppenderLoggingException(e);
-        }
-    }
-
-    @Override
-    public boolean stop(final long timeout, final TimeUnit timeUnit) {
-        setStopping();
-        boolean stopped = super.stop(timeout, timeUnit, false);
-        stopped &= this.manager.stop(timeout, timeUnit);
-        setStopped();
-        return stopped;
-    }
-
-    @PluginBuilderFactory
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
     public static class Builder implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {
 
         @PluginBuilderAttribute
@@ -110,7 +77,7 @@ public class JmsAppender extends AbstractAppender {
         private String factoryBindingName;
 
         @PluginBuilderAttribute
-        @PluginAliases({"queueBindingName", "topicBindingName"})
+        @PluginAliases({ "queueBindingName", "topicBindingName" })
         @Required(message = "A javax.jms.Destination JNDI name must be specified")
         private String destinationBindingName;
 
@@ -126,114 +93,123 @@ public class JmsAppender extends AbstractAppender {
         @PluginElement("Filter")
         private Filter filter;
 
+        @PluginElement("ReconnectOnExceptionMessage")
+        private String[] reconnectOnExceptionMessages = new String[] { "closed" };
+
+        @PluginBuilderAttribute("reconnectAttempts")
+        private final int reconnectAttempts = 3;
+
+        @PluginBuilderAttribute("reconnectIntervalMillis")
+        private final long reconnectIntervalMillis = 1000;
+
         @PluginBuilderAttribute
         private boolean ignoreExceptions = true;
-        
+
         // Programmatic access only for now.
         private JmsManager jmsManager;
 
         private Builder() {
         }
 
-        public Builder setName(final String name) {
-            this.name = name;
-            return this;
+        @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
+        @Override
+        public JmsAppender build() {
+            JmsManager actualJmsManager = jmsManager;
+            JndiManager jndiManager = null;
+            JmsManagerConfiguration configuration = null;
+            if (actualJmsManager == null) {
+                jndiManager = JndiManager.getJndiManager(factoryName, providerUrl, urlPkgPrefixes,
+                        securityPrincipalName, securityCredentials, null);
+                configuration = new JmsManagerConfiguration(jndiManager, factoryBindingName, destinationBindingName,
+                        userName, password);
+                actualJmsManager = AbstractManager.getManager(name, JmsManager.FACTORY, configuration);
+            }
+            // TODO Try to reconnect later by letting the manager be null?
+            if (actualJmsManager == null) {
+                // JmsManagerFactory has already logged an ERROR.
+                return null;
+            }
+            return new JmsAppender(name, filter, layout, ignoreExceptions, reconnectOnExceptionMessages,
+                    reconnectAttempts, reconnectIntervalMillis, actualJmsManager);
         }
 
-        public Builder setFactoryName(final String factoryName) {
-            this.factoryName = factoryName;
+        public Builder setDestinationBindingName(final String destinationBindingName) {
+            this.destinationBindingName = destinationBindingName;
             return this;
         }
 
-        public Builder setProviderUrl(final String providerUrl) {
-            this.providerUrl = providerUrl;
+        public Builder setFactoryBindingName(final String factoryBindingName) {
+            this.factoryBindingName = factoryBindingName;
             return this;
         }
 
-        public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
-            this.urlPkgPrefixes = urlPkgPrefixes;
+        public Builder setFactoryName(final String factoryName) {
+            this.factoryName = factoryName;
             return this;
         }
 
-        public Builder setSecurityPrincipalName(final String securityPrincipalName) {
-            this.securityPrincipalName = securityPrincipalName;
+        public Builder setFilter(final Filter filter) {
+            this.filter = filter;
             return this;
         }
 
-        public Builder setSecurityCredentials(final String securityCredentials) {
-            this.securityCredentials = securityCredentials;
+        public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
+            this.ignoreExceptions = ignoreExceptions;
             return this;
         }
 
-        public Builder setFactoryBindingName(final String factoryBindingName) {
-            this.factoryBindingName = factoryBindingName;
+        public Builder setJmsManager(final JmsManager jmsManager) {
+            this.jmsManager = jmsManager;
             return this;
         }
 
-        public Builder setDestinationBindingName(final String destinationBindingName) {
-            this.destinationBindingName = destinationBindingName;
+        public Builder setLayout(final Layout<? extends Serializable> layout) {
+            this.layout = layout;
             return this;
         }
 
-        /**
-         * @deprecated Use {@link #setUserName(String)}.
-         */
-        @Deprecated
-        public Builder setUsername(final String username) {
-            this.userName = username;
+        public Builder setName(final String name) {
+            this.name = name;
             return this;
         }
 
-        public Builder setUserName(final String userName) {
-            this.userName = userName;
+        public Builder setPassword(final String password) {
+            this.password = password;
             return this;
         }
 
-        public Builder setPassword(final String password) {
-            this.password = password;
+        public Builder setProviderUrl(final String providerUrl) {
+            this.providerUrl = providerUrl;
             return this;
         }
 
-        public Builder setLayout(final Layout<? extends Serializable> layout) {
-            this.layout = layout;
+        public Builder setSecurityCredentials(final String securityCredentials) {
+            this.securityCredentials = securityCredentials;
             return this;
         }
 
-        public Builder setFilter(final Filter filter) {
-            this.filter = filter;
+        public Builder setSecurityPrincipalName(final String securityPrincipalName) {
+            this.securityPrincipalName = securityPrincipalName;
             return this;
         }
 
-        public Builder setJmsManager(final JmsManager jmsManager) {
-            this.jmsManager = jmsManager;
+        public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
+            this.urlPkgPrefixes = urlPkgPrefixes;
             return this;
         }
 
-        public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
-            this.ignoreExceptions = ignoreExceptions;
+        /**
+         * @deprecated Use {@link #setUserName(String)}.
+         */
+        @Deprecated
+        public Builder setUsername(final String username) {
+            this.userName = username;
             return this;
         }
 
-        @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
-        @Override
-        public JmsAppender build() {
-            JmsManager actualJmsManager = jmsManager;
-            JndiManager jndiManager = null;
-            if (actualJmsManager == null) {
-                jndiManager = JndiManager.getJndiManager(factoryName, providerUrl, urlPkgPrefixes,
-                        securityPrincipalName, securityCredentials, null);
-                actualJmsManager = JmsManager.getJmsManager(name, jndiManager, factoryBindingName,
-                        destinationBindingName, userName, password);
-            }
-            try {
-                return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager);
-            } catch (final JMSException e) {
-                LOGGER.error("Error creating JmsAppender [{}].", name, e);
-                if (jndiManager != null) {
-                    jndiManager.stop(500, TimeUnit.MILLISECONDS);
-                }
-                return null;
-            }
+        public Builder setUserName(final String userName) {
+            this.userName = userName;
+            return this;
         }
 
         /**
@@ -249,6 +225,115 @@ public class JmsAppender extends AbstractAppender {
                     + jmsManager + "]";
         }
 
+        public void setReconnectOnExceptionMessage(final String[] reconnectOnExceptionMessage) {
+            this.reconnectOnExceptionMessages = reconnectOnExceptionMessage;
+        }
+
+    }
+
+    @PluginBuilderFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    private volatile JmsManager manager;
+    private final String[] reconnectOnExceptionMessages;
+    private final int reconnectAttempts;
+    private final long reconnectIntervalMillis;
+
+    /**
+     * 
+     * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility
+     * @deprecated Use the other constructor
+     */
+    @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);
+        this.manager = manager;
+        this.reconnectOnExceptionMessages = null;
+        this.reconnectAttempts = 0;
+        this.reconnectIntervalMillis = 0;
+    }
+
+    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+            final boolean ignoreExceptions, final String[] reconnectOnExceptionMessage, final int reconnectAttempts,
+            final long reconnectIntervalMillis, final JmsManager manager) {
+        super(name, filter, layout, ignoreExceptions);
+        this.manager = manager;
+        this.reconnectOnExceptionMessages = reconnectOnExceptionMessage;
+        this.reconnectAttempts = reconnectAttempts;
+        this.reconnectIntervalMillis = reconnectIntervalMillis;
+    }
+
+    @Override
+    public void append(final LogEvent event) {
+        Serializable serializable = null;
+        try {
+            serializable = getLayout().toSerializable(event);
+            send(event, serializable);
+        } catch (final JMSException e) {
+            // Try to reconnect once under specific conditions
+            // reconnectOnExceptionMessages MUST be set to demonstrate intent
+            // This is designed to handle the use case where an application is running and the JMS broker is recycled.
+            if (reconnectOnExceptionMessages == null) {
+                throw new AppenderLoggingException(e);
+            }
+            boolean reconnect = false;
+            for (final String message : reconnectOnExceptionMessages) {
+                reconnect = Objects.toString(e.getMessage()).contains(message);
+                if (reconnect) {
+                    break;
+                }
+            }
+            if (reconnect) {
+                int count = 0;
+                while (count < reconnectAttempts) {
+                    // TODO How to best synchronize this?
+                    final JmsManagerConfiguration config = this.manager.getJmsManagerConfiguration();
+                    this.manager = AbstractManager.getManager(getName(), JmsManager.FACTORY, config);
+                    try {
+                        if (serializable != null) {
+                            count++;
+                            StatusLogger.getLogger().debug(
+                                    "Reconnect attempt {} of {} for JMS appender {} and configuration {} due to {}",
+                                    count, reconnectAttempts, getName(), config, e.toString(), e);
+                            send(event, serializable);
+                            return;
+                        }
+                    } catch (final JMSException e1) {
+                        if (count == reconnectAttempts) {
+                            throw new AppenderLoggingException(e);
+                        }
+                        StatusLogger.getLogger().debug(
+                                "Reconnect attempt {} of {} FAILED for JMS appender {} and configuration {} due to {}; slepping {} milliseconds...",
+                                count, reconnectAttempts, getName(), config, e.toString(), reconnectIntervalMillis, e);
+                        if (reconnectIntervalMillis > 0) {
+                            try {
+                                Thread.sleep(reconnectIntervalMillis);
+                            } catch (final InterruptedException e2) {
+                                throw new AppenderLoggingException(e2);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void send(final LogEvent event, final Serializable serializable) throws JMSException {
+        final Message message = this.manager.createMessage(serializable);
+        message.setJMSTimestamp(event.getTimeMillis());
+        this.manager.send(message);
+    }
+
+    @Override
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        setStopping();
+        boolean stopped = super.stop(timeout, timeUnit, false);
+        stopped &= this.manager.stop(timeout, timeUnit);
+        setStopped();
+        return stopped;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
----------------------------------------------------------------------
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 5677b86..b6ad13c 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
@@ -44,31 +44,47 @@ import org.apache.logging.log4j.util.BiConsumer;
  */
 public class JmsManager extends AbstractManager {
 
-    private static final Logger LOGGER = StatusLogger.getLogger();
+    static class JmsManagerConfiguration {
+        private final JndiManager jndiManager;
+        private final String connectionFactoryName;
+        private final String destinationName;
+        private final String userName;
+        private final String password;
 
-    private static final JmsManagerFactory FACTORY = new JmsManagerFactory();
+        JmsManagerConfiguration(final JndiManager jndiManager, final String connectionFactoryName, final String destinationName,
+                                 final String userName, final String password) {
+            this.jndiManager = jndiManager;
+            this.connectionFactoryName = connectionFactoryName;
+            this.destinationName = destinationName;
+            this.userName = userName;
+            this.password = password;
+        }
 
-    private final JndiManager jndiManager;
-    private final Connection connection;
-    private final Session session;
-    private final Destination destination;
+        /**
+         * Does not include the password.
+         */
+        @Override
+        public String toString() {
+            return "JmsConfiguration [jndiManager=" + jndiManager + ", connectionFactoryName=" + connectionFactoryName
+                    + ", destinationName=" + destinationName + ", userName=" + userName + "]";
+        }
+    }
 
-    private JmsManager(final String name, final JndiManager jndiManager, final String connectionFactoryName,
-                       final String destinationName, final String userName, final String password)
-        throws NamingException, JMSException {
-        super(null, name);
-        this.jndiManager = jndiManager;
-        final ConnectionFactory connectionFactory = this.jndiManager.lookup(connectionFactoryName);
-        if (userName != null && password != null) {
-            this.connection = connectionFactory.createConnection(userName, password);
-        } else {
-            this.connection = connectionFactory.createConnection();
+    private static class JmsManagerFactory implements ManagerFactory<JmsManager, JmsManagerConfiguration> {
+
+        @Override
+        public JmsManager createManager(final String name, final JmsManagerConfiguration data) {
+            try {
+                return new JmsManager(name, data);
+            } catch (final Exception e) {
+                LOGGER.error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
+                return null;
+            }
         }
-        this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
-        this.destination = this.jndiManager.lookup(destinationName);
-        this.connection.start();
     }
 
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    static final JmsManagerFactory FACTORY = new JmsManagerFactory();
     /**
      * Gets a JmsManager using the specified configuration parameters.
      *
@@ -83,29 +99,36 @@ public class JmsManager extends AbstractManager {
     public static JmsManager getJmsManager(final String name, final JndiManager jndiManager,
                                            final String connectionFactoryName, final String destinationName,
                                            final String userName, final String password) {
-        final JmsConfiguration configuration = new JmsConfiguration(jndiManager, connectionFactoryName, destinationName,
+        final JmsManagerConfiguration configuration = new JmsManagerConfiguration(jndiManager, connectionFactoryName, destinationName,
             userName, password);
         return getManager(name, FACTORY, configuration);
     }
+    private final JndiManager jndiManager;
+    private final Connection connection;
+    private final Session session;
 
-    /**
-     * Creates a MessageConsumer on this Destination using the current Session.
-     *
-     * @return A MessageConsumer on this Destination.
-     * @throws JMSException
-     */
-    public MessageConsumer createMessageConsumer() throws JMSException {
-        return this.session.createConsumer(this.destination);
-    }
 
-    /**
-     * Creates a MessageProducer on this Destination using the current Session.
-     *
-     * @return A MessageProducer on this Destination.
-     * @throws JMSException
-     */
-    public MessageProducer createMessageProducer() throws JMSException {
-        return this.session.createProducer(this.destination);
+    private final Destination destination;
+
+    private final JmsManagerConfiguration configuration;
+
+    private final MessageProducer producer;
+
+    private JmsManager(final String name, final JmsManagerConfiguration configuration)
+        throws NamingException, JMSException {
+        super(null, name);
+        this.configuration = configuration;
+        this.jndiManager = configuration.jndiManager;
+        final ConnectionFactory connectionFactory = this.jndiManager.lookup(configuration.connectionFactoryName);
+        if (configuration.userName != null && configuration.password != null) {
+            this.connection = connectionFactory.createConnection(configuration.userName, configuration.password);
+        } else {
+            this.connection = connectionFactory.createConnection();
+        }
+        this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        this.destination = this.jndiManager.lookup(configuration.destinationName);
+        this.producer = createMessageProducer();
+        this.connection.start();
     }
 
     /**
@@ -138,6 +161,31 @@ public class JmsManager extends AbstractManager {
         return this.session.createObjectMessage(object);
     }
 
+    /**
+     * Creates a MessageConsumer on this Destination using the current Session.
+     *
+     * @return A MessageConsumer on this Destination.
+     * @throws JMSException
+     */
+    public MessageConsumer createMessageConsumer() throws JMSException {
+        return this.session.createConsumer(this.destination);
+    }
+
+
+    /**
+     * Creates a MessageProducer on this Destination using the current Session.
+     *
+     * @return A MessageProducer on this Destination.
+     * @throws JMSException
+     */
+    public MessageProducer createMessageProducer() throws JMSException {
+        return this.session.createProducer(this.destination);
+    }
+
+    JmsManagerConfiguration getJmsManagerConfiguration() {
+        return configuration;
+    }
+
     private MapMessage map(final org.apache.logging.log4j.message.MapMessage<?, ?> log4jMapMessage, final MapMessage jmsMapMessage) {
         // Map without calling rg.apache.logging.log4j.message.MapMessage#getData() which makes a copy of the map.
         log4jMapMessage.forEach(new BiConsumer<String, Object>() {
@@ -145,7 +193,7 @@ public class JmsManager extends AbstractManager {
             public void accept(final String key, final Object value) {
                 try {
                     jmsMapMessage.setObject(key, value);
-                } catch (JMSException e) {
+                } catch (final JMSException e) {
                     throw new IllegalArgumentException(String.format("%s mapping key '%s' to value '%s': %s",
                             e.getClass(), key, value, e.getLocalizedMessage()), e);
                 }
@@ -154,7 +202,6 @@ public class JmsManager extends AbstractManager {
         return jmsMapMessage;
     }
 
-
     @Override
     protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
         boolean closed = true;
@@ -173,45 +220,8 @@ public class JmsManager extends AbstractManager {
         return closed && this.jndiManager.stop(timeout, timeUnit);
     }
 
-    private static class JmsConfiguration {
-        private final JndiManager jndiManager;
-        private final String connectionFactoryName;
-        private final String destinationName;
-        private final String userName;
-        private final String password;
-
-        private JmsConfiguration(final JndiManager jndiManager, final String connectionFactoryName, final String destinationName,
-                                 final String userName, final String password) {
-            this.jndiManager = jndiManager;
-            this.connectionFactoryName = connectionFactoryName;
-            this.destinationName = destinationName;
-            this.userName = userName;
-            this.password = password;
-        }
-
-        /**
-         * Does not include the password.
-         */
-        @Override
-        public String toString() {
-            return "JmsConfiguration [jndiManager=" + jndiManager + ", connectionFactoryName=" + connectionFactoryName
-                    + ", destinationName=" + destinationName + ", userName=" + userName + "]";
-        }
-    }
-
-    private static class JmsManagerFactory implements ManagerFactory<JmsManager, JmsConfiguration> {
-
-        @Override
-        public JmsManager createManager(final String name, final JmsConfiguration data) {
-            try {
-                return new JmsManager(name, data.jndiManager, data.connectionFactoryName, data.destinationName,
-                    data.userName, data.password);
-            } catch (final Exception e) {
-                LOGGER.error("Error creating JmsManager using ConnectionFactory [{}] and Destination [{}].",
-                    data.connectionFactoryName, data.destinationName, e);
-                return null;
-            }
-        }
+    void send(final Message message) throws JMSException {
+        producer.send(message);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8573c8e5/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 55627e8..ebd9311 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -49,6 +49,9 @@
       <action issue="LOG4J2-1945" dev="ggregory" type="add">
         Generate source jas for all test jars.
       </action>
+      <action issue="LOG4J2-1934" dev="ggregory" type="add">
+        JMS Appender does not know how to recover from a broken connection.
+      </action>
       <action issue="LOG4J2-1874" dev="rpopma" type="add" due-to="Roman Leventov">
         Added methods ::writeBytes(ByteBuffer) and ::writeBytes(byte[], int, int) to ByteBufferDestination interface and use these methods in TextEncoderHelper where possible to prepare for future enhancements to reduce lock contention.
       </action>