Skip to content

Commit 8a54b39

Browse files
authored
Merge pull request #208 from Willhow-Gao/main
[issue#255] Close log4j2 loggerContext when the module is uninstalled
2 parents 029ce76 + d13c921 commit 8a54b39

5 files changed

Lines changed: 227 additions & 2 deletions

File tree

koupleless-base-plugin/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
<version>${sofa.ark.version}</version>
6464
<scope>test</scope>
6565
</dependency>
66+
<dependency>
67+
<groupId>org.apache.logging.log4j</groupId>
68+
<artifactId>log4j-core</artifactId>
69+
<scope>provided</scope>
70+
</dependency>
6671

6772
</dependencies>
6873

koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private void registerEventHandler(final PluginContext context) {
4848
eventAdminService.register(new ShutdownExecutorServicesOnUninstallEventHandler());
4949
eventAdminService.register(new CancelTimersOnUninstallEventHandler());
5050
eventAdminService.register(new ForceStopThreadsOnUninstallEventHandler());
51+
eventAdminService.register(new StopLoggerCxtAfterBizStopEventHandler());
5152
}
5253

5354
/** {@inheritDoc} */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.alipay.sofa.koupleless.plugin.manager.handler;
18+
19+
import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent;
20+
import com.alipay.sofa.ark.spi.model.Biz;
21+
import com.alipay.sofa.ark.spi.service.event.EventHandler;
22+
import com.alipay.sofa.common.utils.ClassUtil;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.core.LoggerContext;
25+
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
26+
import org.apache.logging.log4j.core.selector.ContextSelector;
27+
import org.apache.logging.log4j.spi.LoggerContextFactory;
28+
import org.slf4j.Logger;
29+
30+
import java.util.List;
31+
import java.util.concurrent.TimeUnit;
32+
33+
import static java.lang.Integer.parseInt;
34+
import static java.lang.System.getProperty;
35+
import static org.slf4j.LoggerFactory.getLogger;
36+
37+
/**
38+
* @author gaowh
39+
*/
40+
public class StopLoggerCxtAfterBizStopEventHandler implements EventHandler<BeforeBizStopEvent> {
41+
42+
private static final Logger LOGGER = getLogger(
43+
StopLoggerCxtAfterBizStopEventHandler.class);
44+
45+
public static final String LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND = "com.alipay.koupleless.loggerContext.stop.timeout.millisecond";
46+
public static final String LOG4J2_FACTORY_CLASS_NAME = "org.apache.logging.log4j.core.impl.Log4jContextFactory";
47+
48+
@Override
49+
public void handleEvent(BeforeBizStopEvent beforeBizStopEvent) {
50+
if (ClassUtil.isPresent(LOG4J2_FACTORY_CLASS_NAME)) {
51+
releaseLog4j2LogCtx(beforeBizStopEvent);
52+
}
53+
}
54+
55+
private void releaseLog4j2LogCtx(BeforeBizStopEvent event) {
56+
try {
57+
ClassLoader bizClassLoader = event.getSource().getBizClassLoader();
58+
LoggerContextFactory factory = LogManager.getFactory();
59+
if (factory instanceof Log4jContextFactory) {
60+
String ctxName = Integer.toHexString(System.identityHashCode(bizClassLoader));
61+
ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
62+
List<LoggerContext> contextList = selector.getLoggerContexts();
63+
int stopTimeoutMillisecond = parseInt(
64+
getProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND, "300"));
65+
// Traverse the loggerContext of the selector and find the loggerContext that belongs to the bizClassloader module and close it
66+
for (LoggerContext ctx : contextList) {
67+
if (ctx.getName().equals(ctxName)) {
68+
boolean stop = ctx.stop(stopTimeoutMillisecond, TimeUnit.MILLISECONDS);
69+
LOGGER.info("try stop {}:{}'s logger context {},result={}",
70+
event.getSource().getBizName(), event.getSource().getBizVersion(),
71+
ctxName, stop);
72+
}
73+
}
74+
return;
75+
}
76+
LOGGER.info("Not Log4jContextFactory, do nothing");
77+
} catch (Exception exception) {
78+
Biz source = event.getSource();
79+
LOGGER.error("release {}:{}'s log4j2LogCtx failed,event id = {}", source.getBizName(),
80+
source.getBizVersion(), source.getIdentity(), exception);
81+
}
82+
}
83+
84+
// BizUninstallEventHandler will clean bizContext and classloader, The loggerContext needs to be closed before it
85+
@Override
86+
public int getPriority() {
87+
return DEFAULT_PRECEDENCE - 1;
88+
}
89+
}

koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivatorTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public List<ServiceReference> referenceServices(ServiceFilter serviceFilter) {
131131
};
132132

133133
serverlessRuntimeActivator.start(pluginContext);
134-
assertEquals(5, eventhandlers.size());
134+
assertEquals(6, eventhandlers.size());
135135
assertEquals(ShutdownExecutorServicesOnUninstallEventHandler.class,
136136
eventhandlers.get(2).getClass());
137137
assertEquals(CancelTimersOnUninstallEventHandler.class, eventhandlers.get(3).getClass());
@@ -149,7 +149,7 @@ public void testRegisterCount() {
149149
when(pluginContext.referenceService(EventAdminService.class)).thenReturn((impl));
150150

151151
serverlessRuntimeActivator.start(pluginContext);
152-
verify(eventAdminService, times(5)).register(any());
152+
verify(eventAdminService, times(6)).register(any());
153153
}
154154

155155
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.alipay.sofa.koupleless.plugin.manager.handler;
18+
19+
import com.alipay.sofa.ark.container.model.BizModel;
20+
import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent;
21+
import org.apache.logging.log4j.LogManager;
22+
import org.apache.logging.log4j.core.LoggerContext;
23+
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
24+
import org.apache.logging.log4j.core.selector.ContextSelector;
25+
import org.apache.logging.log4j.util.PropertiesUtil;
26+
import org.junit.*;
27+
28+
import static com.alipay.sofa.koupleless.plugin.manager.handler.StopLoggerCxtAfterBizStopEventHandler.LOG4J2_FACTORY_CLASS_NAME;
29+
import static com.alipay.sofa.koupleless.plugin.manager.handler.StopLoggerCxtAfterBizStopEventHandler.LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND;
30+
import static java.lang.System.clearProperty;
31+
import static java.lang.Thread.currentThread;
32+
import static org.junit.Assert.assertEquals;
33+
import static org.mockito.Mockito.mockStatic;
34+
35+
import org.mockito.MockedStatic;
36+
37+
import java.net.URISyntaxException;
38+
import java.util.List;
39+
import java.util.Properties;
40+
41+
/**
42+
* @author gaowh
43+
*/
44+
public class StopLoggerCxtAfterBizStopEventHandlerTest {
45+
private final ClassLoader bizClassLoader = currentThread()
46+
.getContextClassLoader();
47+
private static MockedStatic<PropertiesUtil> propertiesUtil;
48+
49+
private static final ClassLoader rootClassLoader = new ClassLoader() {
50+
};
51+
52+
@BeforeClass
53+
public static void beforeClass() {
54+
propertiesUtil = mockStatic(PropertiesUtil.class);
55+
Properties properties = new Properties();
56+
properties.setProperty("log4j2.loggerContextFactory", LOG4J2_FACTORY_CLASS_NAME);
57+
PropertiesUtil mockProperties = new PropertiesUtil(properties);
58+
propertiesUtil.when(PropertiesUtil::getProperties).thenReturn(mockProperties);
59+
}
60+
61+
@AfterClass
62+
public static void afterClass() {
63+
if (propertiesUtil != null) {
64+
propertiesUtil.close();
65+
}
66+
}
67+
68+
@Before
69+
public void setUp() {
70+
clearAllLoggerContext();
71+
clearProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND);
72+
currentThread().setContextClassLoader(bizClassLoader);
73+
}
74+
75+
@After
76+
public void tearDown() {
77+
currentThread().setContextClassLoader(bizClassLoader);
78+
clearProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND);
79+
clearAllLoggerContext();
80+
}
81+
82+
@Test
83+
public void testCloseLoggerContext() {
84+
StopLoggerCxtAfterBizStopEventHandler handler = new StopLoggerCxtAfterBizStopEventHandler();
85+
LogManager.getContext(rootClassLoader, false);
86+
Log4jContextFactory contextFactory = (Log4jContextFactory) LogManager.getFactory();
87+
String name = contextFactory.getSelector().getLoggerContexts().get(0).getName();
88+
LogManager.getContext(bizClassLoader, false);
89+
assertEquals("Should have two logger context initially", 2,
90+
contextFactory.getSelector().getLoggerContexts().size());
91+
BizModel bizModel = new BizModel();
92+
bizModel.setClassLoader(bizClassLoader);
93+
BeforeBizStopEvent event = new BeforeBizStopEvent(bizModel);
94+
handler.handleEvent(event);
95+
int afterSize = contextFactory.getSelector().getLoggerContexts().size();
96+
assertEquals("only one logger context should be closed", afterSize, 1);
97+
assertEquals("active logger context name is ", name,
98+
contextFactory.getSelector().getLoggerContexts().get(0).getName());
99+
}
100+
101+
@Test
102+
public void testCloseLoggerContextWithTimeout() throws URISyntaxException {
103+
System.setProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND, "2");
104+
testCloseLoggerContext();
105+
}
106+
107+
@Test
108+
public void testHandleNullClassLoader() {
109+
StopLoggerCxtAfterBizStopEventHandler handler = new StopLoggerCxtAfterBizStopEventHandler();
110+
LogManager.getContext(bizClassLoader, false);
111+
Log4jContextFactory contextFactory = (Log4jContextFactory) LogManager.getFactory();
112+
int beforeSize = contextFactory.getSelector().getLoggerContexts().size();
113+
assertEquals("Should have one logger context initially", beforeSize, 1);
114+
BizModel bizModel = new BizModel();
115+
// Don't set ClassLoader
116+
BeforeBizStopEvent event = new BeforeBizStopEvent(bizModel);
117+
handler.handleEvent(event);
118+
int afterSize = contextFactory.getSelector().getLoggerContexts().size();
119+
assertEquals("All logger contexts should be active", afterSize, 1);
120+
}
121+
122+
private void clearAllLoggerContext() {
123+
ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector();
124+
List<LoggerContext> contextList = selector.getLoggerContexts();
125+
for (LoggerContext loggerContext : contextList) {
126+
loggerContext.stop();
127+
}
128+
}
129+
130+
}

0 commit comments

Comments
 (0)