Skip to content

Commit 9e4ba4f

Browse files
garyrussellartembilan
authored andcommitted
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 fb32210 commit 9e4ba4f

File tree

3 files changed

+45
-27
lines changed

3 files changed

+45
-27
lines changed

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public class AmqpAppender extends AbstractAppender {
126126
/**
127127
* The template.
128128
*/
129-
private final RabbitTemplate rabbitTemplate = new RabbitTemplate();
129+
private RabbitTemplate rabbitTemplate;
130130

131131
/**
132132
* Where LoggingEvents are queued to send.
@@ -250,18 +250,12 @@ public static Builder newBuilder() {
250250
* Submit the required number of senders into the pool.
251251
*/
252252
private void startSenders() {
253-
this.rabbitTemplate.setConnectionFactory(this.manager.connectionFactory);
254253
if (this.manager.async) {
254+
this.manager.senderPool = Executors.newCachedThreadPool();
255255
for (int i = 0; i < this.manager.senderPoolSize; i++) {
256256
this.manager.senderPool.submit(new EventSender());
257257
}
258258
}
259-
else if (this.manager.maxSenderRetries > 0) {
260-
RetryTemplate retryTemplate = new RetryTemplate();
261-
RetryPolicy retryPolicy = new SimpleRetryPolicy(this.manager.maxSenderRetries);
262-
retryTemplate.setRetryPolicy(retryPolicy);
263-
this.rabbitTemplate.setRetryTemplate(retryTemplate);
264-
}
265259
}
266260

267261
@Override
@@ -287,6 +281,22 @@ protected Message postProcessMessageBeforeSend(Message message, Event event) {
287281
}
288282

289283
protected void sendEvent(Event event, Map<?, ?> properties) {
284+
synchronized (this) {
285+
if (this.rabbitTemplate == null) {
286+
if (this.manager.activateOptions()) {
287+
this.rabbitTemplate = new RabbitTemplate(this.manager.connectionFactory);
288+
if (!this.manager.async && this.manager.maxSenderRetries > 0) {
289+
RetryTemplate retryTemplate = new RetryTemplate();
290+
RetryPolicy retryPolicy = new SimpleRetryPolicy(this.manager.maxSenderRetries);
291+
retryTemplate.setRetryPolicy(retryPolicy);
292+
this.rabbitTemplate.setRetryTemplate(retryTemplate);
293+
}
294+
}
295+
else {
296+
throw new AmqpException("Cannot create template");
297+
}
298+
}
299+
}
290300
LogEvent logEvent = event.getEvent();
291301
String name = logEvent.getLoggerName();
292302
Level level = logEvent.getLevel();
@@ -352,7 +362,7 @@ protected void doSend(Event event, LogEvent logEvent, MessageProperties amqpProp
352362
message = new Message(msgBody.toString().getBytes(), amqpProps); //NOSONAR (default charset)
353363
}
354364
message = postProcessMessageBeforeSend(message, event);
355-
this.rabbitTemplate.send(this.manager.exchangeName, routingKey, message);
365+
this.rabbitTemplate.send(this.manager.exchangeName, routingKey, message); // NOSONAR (sync)
356366
}
357367
catch (AmqpException e) {
358368
int retries = event.incrementRetries();
@@ -645,7 +655,7 @@ protected AmqpManager(LoggerContext loggerContext, String name) {
645655
super(loggerContext, name);
646656
}
647657

648-
private boolean activateOptions() {
658+
boolean activateOptions() {
649659
ConnectionFactory rabbitConnectionFactory = createRabbitConnectionFactory();
650660
if (rabbitConnectionFactory != null) {
651661
Assert.state(this.applicationId != null, "applicationId is required");
@@ -667,7 +677,6 @@ private boolean activateOptions() {
667677
this.clientConnectionProperties);
668678
}
669679
setUpExchangeDeclaration();
670-
this.senderPool = Executors.newCachedThreadPool();
671680
return true;
672681
}
673682
return false;
@@ -1182,11 +1191,8 @@ public AmqpAppender build() {
11821191
}
11831192

11841193
AmqpAppender appender = buildInstance(this.name, this.filter, theLayout, this.ignoreExceptions, manager, eventQueue);
1185-
if (manager.activateOptions()) {
1186-
appender.startSenders();
1187-
return appender;
1188-
}
1189-
return null;
1194+
appender.startSenders();
1195+
return appender;
11901196
}
11911197

11921198
/**

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2019 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.
@@ -29,6 +29,7 @@
2929
import java.net.URI;
3030
import java.util.Map;
3131
import java.util.concurrent.ArrayBlockingQueue;
32+
import java.util.concurrent.BlockingQueue;
3233
import java.util.concurrent.LinkedBlockingQueue;
3334

3435
import org.apache.logging.log4j.LogManager;
@@ -45,6 +46,7 @@
4546
import org.springframework.amqp.core.Queue;
4647
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
4748
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
49+
import org.springframework.amqp.rabbit.connection.RabbitUtils;
4850
import org.springframework.amqp.rabbit.core.RabbitAdmin;
4951
import org.springframework.amqp.rabbit.core.RabbitTemplate;
5052
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
@@ -173,31 +175,32 @@ public void testSaslConfig() {
173175
Logger logger = LogManager.getLogger("sasl");
174176
AmqpAppender appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
175177
Map.class).get("sasl1");
176-
assertThat(TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
177-
ConnectionFactory.class).getSaslConfig())
178+
assertThat(RabbitUtils.stringToSaslConfig(TestUtils.getPropertyValue(appender, "manager.saslConfig",
179+
String.class), mock(ConnectionFactory.class)))
178180
.isInstanceOf(DefaultSaslConfig.class)
179181
.hasFieldOrPropertyWithValue("mechanism", "PLAIN");
180182
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
181183
Map.class).get("sasl2");
182-
assertThat(TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
183-
ConnectionFactory.class).getSaslConfig())
184+
assertThat(RabbitUtils.stringToSaslConfig(TestUtils.getPropertyValue(appender, "manager.saslConfig",
185+
String.class), mock(ConnectionFactory.class)))
184186
.isInstanceOf(DefaultSaslConfig.class)
185187
.hasFieldOrPropertyWithValue("mechanism", "EXTERNAL");
186188
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
187189
Map.class).get("sasl3");
188-
assertThat(TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
189-
ConnectionFactory.class).getSaslConfig())
190+
assertThat(RabbitUtils.stringToSaslConfig(TestUtils.getPropertyValue(appender, "manager.saslConfig",
191+
String.class), mock(ConnectionFactory.class)))
190192
.isInstanceOf(JDKSaslConfig.class);
191193
appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
192194
Map.class).get("sasl4");
193-
assertThat(TestUtils.getPropertyValue(appender, "manager.connectionFactory.rabbitConnectionFactory",
194-
ConnectionFactory.class).getSaslConfig())
195+
assertThat(RabbitUtils.stringToSaslConfig(TestUtils.getPropertyValue(appender, "manager.saslConfig",
196+
String.class), mock(ConnectionFactory.class)))
195197
.isInstanceOf(CRDemoMechanism.CRDemoSaslConfig.class);
196198
}
197199

198200
@Test
199-
public void testAmqpAppenderEventQueueTypeDefaultsToLinkedBlockingQueue() {
201+
public void testAmqpAppenderEventQueueTypeDefaultsToLinkedBlockingQueue() throws InterruptedException {
200202
Logger logger = LogManager.getLogger("default_queue_logger");
203+
logger.info("test");
201204
AmqpAppender appender = (AmqpAppender) TestUtils.getPropertyValue(logger, "context.configuration.appenders",
202205
Map.class).get("rabbitmq_default_queue");
203206

@@ -207,6 +210,12 @@ public void testAmqpAppenderEventQueueTypeDefaultsToLinkedBlockingQueue() {
207210
assertThat(TestUtils.getPropertyValue(manager, "addMdcAsHeaders", Boolean.class)).isTrue();
208211

209212
assertThat(events.getClass()).isEqualTo(LinkedBlockingQueue.class);
213+
BlockingQueue<?> queue = (BlockingQueue<?>) events;
214+
int n = 0;
215+
while (n++ < 100 && queue.size() > 0) {
216+
Thread.sleep(100);
217+
}
218+
assertThat(queue).hasSize(0);
210219
}
211220

212221
@Test

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)