Skip to content

Commit 028550c

Browse files
committed
GH-1148: Log4jAppender - fix initialization cycle
Resolves #1148 Defer creation of all Spring static loggers from appender until the appender is initialized. The first logger (e.g. Spring Boot) initializes the static `LoggerContext` field in `LogAdapter.Log4jLog`; this, in turn initializes the appender. However, the appender references Spring objects that have static loggers. This causes the `Log4jLog` CTOR to be called before the field is initialized, causing an NPE. Defer creation of all static loggers until the appender is fully initialized. Tested with the log4j2 sample app. Also add a test for async sends. **cherry-pick to 2.1.x** * NOSONAR - inconsistent sync
1 parent 2068b5c commit 028550c

File tree

3 files changed

+61
-43
lines changed

3 files changed

+61
-43
lines changed

spring-rabbit/src/main/java/org/springframework/amqp/rabbit/log4j2/AmqpAppender.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public class AmqpAppender extends AbstractAppender {
123123
/**
124124
* The template.
125125
*/
126-
private final RabbitTemplate rabbitTemplate = new RabbitTemplate();
126+
private RabbitTemplate rabbitTemplate;
127127

128128
/**
129129
* Where LoggingEvents are queued to send.
@@ -256,18 +256,12 @@ public static AmqpAppender createAppender(// NOSONAR NCSS line count
256256
* Submit the required number of senders into the pool.
257257
*/
258258
private void startSenders() {
259-
this.rabbitTemplate.setConnectionFactory(this.manager.connectionFactory);
260259
if (this.manager.async) {
260+
this.manager.senderPool = Executors.newCachedThreadPool();
261261
for (int i = 0; i < this.manager.senderPoolSize; i++) {
262262
this.manager.senderPool.submit(new EventSender());
263263
}
264264
}
265-
else if (this.manager.maxSenderRetries > 0) {
266-
RetryTemplate retryTemplate = new RetryTemplate();
267-
RetryPolicy retryPolicy = new SimpleRetryPolicy(this.manager.maxSenderRetries);
268-
retryTemplate.setRetryPolicy(retryPolicy);
269-
this.rabbitTemplate.setRetryTemplate(retryTemplate);
270-
}
271265
}
272266

273267
@Override
@@ -293,6 +287,22 @@ protected Message postProcessMessageBeforeSend(Message message, Event event) {
293287
}
294288

295289
protected void sendEvent(Event event, Map<?, ?> properties) {
290+
synchronized (this) {
291+
if (this.rabbitTemplate == null) {
292+
if (this.manager.activateOptions()) {
293+
this.rabbitTemplate = new RabbitTemplate(this.manager.connectionFactory);
294+
if (!this.manager.async && this.manager.maxSenderRetries > 0) {
295+
RetryTemplate retryTemplate = new RetryTemplate();
296+
RetryPolicy retryPolicy = new SimpleRetryPolicy(this.manager.maxSenderRetries);
297+
retryTemplate.setRetryPolicy(retryPolicy);
298+
this.rabbitTemplate.setRetryTemplate(retryTemplate);
299+
}
300+
}
301+
else {
302+
throw new AmqpException("Cannot create template");
303+
}
304+
}
305+
}
296306
LogEvent logEvent = event.getEvent();
297307
String name = logEvent.getLoggerName();
298308
Level level = logEvent.getLevel();
@@ -358,7 +368,7 @@ protected void doSend(Event event, LogEvent logEvent, MessageProperties amqpProp
358368
message = new Message(msgBody.toString().getBytes(), amqpProps); //NOSONAR (default charset)
359369
}
360370
message = postProcessMessageBeforeSend(message, event);
361-
this.rabbitTemplate.send(this.manager.exchangeName, routingKey, message);
371+
this.rabbitTemplate.send(this.manager.exchangeName, routingKey, message); // NOSONAR (sync)
362372
}
363373
catch (AmqpException e) {
364374
int retries = event.incrementRetries();
@@ -651,7 +661,7 @@ protected AmqpManager(LoggerContext loggerContext, String name) {
651661
super(loggerContext, name);
652662
}
653663

654-
private boolean activateOptions() {
664+
boolean activateOptions() {
655665
ConnectionFactory rabbitConnectionFactory = createRabbitConnectionFactory();
656666
if (rabbitConnectionFactory != null) {
657667
Assert.state(this.applicationId != null, "applicationId is required");
@@ -673,7 +683,6 @@ private boolean activateOptions() {
673683
this.clientConnectionProperties);
674684
}
675685
setUpExchangeDeclaration();
676-
this.senderPool = Executors.newCachedThreadPool();
677686
return true;
678687
}
679688
return false;

spring-rabbit/src/test/java/org/springframework/amqp/rabbit/log4j2/AmqpAppenderTests.java

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2017 the original author or authors.
2+
* Copyright 2016-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.amqp.rabbit.log4j2;
1818

19+
import static org.hamcrest.CoreMatchers.equalTo;
1920
import static org.hamcrest.Matchers.instanceOf;
2021
import static org.hamcrest.Matchers.is;
2122
import static org.hamcrest.Matchers.startsWith;
@@ -37,6 +38,7 @@
3738
import java.net.URI;
3839
import java.util.Map;
3940
import java.util.concurrent.ArrayBlockingQueue;
41+
import java.util.concurrent.BlockingQueue;
4042
import java.util.concurrent.LinkedBlockingQueue;
4143

4244
import org.apache.logging.log4j.LogManager;
@@ -54,6 +56,7 @@
5456
import org.springframework.amqp.core.Queue;
5557
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
5658
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
59+
import org.springframework.amqp.rabbit.connection.RabbitUtils;
5760
import org.springframework.amqp.rabbit.core.RabbitAdmin;
5861
import org.springframework.amqp.rabbit.core.RabbitTemplate;
5962
import org.springframework.amqp.rabbit.junit.BrokerRunning;
@@ -178,55 +181,58 @@ public void testProperties() {
178181
assertEquals(ArrayBlockingQueue.class, events.getClass());
179182
}
180183

181-
@Test
182-
public void testAmqpAppenderEventQueueTypeDefaultsToLinkedBlockingQueue() throws Exception {
183-
Logger logger = LogManager.getLogger("default_queue_logger");
184-
AmqpAppender appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
185-
Map.class).get("rabbitmq_default_queue");
186-
187-
Object events = TestUtils.getPropertyValue(appender, "events");
188-
189-
Object manager = TestUtils.getPropertyValue(appender, "manager");
190-
assertTrue(TestUtils.getPropertyValue(manager, "addMdcAsHeaders", Boolean.class));
191-
192-
assertThat(events, instanceOf(LinkedBlockingQueue.class));
193-
}
194-
195184
@Test
196185
public void testSaslConfig() {
197186
Logger logger = LogManager.getLogger("sasl");
198187
AmqpAppender appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
199188
Map.class).get("sasl1");
200-
SaslConfig saslConfig =
201-
TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
202-
ConnectionFactory.class)
203-
.getSaslConfig();
189+
SaslConfig saslConfig = RabbitUtils.stringToSaslConfig(
190+
TestUtils.getPropertyValue(appender, "manager.saslConfig", String.class),
191+
mock(ConnectionFactory.class));
204192
assertThat(saslConfig, instanceOf(DefaultSaslConfig.class));
205193
assertEquals("PLAIN", TestUtils.getPropertyValue(saslConfig, "mechanism"));
206194
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
207195
Map.class).get("sasl2");
208-
saslConfig =
209-
TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
210-
ConnectionFactory.class)
211-
.getSaslConfig();
196+
saslConfig = RabbitUtils.stringToSaslConfig(
197+
TestUtils.getPropertyValue(appender, "manager.saslConfig", String.class),
198+
mock(ConnectionFactory.class));
212199
assertThat(saslConfig, instanceOf(DefaultSaslConfig.class));
213200
assertEquals("EXTERNAL", TestUtils.getPropertyValue(saslConfig, "mechanism"));
214201
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
215202
Map.class).get("sasl3");
216-
saslConfig =
217-
TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
218-
ConnectionFactory.class)
219-
.getSaslConfig();
203+
saslConfig = RabbitUtils.stringToSaslConfig(
204+
TestUtils.getPropertyValue(appender, "manager.saslConfig", String.class),
205+
mock(ConnectionFactory.class));
220206
assertThat(saslConfig, instanceOf(JDKSaslConfig.class));
221207
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
222208
Map.class).get("sasl4");
223-
saslConfig =
224-
TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
225-
ConnectionFactory.class)
226-
.getSaslConfig();
209+
saslConfig = RabbitUtils.stringToSaslConfig(
210+
TestUtils.getPropertyValue(appender, "manager.saslConfig", String.class),
211+
mock(ConnectionFactory.class));
227212
assertThat(saslConfig, instanceOf(CRDemoMechanism.CRDemoSaslConfig.class));
228213
}
229214

215+
@Test
216+
public void testAmqpAppenderEventQueueTypeDefaultsToLinkedBlockingQueue() throws InterruptedException {
217+
Logger logger = LogManager.getLogger("default_queue_logger");
218+
logger.info("test");
219+
AmqpAppender appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
220+
Map.class).get("rabbitmq_default_queue");
221+
222+
Object events = TestUtils.getPropertyValue(appender, "events");
223+
224+
Object manager = TestUtils.getPropertyValue(appender, "manager");
225+
assertTrue(TestUtils.getPropertyValue(manager, "addMdcAsHeaders", Boolean.class));
226+
227+
assertThat(events.getClass(), equalTo(LinkedBlockingQueue.class));
228+
BlockingQueue<?> queue = (BlockingQueue<?>) events;
229+
int n = 0;
230+
while (n++ < 100 && queue.size() > 0) {
231+
Thread.sleep(100);
232+
}
233+
assertThat(queue.size(), equalTo(0));
234+
}
235+
230236
@Test
231237
public void testUriProperties() {
232238
Logger logger = LogManager.getLogger("bar");

spring-rabbit/src/test/resources/log4j2-amqp-appender.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
connectionName="log4j2Appender"
1515
clientConnectionProperties="foo:bar,baz:qux"
1616
async="false"
17-
senderPoolSize="3" maxSenderRetries="5"
17+
senderPoolSize="3"
18+
maxSenderRetries="5"
1819
bufferSize="10">
1920
<ArrayBlockingQueue/>
2021
</RabbitMQ>
@@ -23,6 +24,8 @@
2324
host="localhost" port="5672" user="guest" password="guest" applicationId="testAppId" charset="UTF-8"
2425
routingKeyPattern="%X{applicationId}.%c.%p"
2526
exchange="log4j2Test_default_queue" deliveryMode="NON_PERSISTENT"
27+
async="true"
28+
senderPoolSize="2"
2629
addMdcAsHeaders="true">
2730
</RabbitMQ>
2831
<RabbitMQ name="rabbitmq_uri"

0 commit comments

Comments
 (0)