|
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