Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[issue#255] Close log4j2 loggerContext when the module is uninstalled #208

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Willhow-Gao
Copy link

@Willhow-Gao Willhow-Gao commented Nov 15, 2024

fix issue 255

Summary by CodeRabbit

  • New Features

    • Introduced a new logging event handler to manage logging context during business process stops.
    • Added a new dependency for enhanced logging capabilities.
  • Bug Fixes

    • Improved error handling in the logging context management process.
  • Tests

    • Added unit tests for the new logging event handler to ensure correct behavior during business stop events.

Copy link
Contributor

coderabbitai bot commented Nov 15, 2024

Walkthrough

The changes in this pull request involve modifications to the koupleless-base-plugin project, specifically the addition of a new dependency and the introduction of a new event handler. A dependency on log4j-core has been added to the pom.xml file, enabling logging capabilities. Additionally, the ServerlessRuntimeActivator class has been updated to register a new event handler, StopLoggerCxtAfterBizStopEventHandler, which manages the logging context during business stop events. This new handler is implemented in a separate file and includes methods for handling events and releasing the logging context.

Changes

File Change Summary
koupleless-base-plugin/pom.xml Added dependency: log4j-core from org.apache.logging.log4j with scope provided.
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java Updated registerEventHandler method to register StopLoggerCxtAfterBizStopEventHandler.
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java Added class StopLoggerCxtAfterBizStopEventHandler with methods for handling BeforeBizStopEvent and managing logging context.
koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java Added unit test class StopLoggerCxtAfterBizStopEventHandlerTest to validate logging context behavior on business stop events.

Possibly related PRs

  • fix base package dependency #158: The changes in this PR involve enhancing dependency management logic, which is relevant to the main PR's addition of a new dependency (log4j-core) in the pom.xml, as both PRs focus on managing project dependencies effectively.

Poem

🐇 In the code where bunnies hop,
New logs will now never stop!
With handlers swift and bright,
They’ll manage logs, day and night.
So let’s cheer for changes made,
In our plugin, joy displayed! 🎉


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (1)

44-47: Consider grouping related event handlers together

The new logger context handler is placed separately from other cleanup-related handlers. Consider grouping it with other cleanup handlers for better code organization and maintainability.

-        eventAdminService.register(new StopLoggerCxtAfterBizStopEventHandler());
         eventAdminService.register(new BizUninstallEventHandler());
         eventAdminService.register(new BeforeBizStartupEventHandler());
 
         // 清理用户主动托管给 Serverless 运行时的 ExecutorService (含线程池), Timer 和 Thread.
+        eventAdminService.register(new StopLoggerCxtAfterBizStopEventHandler());
         eventAdminService.register(new ShutdownExecutorServicesOnUninstallEventHandler());
         eventAdminService.register(new CancelTimersOnUninstallEventHandler());
         eventAdminService.register(new ForceStopThreadsOnUninstallEventHandler());
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (4)

39-39: Adjust the order of modifiers for the LOGGER field

The Java convention is to declare modifiers in the order private static final rather than private final static. Changing the order improves readability and follows standard coding practices.

Apply this diff to adjust the modifiers' order:

- private final static Logger LOGGER = getLogger(StopLoggerCxtAfterBizStopEventHandler.class);
+ private static final Logger LOGGER = getLogger(StopLoggerCxtAfterBizStopEventHandler.class);

55-56: Translate code comments to English for consistency

The comments on lines 55-56 and 75 are in Chinese. To maintain consistency and improve readability for all contributors, please translate these comments to English.

Also applies to: 75-75


59-59: Consider making the LoggerContext stop timeout configurable

The timeout value of 300 milliseconds in ctx.stop(300, TimeUnit.MILLISECONDS) is hardcoded. Consider extracting this value into a constant or making it configurable to allow flexibility and easier adjustment in different environments.

Apply this diff to extract the timeout value:

+        private static final long LOGGER_CONTEXT_STOP_TIMEOUT = 300;

...

-                            boolean stop = ctx.stop(300, TimeUnit.MILLISECONDS);
+                            boolean stop = ctx.stop(LOGGER_CONTEXT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);

37-80: Add unit tests for StopLoggerCxtAfterBizStopEventHandler

Since this is a new class handling critical cleanup operations, consider adding unit tests to verify its behavior under different scenarios, such as when Log4jContextFactory is present or absent, and when exceptions occur during context shutdown.

Would you like assistance in creating unit tests for this class?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a34b4fe and 1cc207d.

📒 Files selected for processing (3)
  • koupleless-base-plugin/pom.xml (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (1 hunks)
🔇 Additional comments (4)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (2)

44-44: Verify handler execution order for proper cleanup sequence

The new StopLoggerCxtAfterBizStopEventHandler is registered first in the sequence of handlers. Since logging might be needed during the cleanup of other resources (ExecutorServices, Timers, Threads), we should verify that the handler's priority allows logging to remain available during the cleanup process.

Let's check the priority implementation and compare it with other handlers:

✅ Verification successful

Let me analyze the verification results and generate a final response:

Logger context cleanup is properly ordered with other cleanup handlers

The StopLoggerCxtAfterBizStopEventHandler is registered to handle BeforeBizRecycleEvent, which is the same event type handled by other cleanup handlers like ShutdownExecutorServicesOnUninstallEventHandler, ForceStopThreadsOnUninstallEventHandler, and CancelTimersOnUninstallEventHandler. The logging context cleanup is safe because:

  1. The handler stops only its own logger context by matching the classloader identity
  2. The cleanup is performed using a graceful timeout (300ms)
  3. Other handlers can still log during their cleanup as they operate on their own resources independently
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check priority implementation across handlers to ensure proper ordering

# Search for getPriority implementations in all handlers
ast-grep --pattern 'class $_ implements EventHandler {
  $$$
  public int getPriority() {
    $$$
  }
  $$$
}'

# Also check for any priority-related constants or documentation
rg -i "priority|order" --type java

Length of output: 81957


44-44: Verify log4j2 context cleanup implementation

The new handler is crucial for addressing issue #255. Let's verify its implementation to ensure proper cleanup of log4j2 context.

✅ Verification successful

Based on the search results, I can see the implementation of StopLoggerCxtAfterBizStopEventHandler in the codebase. Let me analyze the log4j2 context cleanup implementation:

The handler properly implements the cleanup by:

  1. Getting the Log4j2 LoggerContext factory and checking if it's Log4jContextFactory
  2. Generating a unique context name based on the biz classloader's identity hash
  3. Getting all logger contexts from the selector
  4. Finding and stopping the matching context with a timeout of 300ms

The implementation looks correct and follows best practices for log4j2 context cleanup. Here's my final response:

Log4j2 context cleanup implementation verified successfully

The StopLoggerCxtAfterBizStopEventHandler properly handles log4j2 context cleanup by:

  • Using proper context identification based on classloader identity
  • Implementing graceful shutdown with timeout
  • Handling edge cases when context selector is not ClassLoaderContextSelector
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of StopLoggerCxtAfterBizStopEventHandler

# Search for log4j2 context cleanup implementation
ast-grep --pattern 'class StopLoggerCxtAfterBizStopEventHandler {
  $$$
  private static void releaseLog4j2LogCtx($_) {
    $$$
  }
  $$$
}'

# Check for any potential memory leak patterns in the implementation
rg -A 5 "LoggerContext|getContext|shutdown" --type java

Length of output: 81957

koupleless-base-plugin/pom.xml (1)

66-70: Verify log4j2 version for security vulnerabilities

The log4j-core dependency is added without an explicit version. While this is typically managed by the parent POM, it's crucial to ensure we're using a secure version given log4j's history of critical vulnerabilities (e.g., CVE-2021-44228).

✅ Verification successful

Let me gather more information about the actual log4j version being used.


Log4j dependency configuration is secure and well-structured

The verification shows that:

  • The project uses Spring Boot's log4j2 starter with 'provided' scope
  • A test dependency on log4j-slf4j-impl is explicitly versioned to 2.17.1, which is a secure version
  • The log4j-core dependency inherits version management from Spring Boot starter, which maintains secure versions

The current setup is appropriate because:

  • The 'provided' scope is correctly used
  • Version management is handled by Spring Boot's dependency management
  • The observed version (2.17.1) is after the critical CVE-2021-44228 patch
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check parent POM for log4j version and known vulnerabilities
echo "Checking parent POM for log4j version..."
find . -name "pom.xml" -exec grep -l "log4j.version" {} \;

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "org.apache.logging.log4j:log4j-core") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 1713


Script:

#!/bin/bash
# Check for log4j version in dependency management or properties
find . -name "pom.xml" -exec grep -A 5 -B 5 "log4j" {} \;

# Also check parent pom reference
find . -name "pom.xml" -exec grep -A 2 "<parent>" {} \;

Length of output: 3887

koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (1)

58-64: Ensure matching LoggerContexts by name is reliable

Matching LoggerContexts using the context name derived from the class loader's identity hash code (ctxName) may not be unique or reliable in all scenarios. Consider verifying that this method uniquely identifies the correct LoggerContext, or explore alternative approaches, such as matching based on the class loader reference itself if possible.

Run the following script to check for potential duplicate context names:

✅ Verification successful

The LoggerContext name matching is reliable and safe

The code uses System.identityHashCode(bizClassLoader) to generate the context name, which is guaranteed to be unique for each distinct ClassLoader instance during its lifetime. This matches Log4j2's own implementation for ClassLoader-based context naming. The context cleanup is correctly scoped to the specific biz module's classloader.

Key observations:

  • The context name is derived from the classloader's identity hash code, which is unique per classloader instance
  • The cleanup only affects the specific biz module's logger context
  • The implementation aligns with Log4j2's classloader-based context isolation model
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if multiple LoggerContexts have the same name derived from class loader identity hash code.

# Get all active LoggerContext names
ast-grep --pattern '$_ = new LoggerContext($_)' | awk -F'[(|)]' '{print $2}' | sort | uniq -c | awk '$1 > 1 {print $2}'

# Expected result: No duplicate LoggerContext names based on class loader identity hash codes.

Length of output: 121


Script:

#!/bin/bash
# Let's analyze how LoggerContext names are generated and used

# First, find where LoggerContext names are created or used
rg -l "LoggerContext" --type java

# Then look for specific patterns around context name generation and usage
rg "getName\(\)" --type java -B 2 -A 2

# Also check for any direct string comparisons with context names
rg "equals\(.*Name\)" --type java -B 2 -A 2

Length of output: 56191


Script:

#!/bin/bash
# Let's analyze how LoggerContext names are created and used in the specific file

# First, check how ctxName is generated
rg -B 5 -A 5 "ctxName" koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java

# Also check for any LoggerContext creation patterns
ast-grep --pattern 'new LoggerContext($_, $_)'

Length of output: 1374

Comment on lines +66 to +70
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>provided</scope>
</dependency>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding log4j-api dependency

While log4j-core is added, it typically requires log4j-api as a companion dependency. Consider adding:

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <scope>provided</scope>
        </dependency>
+       <dependency>
+           <groupId>org.apache.logging.log4j</groupId>
+           <artifactId>log4j-api</artifactId>
+           <scope>provided</scope>
+       </dependency>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>provided</scope>
</dependency>

Comment on lines 68 to 72
} catch (Throwable throwable) {
Biz source = event.getSource();
LOGGER.error("release {}:{}'s log4j2LogCtx failed,event id = {}", source.getBizName(),
source.getBizVersion(), source.getIdentity(), throwable);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid catching Throwable directly

Catching Throwable can mask serious errors such as OutOfMemoryError or StackOverflowError. It's advisable to catch Exception instead, unless there is a specific need to catch Throwable.

Apply this diff to change Throwable to Exception:

-        } catch (Throwable throwable) {
+        } catch (Exception exception) {
             Biz source = event.getSource();
-            LOGGER.error("release {}:{}'s log4j2LogCtx failed,event id = {}", source.getBizName(),
-                source.getBizVersion(), source.getIdentity(), throwable);
+            LOGGER.error("release {}:{}'s log4j2LogCtx failed, event id = {}", source.getBizName(),
+                source.getBizVersion(), source.getIdentity(), exception);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (Throwable throwable) {
Biz source = event.getSource();
LOGGER.error("release {}:{}'s log4j2LogCtx failed,event id = {}", source.getBizName(),
source.getBizVersion(), source.getIdentity(), throwable);
}
} catch (Exception exception) {
Biz source = event.getSource();
LOGGER.error("release {}:{}'s log4j2LogCtx failed, event id = {}", source.getBizName(),
source.getBizVersion(), source.getIdentity(), exception);
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java (2)

44-46: Enhance class documentation

The current documentation only shows the author. Consider adding a class-level JavaDoc that describes:

  • The purpose of these tests
  • The relationship with issue #255
  • Key scenarios being tested
 /**
+ * Test class for StopLoggerCxtAfterBizStopEventHandler which validates proper cleanup
+ * of log4j2 logger contexts during module uninstallation (issue #255).
+ * 
  * @author gaowh
  */

61-71: Refactor duplicate property cleanup

The LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND property is cleared in both setUp and tearDown methods.

Consider moving the cleanup to a single location, preferably tearDown:

     @Before
     public void setUp() {
-        clearProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND);
         currentThread().setContextClassLoader(originalClassLoader);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1cc207d and 571f0dc.

📒 Files selected for processing (2)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (1 hunks)
  • koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java
🔇 Additional comments (1)
koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java (1)

47-47: Verify test uniqueness

Let's verify that similar tests don't exist elsewhere in the codebase to prevent test duplication.

✅ Verification successful

Based on the search results, I can generate the final response as we have sufficient information. The results show that:

  1. StopLoggerCxtAfterBizStopEventHandler exists in only one test file
  2. testCloseLoggerContext method exists only in this test file
  3. The handler class itself is properly placed in the main source directory

Test implementation is unique

The test implementation is unique in the codebase with no duplication. The handler being tested has appropriate test coverage without redundancy.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar test files or methods
rg -l "StopLoggerCxtAfterBizStopEventHandler" --type java
rg -l "testCloseLoggerContext" --type java

Length of output: 601

Comment on lines 73 to 86
@Test
public void testCloseLoggerContext() {
StopLoggerCxtAfterBizStopEventHandler handler = new StopLoggerCxtAfterBizStopEventHandler();
LogManager.getContext(originalClassLoader, false);
Log4jContextFactory contextFactory = (Log4jContextFactory)LogManager.getFactory();
int beforeSize = contextFactory.getSelector().getLoggerContexts().size();
assertEquals(beforeSize,1);
BizModel bizModel = new BizModel();
bizModel.setClassLoader(originalClassLoader);
BeforeBizStopEvent event = new BeforeBizStopEvent(bizModel);
handler.handleEvent(event);
int afterSize = contextFactory.getSelector().getLoggerContexts().size();
assertEquals(afterSize, 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen test coverage

The current test can be improved in several ways:

  1. Verify that the specific logger context for the test ClassLoader was closed
  2. Test error handling scenarios
  3. Verify timeout behavior
  4. Avoid magic numbers in assertions

Consider expanding the test coverage:

     @Test
     public void testCloseLoggerContext() {
         StopLoggerCxtAfterBizStopEventHandler handler = new StopLoggerCxtAfterBizStopEventHandler();
         LogManager.getContext(originalClassLoader, false);
         Log4jContextFactory contextFactory = (Log4jContextFactory)LogManager.getFactory();
         int beforeSize = contextFactory.getSelector().getLoggerContexts().size();
-        assertEquals(beforeSize,1);
+        assertEquals("Should have one logger context initially", 1, beforeSize);
+        
+        // Verify the specific context exists
+        LoggerContext context = contextFactory.getSelector().getContext(originalClassLoader);
+        assertNotNull("Logger context should exist for test ClassLoader", context);
         
         BizModel bizModel = new BizModel();
         bizModel.setClassLoader(originalClassLoader);
         BeforeBizStopEvent event = new BeforeBizStopEvent(bizModel);
         handler.handleEvent(event);
+        
         int afterSize = contextFactory.getSelector().getLoggerContexts().size();
-        assertEquals(afterSize, 0);
+        assertEquals("All logger contexts should be closed", 0, afterSize);
+        
+        context = contextFactory.getSelector().getContext(originalClassLoader);
+        assertNull("Logger context should be closed for test ClassLoader", context);
     }
+    
+    @Test
+    public void testCloseLoggerContextWithTimeout() {
+        System.setProperty(LOGGER_CONTEXT_STOP_TIMEOUT_MILLISECOND, "1000");
+        // Similar test structure but verify timeout is respected
+    }
+    
+    @Test
+    public void testHandleNullClassLoader() {
+        StopLoggerCxtAfterBizStopEventHandler handler = new StopLoggerCxtAfterBizStopEventHandler();
+        BizModel bizModel = new BizModel();
+        // Don't set ClassLoader
+        BeforeBizStopEvent event = new BeforeBizStopEvent(bizModel);
+        handler.handleEvent(event);
+        // Verify graceful handling
+    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 50 to 59
private static MockedStatic<PropertiesUtil> propertiesUtil;

@BeforeClass
public static void beforeClass() {
propertiesUtil = mockStatic(PropertiesUtil.class);
Properties properties = new Properties();
properties.setProperty("log4j2.loggerContextFactory", LOG4J2_FACTORY_CLASS_NAME);
PropertiesUtil mockProperties = new PropertiesUtil(properties);
propertiesUtil.when(PropertiesUtil::getProperties).thenReturn(mockProperties);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Close static mock after tests

The propertiesUtil static mock should be closed after all tests complete to prevent memory leaks.

Add an @AfterClass method:

+    @AfterClass
+    public static void afterClass() {
+        if (propertiesUtil != null) {
+            propertiesUtil.close();
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static MockedStatic<PropertiesUtil> propertiesUtil;
@BeforeClass
public static void beforeClass() {
propertiesUtil = mockStatic(PropertiesUtil.class);
Properties properties = new Properties();
properties.setProperty("log4j2.loggerContextFactory", LOG4J2_FACTORY_CLASS_NAME);
PropertiesUtil mockProperties = new PropertiesUtil(properties);
propertiesUtil.when(PropertiesUtil::getProperties).thenReturn(mockProperties);
}
private static MockedStatic<PropertiesUtil> propertiesUtil;
@BeforeClass
public static void beforeClass() {
propertiesUtil = mockStatic(PropertiesUtil.class);
Properties properties = new Properties();
properties.setProperty("log4j2.loggerContextFactory", LOG4J2_FACTORY_CLASS_NAME);
PropertiesUtil mockProperties = new PropertiesUtil(properties);
propertiesUtil.when(PropertiesUtil::getProperties).thenReturn(mockProperties);
}
@AfterClass
public static void afterClass() {
if (propertiesUtil != null) {
propertiesUtil.close();
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (5)

37-40: Enhance class-level documentation

The class documentation could be improved by adding a detailed JavaDoc that explains:

  • The purpose of this event handler
  • Its responsibility in the lifecycle of a business module
  • The relationship with log4j2 context management

Example improvement:

/**
 * Event handler responsible for cleaning up Log4j2 logger contexts when a business module is stopped.
 * This handler ensures proper release of logging resources to prevent memory leaks.
 * It executes before the business context and classloader cleanup to ensure orderly shutdown.
 *
 * @author gaowh
 * @since 1.0
 */

45-46: Consider documenting the timeout configuration

The default timeout of 300ms for stopping the logger context seems arbitrary. Consider:

  • Adding a comment explaining why 300ms was chosen as the default
  • Documenting this configuration property in your application's configuration guide
  • Adding validation for negative values

61-61: Consider using a more unique context name format

Using identityHashCode alone could theoretically lead to naming collisions. Consider using a more unique identifier format:

-String ctxName = Integer.toHexString(System.identityHashCode(bizClassLoader));
+String ctxName = event.getSource().getBizName() + "-" + event.getSource().getBizVersion() + "-" + Integer.toHexString(System.identityHashCode(bizClassLoader));

66-66: Improve comment readability

The inline comment could be more readable and follow proper grammar.

-// Traverse the loggerContext of the selector and find the loggerContext that belongs to the bizClassloader module and close it
+// Find and close the logger context associated with this business module's classloader

75-77: Remove unnecessary return statement

The return statement here creates an additional exit point in the method, making it harder to follow the control flow.

-                return;
-            }
-            LOGGER.info("Not Log4jContextFactory, do nothing");
+            } else {
+                LOGGER.info("Not Log4jContextFactory, do nothing");
+            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 571f0dc and bdeb12f.

📒 Files selected for processing (2)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (1 hunks)
  • koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandlerTest.java
🔇 Additional comments (3)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/StopLoggerCxtAfterBizStopEventHandler.java (3)

49-54: LGTM! Defensive programming approach.

The method properly checks for log4j2 presence before proceeding with the cleanup, preventing potential ClassNotFoundException issues.


85-89: LGTM! Proper event ordering.

The priority is correctly set to execute before the BizUninstallEventHandler, ensuring proper cleanup order.


56-83: Verify thread safety of logger context operations

Since multiple threads could potentially trigger business module stops, we should verify that the Log4j2 API operations are thread-safe.

✅ Verification successful

Based on the search results, I can see that the Log4j2 operations in the code are thread-safe by design:

  1. The code uses Log4j2's factory pattern and selector mechanism which are designed to be thread-safe
  2. The LoggerContext operations are performed through the official Log4j2 API which handles synchronization internally
  3. The code already has proper null checks and exception handling

Let me verify one more aspect to be completely certain:


Thread safety is properly handled by Log4j2's internal implementation

The LoggerContext operations in this code are thread-safe because:

  1. Log4j2's LoggerContext and ContextSelector implementations handle thread synchronization internally
  2. The code uses the official Log4j2 API methods (factory pattern, selector) which are designed for concurrent access
  3. The implementation has proper null checks and exception handling around the context operations
  4. The context lookup is done using a unique identifier (hex string of classloader's identity hashcode) which prevents conflicts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any documentation or usage patterns related to concurrent access of LoggerContext
rg -A 3 "thread|concurrent|synchronized" --type java

# Look for other places where LoggerContext operations are performed
ast-grep --pattern 'LoggerContext $_ = $$$'

Length of output: 110944


Script:

# Check if there are any other places where LoggerContext is modified
rg -A 3 "LoggerContext.*\.(stop|close|reconfigure|updateLoggers)" --type java

Length of output: 79

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Log4j2 在模块卸载后未释放
1 participant