|
16 | 16 | */ |
17 | 17 | package org.apache.logging.log4j.core; |
18 | 18 |
|
19 | | -import static org.hamcrest.MatcherAssert.assertThat; |
20 | | -import static org.hamcrest.Matchers.containsString; |
21 | | -import static org.hamcrest.Matchers.is; |
22 | | -import static org.junit.jupiter.api.Assertions.assertNull; |
23 | | -import static org.junit.jupiter.api.Assertions.assertTrue; |
24 | | - |
25 | | -import java.io.BufferedReader; |
26 | | -import java.io.File; |
27 | | -import java.io.FileReader; |
28 | | -import java.util.concurrent.CountDownLatch; |
29 | | -import java.util.concurrent.TimeUnit; |
30 | | -import org.apache.logging.log4j.LogManager; |
| 19 | +import static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; |
| 20 | +import static org.assertj.core.api.Assertions.assertThat; |
| 21 | + |
| 22 | +import java.util.List; |
| 23 | +import org.apache.logging.log4j.Level; |
31 | 24 | import org.apache.logging.log4j.Logger; |
32 | | -import org.apache.logging.log4j.core.config.ConfigurationFactory; |
33 | | -import org.apache.logging.log4j.core.test.CoreLoggerContexts; |
34 | | -import org.junit.jupiter.api.BeforeAll; |
| 25 | +import org.apache.logging.log4j.core.config.Configuration; |
| 26 | +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; |
| 27 | +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; |
| 28 | +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; |
| 29 | +import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; |
| 30 | +import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; |
| 31 | +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; |
| 32 | +import org.apache.logging.log4j.core.test.appender.ListAppender; |
35 | 33 | import org.junit.jupiter.api.Tag; |
36 | 34 | import org.junit.jupiter.api.Test; |
| 35 | +import org.junit.jupiter.api.TestInfo; |
| 36 | +import org.junitpioneer.jupiter.SetSystemProperty; |
37 | 37 |
|
38 | 38 | @Tag("functional") |
39 | | -public class EventParameterMemoryLeakTest { |
| 39 | +class EventParameterMemoryLeakTest { |
| 40 | + |
| 41 | + @Test |
| 42 | + @SetSystemProperty(key = "log4j2.enableDirectEncoders", value = "true") |
| 43 | + @SetSystemProperty(key = "log4j2.enableThreadLocals", value = "true") |
| 44 | + void parameters_should_be_garbage_collected(final TestInfo testInfo) throws Throwable { |
| 45 | + awaitGarbageCollection(() -> { |
| 46 | + final ListAppender[] appenderRef = {null}; |
| 47 | + final Logger[] loggerRef = {null}; |
| 48 | + try (final LoggerContext ignored = createLoggerContext(testInfo, appenderRef, loggerRef)) { |
| 49 | + |
| 50 | + // Log messages |
| 51 | + final ParameterObject parameter = new ParameterObject("paramValue"); |
| 52 | + loggerRef[0].info("Message with parameter {}", parameter); |
| 53 | + loggerRef[0].info(parameter); |
| 54 | + loggerRef[0].info("test", new ObjectThrowable(parameter)); |
| 55 | + loggerRef[0].info("test {}", "hello", new ObjectThrowable(parameter)); |
40 | 56 |
|
41 | | - @BeforeAll |
42 | | - public static void beforeClass() { |
43 | | - System.setProperty("log4j2.enableThreadlocals", "true"); |
44 | | - System.setProperty("log4j2.enableDirectEncoders", "true"); |
45 | | - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "EventParameterMemoryLeakTest.xml"); |
| 57 | + // Verify the logging |
| 58 | + final List<String> messages = appenderRef[0].getMessages(); |
| 59 | + assertThat(messages).hasSize(4); |
| 60 | + assertThat(messages.get(0)).isEqualTo("Message with parameter %s", parameter.value); |
| 61 | + assertThat(messages.get(1)).isEqualTo(parameter.value); |
| 62 | + assertThat(messages.get(2)) |
| 63 | + .startsWith(String.format("test%n%s: %s", ObjectThrowable.class.getName(), parameter.value)); |
| 64 | + assertThat(messages.get(3)) |
| 65 | + .startsWith( |
| 66 | + String.format("test hello%n%s: %s", ObjectThrowable.class.getName(), parameter.value)); |
| 67 | + |
| 68 | + // Return the GC subject |
| 69 | + return parameter; |
| 70 | + } |
| 71 | + }); |
46 | 72 | } |
47 | 73 |
|
48 | | - @Test |
49 | | - @SuppressWarnings("UnusedAssignment") // parameter set to null to allow garbage collection |
50 | | - public void testParametersAreNotLeaked() throws Exception { |
51 | | - final File file = new File("target", "EventParameterMemoryLeakTest.log"); |
52 | | - assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); |
53 | | - |
54 | | - final Logger log = LogManager.getLogger("com.foo.Bar"); |
55 | | - final CountDownLatch latch = new CountDownLatch(1); |
56 | | - Object parameter = new ParameterObject("paramValue", latch); |
57 | | - log.info("Message with parameter {}", parameter); |
58 | | - log.info(parameter); |
59 | | - log.info("test", new ObjectThrowable(parameter)); |
60 | | - log.info("test {}", "hello", new ObjectThrowable(parameter)); |
61 | | - parameter = null; |
62 | | - CoreLoggerContexts.stopLoggerContext(file); |
63 | | - final BufferedReader reader = new BufferedReader(new FileReader(file)); |
64 | | - final String line1 = reader.readLine(); |
65 | | - final String line2 = reader.readLine(); |
66 | | - final String line3 = reader.readLine(); |
67 | | - // line4 is empty line because of the line separator after throwable pattern |
68 | | - final String line4 = reader.readLine(); |
69 | | - final String line5 = reader.readLine(); |
70 | | - final String line6 = reader.readLine(); |
71 | | - final String line7 = reader.readLine(); |
72 | | - reader.close(); |
73 | | - file.delete(); |
74 | | - assertThat(line1, containsString("Message with parameter paramValue")); |
75 | | - assertThat(line2, containsString("paramValue")); |
76 | | - assertThat(line3, containsString("paramValue")); |
77 | | - assertThat(line4, is("")); |
78 | | - assertThat(line5, containsString("paramValue")); |
79 | | - assertThat(line6, is("")); |
80 | | - assertNull(line7, "Expected only six lines"); |
81 | | - final GarbageCollectionHelper gcHelper = new GarbageCollectionHelper(); |
82 | | - gcHelper.run(); |
83 | | - try { |
84 | | - assertTrue(latch.await(30, TimeUnit.SECONDS), "Parameter should have been garbage collected"); |
85 | | - } finally { |
86 | | - gcHelper.close(); |
87 | | - } |
| 74 | + private static LoggerContext createLoggerContext( |
| 75 | + final TestInfo testInfo, final ListAppender[] appenderRef, final Logger[] loggerRef) { |
| 76 | + final String loggerContextName = String.format("%s-LC", testInfo.getDisplayName()); |
| 77 | + final LoggerContext loggerContext = new LoggerContext(loggerContextName); |
| 78 | + final String appenderName = "LIST"; |
| 79 | + final Configuration configuration = createConfiguration(appenderName); |
| 80 | + loggerContext.start(configuration); |
| 81 | + appenderRef[0] = configuration.getAppender(appenderName); |
| 82 | + assertThat(appenderRef[0]).isNotNull(); |
| 83 | + final Class<?> testClass = testInfo.getTestClass().orElse(null); |
| 84 | + assertThat(testClass).isNotNull(); |
| 85 | + loggerRef[0] = loggerContext.getLogger(testClass); |
| 86 | + return loggerContext; |
| 87 | + } |
| 88 | + |
| 89 | + @SuppressWarnings("SameParameterValue") |
| 90 | + private static Configuration createConfiguration(final String appenderName) { |
| 91 | + final ConfigurationBuilder<BuiltConfiguration> configBuilder = |
| 92 | + ConfigurationBuilderFactory.newConfigurationBuilder(); |
| 93 | + final LayoutComponentBuilder layoutComponentBuilder = |
| 94 | + configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m"); |
| 95 | + final AppenderComponentBuilder appenderComponentBuilder = |
| 96 | + configBuilder.newAppender(appenderName, "List").add(layoutComponentBuilder); |
| 97 | + final RootLoggerComponentBuilder loggerComponentBuilder = |
| 98 | + configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(appenderName)); |
| 99 | + return configBuilder |
| 100 | + .add(appenderComponentBuilder) |
| 101 | + .add(loggerComponentBuilder) |
| 102 | + .build(false); |
88 | 103 | } |
89 | 104 |
|
90 | 105 | private static final class ParameterObject { |
| 106 | + |
91 | 107 | private final String value; |
92 | | - private final CountDownLatch latch; |
93 | 108 |
|
94 | | - ParameterObject(final String value, final CountDownLatch latch) { |
| 109 | + private ParameterObject(final String value) { |
95 | 110 | this.value = value; |
96 | | - this.latch = latch; |
97 | 111 | } |
98 | 112 |
|
99 | 113 | @Override |
100 | 114 | public String toString() { |
101 | 115 | return value; |
102 | 116 | } |
103 | | - |
104 | | - @Override |
105 | | - protected void finalize() throws Throwable { |
106 | | - latch.countDown(); |
107 | | - super.finalize(); |
108 | | - } |
109 | 117 | } |
110 | 118 |
|
111 | 119 | private static final class ObjectThrowable extends RuntimeException { |
| 120 | + |
112 | 121 | private final Object object; |
113 | 122 |
|
114 | | - ObjectThrowable(final Object object) { |
| 123 | + private ObjectThrowable(final Object object) { |
115 | 124 | super(String.valueOf(object)); |
116 | 125 | this.object = object; |
117 | 126 | } |
|
0 commit comments