Repository: logging-log4j2 Updated Branches: refs/heads/master c13e31913 -> de7487ed2
[LOG4J2-1934] JMS Appender does not know how to recover from a broken connection. [LOG4J2-1955]JMS Appender should be able connect to a broker (later) even it is not present at configuration time. Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/de7487ed Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/de7487ed Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/de7487ed Branch: refs/heads/master Commit: de7487ed22b03f5c1a73ddb8f8d0f8bc89b8f4d8 Parents: c13e319 Author: Gary Gregory <[email protected]> Authored: Wed Jul 5 17:55:48 2017 -0700 Committer: Gary Gregory <[email protected]> Committed: Wed Jul 5 17:55:48 2017 -0700 ---------------------------------------------------------------------- log4j-core-its/pom.xml | 6 +- .../AbstractJmsAppenderReconnectIT.java | 93 +++++ .../activemq/ActiveMqBrokerServiceHelper.java | 37 +- .../mom/activemq/JmsAppenderConnectLaterIT.java | 109 ------ .../JmsAppenderConnectPostStartupIT.java | 91 +++-- .../activemq/JmsAppenderConnectReConnectIT.java | 93 ++--- .../mom/activemq/JmsClientTestConfig.java | 113 +++--- .../src/test/resources/logback-test.xml | 29 ++ .../log4j/core/appender/mom/JmsAppender.java | 129 ++----- .../log4j/core/appender/mom/JmsManager.java | 380 +++++++++++++++---- .../logging/log4j/core/net/JndiManager.java | 72 +++- .../log4j/core/appender/HttpAppenderTest.java | 2 + pom.xml | 6 - src/changes/changes.xml | 3 + src/site/xdoc/manual/appenders.xml | 14 + 15 files changed, 693 insertions(+), 484 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/pom.xml ---------------------------------------------------------------------- diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml index 3b720c2..a9985dd 100644 --- a/log4j-core-its/pom.xml +++ b/log4j-core-its/pom.xml @@ -201,6 +201,9 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <configuration> + <additionalClasspathElements> + <additionalClasspathElement>${project.basedir}/src/test/resources</additionalClasspathElement> + </additionalClasspathElements> <includes> <include>**/*.java</include> </includes> @@ -208,7 +211,8 @@ <exclude>**/ForceNoDefClassFoundError.*</exclude> </excludes> <groups> - org.apache.logging.log4j.categories.PerformanceTests + org.apache.logging.log4j.categories.PerformanceTests, + org.apache.logging.log4j.categories.Appenders$Jms </groups> </configuration> </plugin> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/AbstractJmsAppenderReconnectIT.java ---------------------------------------------------------------------- diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/AbstractJmsAppenderReconnectIT.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/AbstractJmsAppenderReconnectIT.java new file mode 100644 index 0000000..d457162 --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/AbstractJmsAppenderReconnectIT.java @@ -0,0 +1,93 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +import org.apache.activemq.broker.BrokerService; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.mom.JmsAppender; +import org.apache.logging.log4j.core.appender.mom.JmsManager; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.After; +import org.junit.Assert; + +/** + * Subclass for tests that reconnect to Apache Active MQ. The class makes sure resources are properly shutdown after + * each @Test method. A subclass normally only has one @Test method. + * <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> + */ +public class AbstractJmsAppenderReconnectIT { + + protected JmsClientTestConfig jmsClientTestConfig; + protected JmsAppender appender; + protected BrokerService brokerService; + + @After + public void after() { + try { + ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); + } catch (final Exception e) { + // Just log to the console for now. + e.printStackTrace(); + } + if (appender != null) { + appender.stop(); + } + if (jmsClientTestConfig != null) { + jmsClientTestConfig.stop(); + } + // Make sure the manager is gone as to not have bad side effect on other tests. + @SuppressWarnings("resource") + final JmsManager appenderManager = appender.getManager(); + if (appenderManager != null) { + Assert.assertFalse(AbstractManager.hasManager(appenderManager.getName())); + } + // Make sure the manager is gone as to not have bad side effect on other tests. + @SuppressWarnings("resource") + final JmsManager testManager = jmsClientTestConfig.getJmsManager(); + if (testManager != null) { + Assert.assertFalse(AbstractManager.hasManager(testManager.getName())); + } + } + + protected void appendEvent(final JmsAppender appender) { + final Map<String, String> map = new HashMap<>(); + final String messageText = "Hello, World!"; + final String loggerName = this.getClass().getName(); + map.put("messageText", messageText); + map.put("threadName", Thread.currentThread().getName()); + // @formatter:off + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(loggerName) + .setLoggerFqcn(loggerName) + .setLevel(Level.INFO) + .setMessage(new StringMapMessage(map)) + .setTimeMillis(System.currentTimeMillis()) + .build(); + // @formatter:on + appender.append(event); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/ActiveMqBrokerServiceHelper.java ---------------------------------------------------------------------- diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/ActiveMqBrokerServiceHelper.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/ActiveMqBrokerServiceHelper.java index 6f6949d..67d2b3e 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/ActiveMqBrokerServiceHelper.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/ActiveMqBrokerServiceHelper.java @@ -26,23 +26,26 @@ import org.apache.activemq.broker.BrokerService; */ public class ActiveMqBrokerServiceHelper { - static BrokerService startBrokerService(final String brokerName, String brokerUrlString, final int port) throws Exception { - // TODO Abstract out scheme - brokerUrlString = "tcp://localhost:" + port; - final BrokerService broker = new BrokerService(); - // configure the Broker - broker.setBrokerName(brokerName); - broker.addConnector(brokerUrlString); - broker.setPersistent(false); - broker.start(); - broker.waitUntilStarted(); - return broker; - } + static BrokerService startBrokerService(final String brokerName, String brokerUrlString, final int port) + throws Exception { + // TODO Abstract out scheme + brokerUrlString = "tcp://localhost:" + port; + final BrokerService broker = new BrokerService(); + // configure the Broker + broker.setBrokerName(brokerName); + broker.addConnector(brokerUrlString); + broker.setPersistent(false); + broker.start(); + broker.waitUntilStarted(); + return broker; + } - static void stopBrokerService(final BrokerService broker) throws IOException, Exception { - broker.deleteAllMessages(); - broker.stop(); - broker.waitUntilStopped(); - } + static void stopBrokerService(final BrokerService brokerService) throws IOException, Exception { + if (brokerService != null) { + brokerService.deleteAllMessages(); + brokerService.stop(); + brokerService.waitUntilStopped(); + } + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectLaterIT.java ---------------------------------------------------------------------- diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectLaterIT.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectLaterIT.java deleted file mode 100644 index 6fcb9d2..0000000 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsAppenderConnectLaterIT.java +++ /dev/null @@ -1,109 +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 java.util.HashMap; -import java.util.Map; - -import org.apache.activemq.broker.BrokerService; -import org.apache.activemq.jndi.ActiveMQInitialContextFactory; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.categories.Appenders; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.mom.JmsAppender; -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; - -/** - * Tests that a JMS Appender can reconnect to a JMS broker after it has been - * recycled. - * <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 JmsAppenderConnectLaterIT { - - private void appendEvent(final JmsAppender appender) { - final Map<String, String> map = new HashMap<>(); - final String messageText = "Hello, World!"; - final String loggerName = this.getClass().getName(); - map.put("messageText", messageText); - map.put("threadName", Thread.currentThread().getName()); - // @formatter:off - final LogEvent event = Log4jLogEvent.newBuilder() - .setLoggerName(loggerName) - .setLoggerFqcn(loggerName) - .setLevel(Level.INFO) - .setMessage(new StringMapMessage(map)) - .setTimeMillis(System.currentTimeMillis()) - .build(); - // @formatter:on - appender.append(event); - } - - @Test - public void testConnectReConnect() throws Exception { - // Start broker - final int port = AvailablePortFinder.getNextAvailable(); - final String brokerUrlString = "tcp://localhost:" + port; - // Start appender - // final JmsClientTestConfig jmsClientTestConfig = new JmsClientTestConfig( - // ActiveMQInitialContextFactory.class.getName(), brokerUrlString, "admin", - // "admin".toCharArray()); - // jmsClientTestConfig.start(); - // final JmsAppender appender = - // jmsClientTestConfig.createAppender(MessageLayout.createLayout()); - - // @formatter:off - final JmsAppender appender = JmsAppender.newBuilder() - .setName("JmsAppender") - .setLayout(MessageLayout.createLayout()) - .setIgnoreExceptions(true) - .setFactoryBindingName("ConnectionFactory") - .setProviderUrl(brokerUrlString) - .setUserName("admin") - .setPassword("admin".toCharArray()) - .build(); - // @formatter:on - appender.start(); - - // Log message - appendEvent(appender); - // Start broker - BrokerService brokerService = ActiveMqBrokerServiceHelper - .startBrokerService(JmsAppenderConnectLaterIT.class.getName(), brokerUrlString, port); - // Stop broker - ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); - // Restart broker - brokerService = ActiveMqBrokerServiceHelper.startBrokerService(JmsAppenderConnectLaterIT.class.getName(), - brokerUrlString, port); - // Logging again should cause the appender to reconnect - appendEvent(appender); - // Stop broker - ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); - } - -} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/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 index bf3d50c..87425b2 100644 --- 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 @@ -14,66 +14,61 @@ * 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.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.layout.MessageLayout; +import org.apache.logging.log4j.test.AvailablePortFinder; +import org.junit.Assert; +import org.junit.Test; 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. + * Tests that a JMS Appender start when there is no broker and connect the broker when it is started later.. * <p> - * Tests that a JMS appender can connect to a broker AFTER Log4j startup. + * LOG4J2-1934 JMS Appender does not know how to recover from a broken connection. See + * https://issues.apache.org/jira/browse/LOG4J2-1934 * </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 + * This test class' single test method performs the following: * </p> + * <ol> + * <li>Starts a JMS Appender</li> + * <li>Logs an event (fails and starts the reconnect thread)</li> + * <li>Starts Apache ActiveMQ</li> + * <li>Logs an event successfully</li> + * </ol> */ -@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".toCharArray()); - - /** - * 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 class JmsAppenderConnectPostStartupIT extends AbstractJmsAppenderReconnectIT { - public JmsAppenderConnectPostStartupIT() { - super(jmsClientTestConfigRule); - } + @Test + public void testConnectPostStartup() throws Exception { + // + // Start appender + final int port = AvailablePortFinder.getNextAvailable(); + final String brokerUrlString = "tcp://localhost:" + port; + jmsClientTestConfig = new JmsClientTestConfig(ActiveMQInitialContextFactory.class.getName(), brokerUrlString, + "admin", "admin".toCharArray()); + jmsClientTestConfig.start(); + appender = jmsClientTestConfig.createAppender(MessageLayout.createLayout()); + // + // Logging will fail but the JMS manager is now running a reconnect thread. + try { + appendEvent(appender); + Assert.fail("Expected to catch a " + AppenderLoggingException.class.getName()); + } catch (final AppenderLoggingException e) { + // Expected. + } + // + // Start broker + brokerService = ActiveMqBrokerServiceHelper.startBrokerService(JmsAppenderConnectPostStartupIT.class.getName(), + brokerUrlString, port); + // + // Logging now should just work + Thread.sleep(appender.getManager().getJmsManagerConfiguration().getReconnectIntervalMillis()); + appendEvent(appender); + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/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 8944d5b..d77e32a 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 @@ -16,74 +16,55 @@ */ package org.apache.logging.log4j.core.appender.mom.activemq; -import java.util.HashMap; -import java.util.Map; - -import org.apache.activemq.broker.BrokerService; import org.apache.activemq.jndi.ActiveMQInitialContextFactory; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.categories.Appenders; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.mom.JmsAppender; -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.Test; import org.junit.experimental.categories.Category; /** - * Tests that a JMS Appender can reconnect to a JMS broker after it has been - * recycled. + * Tests that a JMS Appender can reconnect to a JMS broker after it has been recycled. + * <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> * <p> - * LOG4J2-1934 JMS Appender does not know how to recover from a broken - * connection. See https://issues.apache.org/jira/browse/LOG4J2-1934 + * This test class' single test method performs the following: * </p> + * <ol> + * <li>Starts Apache ActiveMQ</li> + * <li>Starts a JMS Appender</li> + * <li>Logs an event</li> + * <li>Stops Apache ActiveMQ</li> + * <li>Starts Apache ActiveMQ</li> + * <li>Logs an event</li> + * </ol> */ @Category(Appenders.Jms.class) -public class JmsAppenderConnectReConnectIT { - - private void appendEvent(final JmsAppender appender) { - final Map<String, String> map = new HashMap<>(); - final String messageText = "Hello, World!"; - final String loggerName = this.getClass().getName(); - map.put("messageText", messageText); - map.put("threadName", Thread.currentThread().getName()); - // @formatter:off - final LogEvent event = Log4jLogEvent.newBuilder() - .setLoggerName(loggerName) - .setLoggerFqcn(loggerName) - .setLevel(Level.INFO) - .setMessage(new StringMapMessage(map)) - .setTimeMillis(System.currentTimeMillis()) - .build(); - // @formatter:on - appender.append(event); - } +public class JmsAppenderConnectReConnectIT extends AbstractJmsAppenderReconnectIT { - @Test - public void testConnectReConnect() throws Exception { - // Start broker - final int port = AvailablePortFinder.getNextAvailable(); - final String brokerUrlString = "tcp://localhost:" + port; - BrokerService brokerService = ActiveMqBrokerServiceHelper - .startBrokerService(JmsAppenderConnectReConnectIT.class.getName(), brokerUrlString, port); - // Start appender - final JmsClientTestConfig jmsClientTestConfig = new JmsClientTestConfig(ActiveMQInitialContextFactory.class.getName(), - brokerUrlString, "admin", "admin".toCharArray()); - jmsClientTestConfig.start(); - final JmsAppender appender = jmsClientTestConfig.createAppender(MessageLayout.createLayout()); - // Log message - appendEvent(appender); - // Stop broker - ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); - // Restart broker - brokerService = ActiveMqBrokerServiceHelper.startBrokerService(JmsAppenderConnectReConnectIT.class.getName(), - brokerUrlString, port); - // Logging again should cause the appender to reconnect - appendEvent(appender); - // Stop broker - ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); - } + @Test + public void testConnectReConnect() throws Exception { + // Start broker + final int port = AvailablePortFinder.getNextAvailable(); + final String brokerUrlString = "tcp://localhost:" + port; + brokerService = ActiveMqBrokerServiceHelper.startBrokerService(JmsAppenderConnectReConnectIT.class.getName(), + brokerUrlString, port); + // Start appender + jmsClientTestConfig = new JmsClientTestConfig(ActiveMQInitialContextFactory.class.getName(), brokerUrlString, + "admin", "admin".toCharArray()); + jmsClientTestConfig.start(); + appender = jmsClientTestConfig.createAppender(MessageLayout.createLayout()); + // Log message + appendEvent(appender); + // Stop broker + ActiveMqBrokerServiceHelper.stopBrokerService(brokerService); + // Restart broker + brokerService = ActiveMqBrokerServiceHelper.startBrokerService(JmsAppenderConnectReConnectIT.class.getName(), + brokerUrlString, port); + // Logging again should cause the appender to reconnect + appendEvent(appender); + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsClientTestConfig.java ---------------------------------------------------------------------- diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsClientTestConfig.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsClientTestConfig.java index f88a804..160d65e 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsClientTestConfig.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/mom/activemq/JmsClientTestConfig.java @@ -34,62 +34,63 @@ class JmsClientTestConfig { .setLayout(layout) .setIgnoreExceptions(true) .setJmsManager(jmsManager) + .setReconnectIntervalMillis(2000) .build(); // @formatter:on - jmsAppender.start(); - return jmsAppender; - } - - JmsAppender getJmsAppender() { - return jmsAppender; - } - - String getJmsInitialContextFactoryClassName() { - return jmsInitialContextFactoryClassName; - } - - JmsManager getJmsManager() { - return jmsManager; - } - - char[] getJmsPassword() { - return jmsPassword; - } - - String getJmsProviderUrlStr() { - return jmsProviderUrlStr; - } - - String getJmsUserName() { - return jmsUserName; - } - - void setJmsAppender(final JmsAppender jmsAppender) { - this.jmsAppender = jmsAppender; - } - - void setJmsManager(final JmsManager jmsManager) { - this.jmsManager = jmsManager; - } - - void start() { - System.setProperty(AbstractJmsAppenderIT.KEY_SERIALIZABLE_PACKAGES, - "org.apache.logging.log4j.core.impl,org.apache.logging.log4j.util,org.apache.logging.log4j,java.rmi"); - final Properties additional = new Properties(); - additional.setProperty("queue.TestQueue", "TestQueue"); - // jndiManager is closed in stop() through the jmsManager - @SuppressWarnings("resource") - final JndiManager jndiManager = JndiManager.getJndiManager(jmsInitialContextFactoryClassName, - jmsProviderUrlStr, null, null, null, additional); - jmsManager = JmsManager.getJmsManager("JmsManager", jndiManager, "ConnectionFactory", "TestQueue", - jmsUserName, jmsPassword); - } - - void stop() { - if (jmsManager != null) { - jmsManager.close(); - jmsManager = null; - } - System.getProperties().remove(AbstractJmsAppenderIT.KEY_SERIALIZABLE_PACKAGES); - } + jmsAppender.start(); + return jmsAppender; + } + + JmsAppender getJmsAppender() { + return jmsAppender; + } + + String getJmsInitialContextFactoryClassName() { + return jmsInitialContextFactoryClassName; + } + + JmsManager getJmsManager() { + return jmsManager; + } + + char[] getJmsPassword() { + return jmsPassword; + } + + String getJmsProviderUrlStr() { + return jmsProviderUrlStr; + } + + String getJmsUserName() { + return jmsUserName; + } + + void setJmsAppender(final JmsAppender jmsAppender) { + this.jmsAppender = jmsAppender; + } + + void setJmsManager(final JmsManager jmsManager) { + this.jmsManager = jmsManager; + } + + void start() { + System.setProperty(AbstractJmsAppenderIT.KEY_SERIALIZABLE_PACKAGES, + "org.apache.logging.log4j.core.impl,org.apache.logging.log4j.util,org.apache.logging.log4j,java.rmi"); + final Properties additional = new Properties(); + additional.setProperty("queue.TestQueue", "TestQueue"); + // jndiManager is closed in stop() through the jmsManager + final Properties jndiProperties = JndiManager.createProperties(jmsInitialContextFactoryClassName, + jmsProviderUrlStr, null, null, null, additional); + final String name = JmsManager.class.getName() + "-" + getClass().getSimpleName() + "@" + hashCode(); + jmsManager = JmsManager.getJmsManager(name, jndiProperties, "ConnectionFactory", "TestQueue", jmsUserName, + jmsPassword, false, JmsAppender.Builder.DEFAULT_RECONNECT_INTERVAL_MILLIS); + } + + void stop() { + if (jmsManager != null) { + jmsManager.close(); + jmsManager = null; + } + System.getProperties().remove(AbstractJmsAppenderIT.KEY_SERIALIZABLE_PACKAGES); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core-its/src/test/resources/logback-test.xml ---------------------------------------------------------------------- diff --git a/log4j-core-its/src/test/resources/logback-test.xml b/log4j-core-its/src/test/resources/logback-test.xml new file mode 100644 index 0000000..989bea6 --- /dev/null +++ b/log4j-core-its/src/test/resources/logback-test.xml @@ -0,0 +1,29 @@ +<?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> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> + </encoder> + </appender> + + <root level="info"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/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 906b36f..57e8c75 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,11 +18,10 @@ package org.apache.logging.log4j.core.appender.mom; import java.io.Serializable; -import java.util.Objects; +import java.util.Properties; import java.util.concurrent.TimeUnit; import javax.jms.JMSException; -import javax.jms.Message; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; @@ -30,7 +29,6 @@ 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; @@ -40,7 +38,6 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 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.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, @@ -52,6 +49,8 @@ public class JmsAppender extends AbstractAppender { public static class Builder implements org.apache.logging.log4j.core.util.Builder<JmsAppender> { + public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000; + @PluginBuilderAttribute @Required(message = "A name for the JmsAppender must be specified") private String name; @@ -92,18 +91,14 @@ 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; + private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; @PluginBuilderAttribute private boolean ignoreExceptions = true; + @PluginBuilderAttribute + private boolean immediateFail; + // Programmatic access only for now. private JmsManager jmsManager; @@ -114,13 +109,12 @@ public class JmsAppender extends AbstractAppender { @Override public JmsAppender build() { JmsManager actualJmsManager = jmsManager; - JndiManager jndiManager = null; JmsManagerConfiguration configuration = null; if (actualJmsManager == null) { - jndiManager = JndiManager.getJndiManager(factoryName, providerUrl, urlPkgPrefixes, + final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes, securityPrincipalName, securityCredentials, null); - configuration = new JmsManagerConfiguration(jndiManager, factoryBindingName, destinationBindingName, - userName, password); + configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName, + userName, password, false, reconnectIntervalMillis); actualJmsManager = AbstractManager.getManager(name, JmsManager.FACTORY, configuration); } if (actualJmsManager == null) { @@ -131,8 +125,12 @@ public class JmsAppender extends AbstractAppender { LOGGER.error("No layout provided for JmsAppender"); return null; } - return new JmsAppender(name, filter, layout, ignoreExceptions, reconnectOnExceptionMessages, - reconnectAttempts, reconnectIntervalMillis, actualJmsManager); + try { + return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager); + } catch (final JMSException e) { + // Never happens since the ctor no longer actually throws a JMSException. + throw new IllegalStateException(e); + } } public Builder setDestinationBindingName(final String destinationBindingName) { @@ -160,6 +158,11 @@ public class JmsAppender extends AbstractAppender { return this; } + public Builder setImmediateFail(final boolean immediateFail) { + this.immediateFail = immediateFail; + return this; + } + public Builder setJmsManager(final JmsManager jmsManager) { this.jmsManager = jmsManager; return this; @@ -194,6 +197,11 @@ public class JmsAppender extends AbstractAppender { return this; } + public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) { + this.reconnectIntervalMillis = reconnectIntervalMillis; + return this; + } + public Builder setSecurityCredentials(final String securityCredentials) { this.securityCredentials = securityCredentials; return this; @@ -236,10 +244,6 @@ public class JmsAppender extends AbstractAppender { + jmsManager + "]"; } - public void setReconnectOnExceptionMessage(final String[] reconnectOnExceptionMessage) { - this.reconnectOnExceptionMessages = reconnectOnExceptionMessage; - } - } @PluginBuilderFactory @@ -248,94 +252,25 @@ public class JmsAppender extends AbstractAppender { } 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 + * + * @throws JMSException + * not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0 */ - @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); - } - } - } - } - } - } + this.manager.send(event, getLayout().toSerializable(event)); } - 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); + public JmsManager getManager() { + return manager; } @Override http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/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 a985980..4cc8e68 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 @@ -18,6 +18,8 @@ package org.apache.logging.log4j.core.appender.mom; import java.io.Serializable; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.jms.Connection; @@ -32,42 +34,84 @@ import javax.jms.Session; import javax.naming.NamingException; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.net.JndiManager; +import org.apache.logging.log4j.core.util.Log4jThread; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.BiConsumer; /** + * 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. + * </p> */ public class JmsManager extends AbstractManager { - static class JmsManagerConfiguration { - private final JndiManager jndiManager; + public static class JmsManagerConfiguration { + private final Properties jndiProperties; private final String connectionFactoryName; private final String destinationName; private final String userName; private final char[] password; + private final boolean immediateFail; + private final boolean retry; + private final long reconnectIntervalMillis; - JmsManagerConfiguration(final JndiManager jndiManager, final String connectionFactoryName, - final String destinationName, final String userName, final char[] password) { - this.jndiManager = jndiManager; + JmsManagerConfiguration(final Properties jndiProperties, final String connectionFactoryName, + final String destinationName, final String userName, final char[] password, final boolean immediateFail, + final long reconnectIntervalMillis) { + this.jndiProperties = jndiProperties; this.connectionFactoryName = connectionFactoryName; this.destinationName = destinationName; this.userName = userName; this.password = password; + this.immediateFail = immediateFail; + this.reconnectIntervalMillis = reconnectIntervalMillis; + this.retry = reconnectIntervalMillis > 0; } - /** - * Does not include the password. - */ - @Override - public String toString() { - return "JmsConfiguration [jndiManager=" + jndiManager + ", connectionFactoryName=" + connectionFactoryName - + ", destinationName=" + destinationName + ", userName=" + userName + "]"; + public String getConnectionFactoryName() { + return connectionFactoryName; + } + + public String getDestinationName() { + return destinationName; + } + + public JndiManager getJndiManager() { + return JndiManager.getJndiManager(getJndiProperties()); + } + + public Properties getJndiProperties() { + return jndiProperties; } + + public char[] getPassword() { + return password; + } + + public long getReconnectIntervalMillis() { + return reconnectIntervalMillis; + } + + public String getUserName() { + return userName; + } + + public boolean isImmediateFail() { + return immediateFail; + } + + public boolean isRetry() { + return retry; + } + } private static class JmsManagerFactory implements ManagerFactory<JmsManager, JmsManagerConfiguration> { @@ -83,7 +127,72 @@ public class JmsManager extends AbstractManager { } } + /** + * Handles reconnecting to a Socket on a Thread. + */ + private class Reconnector extends Log4jThread { + + private final CountDownLatch latch = new CountDownLatch(1); + + private volatile boolean shutdown = false; + + private final Object owner; + + public Reconnector(final Object owner) { + super("JmsManager-Reconnector"); + this.owner = owner; + } + + public void latch() { + try { + latch.await(); + } catch (final InterruptedException ex) { + // Ignore the exception. + } + } + + void reconnect() throws NamingException, JMSException { + final JndiManager jndiManager2 = getJndiManager(); + final Connection connection2 = createConnection(jndiManager2); + final Session session2 = createSession(connection2); + final Destination destination2 = createDestination(jndiManager2); + final MessageProducer messageProducer2 = createMessageProducer(session2, destination2); + connection2.start(); + synchronized (owner) { + jndiManager = jndiManager2; + connection = connection2; + session = session2; + destination = destination2; + messageProducer = messageProducer2; + reconnector = null; + shutdown = true; + } + LOGGER.debug("Connection reestablished to {}", configuration); + } + + @Override + public void run() { + while (!shutdown) { + try { + sleep(configuration.getReconnectIntervalMillis()); + reconnect(); + } catch (final InterruptedException | JMSException | NamingException e) { + LOGGER.debug("Cannot reestablish JMS connection to {}: {}", configuration, e.getLocalizedMessage(), + e); + } finally { + latch.countDown(); + } + } + } + + public void shutdown() { + shutdown = true; + } + + } + private static final Logger LOGGER = StatusLogger.getLogger(); + static final JmsManagerFactory FACTORY = new JmsManagerFactory(); /** @@ -91,8 +200,6 @@ public class JmsManager extends AbstractManager { * * @param name * The name to use for this JmsManager. - * @param jndiManager - * The JndiManager to look up JMS information through. * @param connectionFactoryName * The binding name for the {@link javax.jms.ConnectionFactory}. * @param destinationName @@ -101,69 +208,121 @@ public class JmsManager extends AbstractManager { * The userName to connect with or {@code null} for no authentication. * @param password * The password to use with the given userName or {@code null} for no authentication. - * @return The JmsManager as configured. - * @deprecated Use the other getJmsManager() method - */ - @Deprecated - public static JmsManager getJmsManager(final String name, final JndiManager jndiManager, - final String connectionFactoryName, final String destinationName, final String userName, - final String password) { - final JmsManagerConfiguration configuration = new JmsManagerConfiguration(jndiManager, connectionFactoryName, - destinationName, userName, password == null ? null : password.toCharArray()); - return getManager(name, FACTORY, configuration); - } - - /** - * Gets a JmsManager using the specified configuration parameters. - * - * @param name - * The name to use for this JmsManager. + * @param immediateFail + * Whether or not to fail immediately with a {@link AppenderLoggingException} when connecting to JMS + * fails. + * @param reconnectIntervalMillis + * How to log sleep in milliseconds before trying to reconnect to JMS. * @param jndiManager * The JndiManager to look up JMS information through. - * @param connectionFactoryName - * The binding name for the {@link javax.jms.ConnectionFactory}. - * @param destinationName - * The binding name for the {@link javax.jms.Destination}. - * @param userName - * The userName to connect with or {@code null} for no authentication. - * @param password - * The password to use with the given userName or {@code null} for no authentication. * @return The JmsManager as configured. */ - public static JmsManager getJmsManager(final String name, final JndiManager jndiManager, + public static JmsManager getJmsManager(final String name, final Properties jndiProperties, final String connectionFactoryName, final String destinationName, final String userName, - final char[] password) { - final JmsManagerConfiguration configuration = new JmsManagerConfiguration(jndiManager, connectionFactoryName, - destinationName, userName, password); + final char[] password, final boolean immediateFail, final long reconnectIntervalMillis) { + final JmsManagerConfiguration configuration = new JmsManagerConfiguration(jndiProperties, connectionFactoryName, + destinationName, userName, password, immediateFail, reconnectIntervalMillis); return getManager(name, FACTORY, configuration); } - private final JndiManager jndiManager; - private final Connection connection; - private final Session session; - - private final Destination destination; - private final JmsManagerConfiguration configuration; - private final MessageProducer producer; + private volatile Reconnector reconnector; + private volatile JndiManager jndiManager; + private volatile Connection connection; + private volatile Session session; + private volatile Destination destination; + private volatile MessageProducer messageProducer; - private JmsManager(final String name, final JmsManagerConfiguration configuration) - throws NamingException, JMSException { + private JmsManager(final String name, final JmsManagerConfiguration configuration) { 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 == null ? null : String.valueOf(configuration.password)); - } else { - this.connection = connectionFactory.createConnection(); + this.jndiManager = configuration.getJndiManager(); + try { + this.connection = createConnection(this.jndiManager); + this.session = createSession(this.connection); + this.destination = createDestination(this.jndiManager); + this.messageProducer = createMessageProducer(this.session, this.destination); + this.connection.start(); + } catch (NamingException | JMSException e) { + this.reconnector = createReconnector(); + this.reconnector.start(); + } + } + + private boolean closeConnection() { + if (connection == null) { + return true; + } + final Connection temp = connection; + connection = null; + try { + temp.close(); + return true; + } catch (final JMSException e) { + StatusLogger.getLogger().debug( + "Caught exception closing JMS Connection: {} ({}); contiuing JMS manager shutdown", + e.getLocalizedMessage(), temp, e); + return false; + } + } + + private boolean closeJndiManager() { + if (jndiManager == null) { + return true; + } + final JndiManager tmp = jndiManager; + jndiManager = null; + tmp.close(); + return true; + } + + private boolean closeMessageProducer() { + if (messageProducer == null) { + return true; + } + final MessageProducer temp = messageProducer; + messageProducer = null; + try { + temp.close(); + return true; + } catch (final JMSException e) { + StatusLogger.getLogger().debug( + "Caught exception closing JMS MessageProducer: {} ({}); contiuing JMS manager shutdown", + e.getLocalizedMessage(), temp, e); + return false; + } + } + + private boolean closeSession() { + if (session == null) { + return true; } - this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - this.destination = this.jndiManager.lookup(configuration.destinationName); - this.producer = createMessageProducer(); - this.connection.start(); + final Session temp = session; + session = null; + try { + temp.close(); + return true; + } catch (final JMSException e) { + StatusLogger.getLogger().debug( + "Caught exception closing JMS Session: {} ({}); contiuing JMS manager shutdown", + e.getLocalizedMessage(), temp, e); + return false; + } + } + + private Connection createConnection(final JndiManager jndiManager) throws NamingException, JMSException { + final ConnectionFactory connectionFactory = jndiManager.lookup(configuration.getConnectionFactoryName()); + if (configuration.getUserName() != null && configuration.getPassword() != null) { + return connectionFactory.createConnection(configuration.getUserName(), + configuration.getPassword() == null ? null : String.valueOf(configuration.getPassword())); + } + return connectionFactory.createConnection(); + + } + + private Destination createDestination(final JndiManager jndiManager) throws NamingException { + return jndiManager.lookup(configuration.getDestinationName()); } /** @@ -196,6 +355,12 @@ public class JmsManager extends AbstractManager { return this.session.createObjectMessage(object); } + private void createMessageAndSend(final LogEvent event, final Serializable serializable) throws JMSException { + final Message message = createMessage(serializable); + message.setJMSTimestamp(event.getTimeMillis()); + messageProducer.send(message); + } + /** * Creates a MessageConsumer on this Destination using the current Session. * @@ -209,17 +374,40 @@ public class JmsManager extends AbstractManager { /** * Creates a MessageProducer on this Destination using the current Session. * + * @param session + * The JMS Session to use to create the MessageProducer + * @param destination + * The JMS Destination for the MessageProducer * @return A MessageProducer on this Destination. * @throws JMSException */ - public MessageProducer createMessageProducer() throws JMSException { - return this.session.createProducer(this.destination); + public MessageProducer createMessageProducer(final Session session, final Destination destination) throws JMSException { + return session.createProducer(destination); + } + + private Reconnector createReconnector() { + final Reconnector recon = new Reconnector(this); + recon.setDaemon(true); + recon.setPriority(Thread.MIN_PRIORITY); + return recon; + } + + private Session createSession(final Connection connection) throws JMSException { + return connection.createSession(false, Session.AUTO_ACKNOWLEDGE); } - JmsManagerConfiguration getJmsManagerConfiguration() { + public JmsManagerConfiguration getJmsManagerConfiguration() { return configuration; } + JndiManager getJndiManager() { + return configuration.getJndiManager(); + } + + <T> T lookup(final String destinationName) throws NamingException { + return this.jndiManager.lookup(destinationName); + } + 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. @@ -239,24 +427,56 @@ public class JmsManager extends AbstractManager { @Override protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - boolean closed = true; - try { - this.session.close(); - } catch (final JMSException ignored) { - // ignore - closed = false; - } - try { - this.connection.close(); - } catch (final JMSException ignored) { - // ignore - closed = false; + if (reconnector != null) { + reconnector.shutdown(); + reconnector.interrupt(); + reconnector = null; } + boolean closed = false; + closed &= closeJndiManager(); + closed &= closeMessageProducer(); + closed &= closeSession(); + closed &= closeConnection(); return closed && this.jndiManager.stop(timeout, timeUnit); } - void send(final Message message) throws JMSException { - producer.send(message); + void send(final LogEvent event, final Serializable serializable) { + if (messageProducer == null) { + if (reconnector != null && !configuration.isImmediateFail()) { + reconnector.latch(); + } + if (messageProducer == null) { + throw new AppenderLoggingException( + "Error sending to JMS Manager '" + getName() + "': JMS message producer not available"); + } + } + synchronized (this) { + try { + createMessageAndSend(event, serializable); + } catch (final JMSException causeEx) { + if (configuration.isRetry() && reconnector == null) { + reconnector = createReconnector(); + try { + closeJndiManager(); + reconnector.reconnect(); + } catch (NamingException | JMSException reconnEx) { + LOGGER.debug("Cannot reestablish JMS connection to {}: {}; starting reconnector thread {}", + configuration, reconnEx.getLocalizedMessage(), reconnector.getName(), reconnEx); + reconnector.start(); + throw new AppenderLoggingException( + String.format("Error sending to %s for %s", getName(), configuration), causeEx); + } + try { + createMessageAndSend(event, serializable); + } catch (final JMSException e) { + throw new AppenderLoggingException( + String.format("Error sending to %s after reestablishing connection for %s", getName(), + configuration), + causeEx); + } + } + } + } } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java index 7923190..2384680 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java @@ -29,7 +29,7 @@ import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.util.JndiCloser; /** - * JNDI {@link javax.naming.Context} manager. + * Manages a JNDI {@link javax.naming.Context}. * * @since 2.1 */ @@ -55,6 +55,7 @@ public class JndiManager extends AbstractManager { /** * Gets a named JndiManager using the default {@link javax.naming.InitialContext}. + * * @param name the name of the JndiManager instance to create or use if available * @return a default JndiManager */ @@ -76,22 +77,64 @@ public class JndiManager extends AbstractManager { * @return the JndiManager for the provided parameters. */ public static JndiManager getJndiManager(final String initialContextFactoryName, - final String providerURL, - final String urlPkgPrefixes, - final String securityPrincipal, - final String securityCredentials, - final Properties additionalProperties) { - final String name = JndiManager.class.getName() + '@' + JndiManager.class.hashCode(); + final String providerURL, + final String urlPkgPrefixes, + final String securityPrincipal, + final String securityCredentials, + final Properties additionalProperties) { + Properties properties = createProperties(initialContextFactoryName, providerURL, urlPkgPrefixes, + securityPrincipal, securityCredentials, additionalProperties); + return getManager(createManagerName(), FACTORY, properties); + } + + /** + * Gets a JndiManager with the provided configuration information. + * + * @param properties JNDI properties, usually created by calling {@link #createProperties(String, String, String, String, String, Properties)}. + * @return the JndiManager for the provided parameters. + * @see #createProperties(String, String, String, String, String, Properties) + * @since 2.9 + */ + public static JndiManager getJndiManager(final Properties properties) { + return getManager(createManagerName(), FACTORY, properties); + } + + private static String createManagerName() { + return JndiManager.class.getName() + '@' + JndiManager.class.hashCode(); + } + + /** + * Creates JNDI Properties with the provided configuration information. + * + * @param initialContextFactoryName + * Fully qualified class name of an implementation of {@link javax.naming.spi.InitialContextFactory}. + * @param providerURL + * The provider URL to use for the JNDI connection (specific to the above factory). + * @param urlPkgPrefixes + * A colon-separated list of package prefixes for the class name of the factory class that will create a + * URL context factory + * @param securityPrincipal + * The name of the identity of the Principal. + * @param securityCredentials + * The security credentials of the Principal. + * @param additionalProperties + * Any additional JNDI environment properties to set or {@code null} for none. + * @return the Properties for the provided parameters. + * @since 2.9 + */ + public static Properties createProperties(final String initialContextFactoryName, final String providerURL, + final String urlPkgPrefixes, final String securityPrincipal, final String securityCredentials, + final Properties additionalProperties) { if (initialContextFactoryName == null) { - return getManager(name, FACTORY, null); + return null; } final Properties properties = new Properties(); properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName); if (providerURL != null) { properties.setProperty(Context.PROVIDER_URL, providerURL); } else { - LOGGER.warn("The JNDI InitialContextFactory class name [{}] was provided, but there was no associated " + - "provider URL. This is likely to cause problems.", initialContextFactoryName); + LOGGER.warn("The JNDI InitialContextFactory class name [{}] was provided, but there was no associated " + + "provider URL. This is likely to cause problems.", initialContextFactoryName); } if (urlPkgPrefixes != null) { properties.setProperty(Context.URL_PKG_PREFIXES, urlPkgPrefixes); @@ -102,13 +145,13 @@ public class JndiManager extends AbstractManager { properties.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials); } else { LOGGER.warn("A security principal [{}] was provided, but with no corresponding security credentials.", - securityPrincipal); + securityPrincipal); } } if (additionalProperties != null) { properties.putAll(additionalProperties); } - return getManager(name, FACTORY, properties); + return properties; } @Override @@ -122,7 +165,7 @@ public class JndiManager extends AbstractManager { * @param name name of the object to look up. * @param <T> the type of the object. * @return the named object if it could be located. - * @throws NamingException + * @throws NamingException if a naming exception is encountered */ @SuppressWarnings("unchecked") public <T> T lookup(final String name) throws NamingException { @@ -144,6 +187,7 @@ public class JndiManager extends AbstractManager { @Override public String toString() { - return "JndiManager [context=" + context + "]"; + return "JndiManager [context=" + context + ", count=" + count + "]"; } + } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java index 70c28cf..ac544ea 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java @@ -30,12 +30,14 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.status.StatusData; import org.apache.logging.log4j.status.StatusListener; import org.apache.logging.log4j.status.StatusLogger; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.junit.WireMockRule; +@Ignore public class HttpAppenderTest { private static final String LOG_MESSAGE = "Hello, world!"; http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index db3cba2..682265e 100644 --- a/pom.xml +++ b/pom.xml @@ -1017,14 +1017,8 @@ <version>${failsafe.plugin.version}</version> <executions> <execution> - <id>integration-tests</id> <goals> <goal>integration-test</goal> - </goals> - </execution> - <execution> - <id>verify</id> - <goals> <goal>verify</goal> </goals> </execution> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c99d604..973fb98 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -76,6 +76,9 @@ <action issue="LOG4J2-1934" dev="ggregory" type="add"> JMS Appender does not know how to recover from a broken connection. </action> + <action issue="LOG4J2-1955" dev="ggregory" type="add"> + JMS Appender should be able connect to a broker (later) even it is not present at configuration time. + </action> <action issue="LOG4J2-1956" dev="ggregory" type="update"> JMS Appender broker password should be a char[], not a String. </action> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/de7487ed/src/site/xdoc/manual/appenders.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml index 528dabe..84834f0 100644 --- a/src/site/xdoc/manual/appenders.xml +++ b/src/site/xdoc/manual/appenders.xml @@ -1351,6 +1351,20 @@ public class ConnectionFactory { <a href="#FailoverAppender">FailoverAppender</a>.</td> </tr> <tr> + <td>immediateFail</td> + <td>boolean</td> + <td>When set to true, log events will not wait to try to reconnect and will fail immediately if the + JMS resources are not available. New in 2.9.</td> + </tr> + <tr> + <td>reconnectIntervalMillis</td> + <td>long</td> + <td>If set to a value greater than 0, after an error, the JMSManager will attempt to reconnect to + the broker after waiting the specified number of milliseconds. If the reconnect fails then + an exception will be thrown (which can be caught by the application if <code>ignoreExceptions</code> is + set to <code>false</code>). New in 2.9.</td> + </tr> + <tr> <td>urlPkgPrefixes</td> <td>String</td> <td>A colon-separated list of package prefixes for the class name of the factory class that will create
