[ 
https://issues.apache.org/jira/browse/LOG4J2-2940?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17220507#comment-17220507
 ] 

Ralph Goers edited comment on LOG4J2-2940 at 10/26/20, 6:28 AM:
----------------------------------------------------------------

Looking at the JBoss implementation I see that they are keeping track of the 
LoggerContext in a Map with the ClassLoader as the key. Maybe JBoss can get 
away with that but Log4j cannot. Instead, Log4j stores the LoggerContext in a 
Map where the key is the identity hashcode of the ClassLoader. This should have 
the same benefit with minimal extra overhead and won't run the risk of causing 
ClassLoader leakage. 

The comments for ClassLoaderContextSelector are "A log context selector which 
chooses a log context based on the caller's classloader.", but that doesn't 
seem to be what it does at all.

The algorithm JBoss seems to be using is:
 # Call the JDK specific LogContextFinder passing in the Map of previously 
located LogContexts and the LogContextFinder function (which just walks the 
ClassLoader parent chain looking for the first LogContext it finds in the 
ContextMap).
 # Walks the StackFrames until it finds one whose class is associated with a 
ClassLoader that can be found in the Map.
 # If it doesn't find one it returns the default LogContext.

What really has me confused is I don't see any code that adds LogContext's to 
the contextMap other than the registerLogContext method in 
ClassLoaderContextSelector that IntelliJ says is never used. So it appears that 
all this code ever does is return the default LogContext. Without some 
algorithm to tell it to add a LogContext to the Map I don't see how the JBoss 
code can be doing what it should be doing. I also don't see any tests in the 
jboss-logging or jboss-logmanager projects to test this.

Am I misreading the code somehow?

I should contrast the algorithm that JBoss uses with the one Log4j uses:
 # If the currentContext parameter is true then return the LoggerContext 
associated with the ContextAnchor ThreadLocal. If one is not present the 
default LoggerContext is returned.
 # If the ClassLoader was specified locate the LoggerContext associated with 
it, otherwise locate the caller's class (using StackLocatorUtil), get its 
ClassLoader and locate the LoggerContext associated with it.
 # To locate the LoggerContext check if the ClassLoader's identity is present 
as a key in the contextMap. If it is return the LoggerContext. If not 
recursively check its parents.
 # Since no LoggerContext could be located, create a new one and register it 
using the ClassLoader's identity as the key and return the created 
LoggerContext.

After looking at the stack trace you provided I see that it isn't even in 
Log4j's ClassLoaderContextSelector but the SLF4J adapter's getContext method. 
It is trying to resolve the calling class to get its ClassLoader to pass to the 
above algorithm. This probably shouldn't be handled this way because it you 
have chosen to use a ContextSelector other than ClassLoaderContextSelect than 
determining the caller's ClassLoader may be a waste of time. It should probably 
just pass the FQCN.


was (Author: [email protected]):
Looking at the JBoss implementation I see that they are keeping track of the 
LoggerContext in a Map with the ClassLoader as the key. Maybe JBoss can get 
away with that but Log4j cannot. Instead, Log4j stores the LoggerContext in a 
Map where the key is the identity hashcode of the ClassLoader. This should have 
the same benefit with minimal extra overhead and won't run the risk of causing 
ClassLoader leakage. 

The comments for ClassLoaderContextSelector are "A log context selector which 
chooses a log context based on the caller's classloader.", but that doesn't 
seem to be what it does at all.

The algorithm JBoss seems to be using is:
 # Call the JDK specific LogContextFinder passing in the Map of previously 
located LogContexts and the LogContextFinder function (which just walks the 
ClassLoader parent chain looking for the first LogContext it finds in the 
ContextMap).
 # Walks the StackFrames until it finds one whose class is associated with a 
ClassLoader that can be found in the Map.
 # If it doesn't find one it returns the default LogContext.

What really has me confused is I don't see any code that adds LogContext's to 
the contextMap other than the registerLogContext method in 
ClassLoaderContextSelector that IntelliJ says is never used. So it appears that 
all this code ever does is return the default LogContext. Without some 
algorithm to tell it to add a LogContext to the Map I don't see how the JBoss 
code can be doing what it should be doing. I also don't see any tests in the 
jboss-logging or jboss-logmanager projects to test this.

Am I misreading the code somehow?

I should contrast the algorithm that JBoss uses with the one Log4j uses:
 # If the currentContext parameter is true then return the LoggerContext 
associated with the ContextAnchor ThreadLocal. If one is not present the 
default LoggerContext is returned.
 # If the ClassLoader was specified locate the LoggerContext associated with 
it, otherwise locate the caller's class (using StackLocatorUtil), get its 
ClassLoader and locate the LoggerContext associated with it.
 # To locate the LoggerContext check if the ClassLoader's identity is present 
as a key in the contextMap. If it is return the LoggerContext. If not 
recursively check its parents.
 # Since no LoggerContext could be located, create a new one and register it 
using the ClassLoader's identity as the key.

After looking at the stack trace you provided I see that it isn't even in 
Log4j's ClassLoaderContextSelector but the SLF4J adapter's getContext method. 
It is trying to resolve the calling class to get its ClassLoader to pass to the 
above algorithm. This probably shouldn't be handled this way because it you 
have chosen to use a ContextSelector other than ClassLoaderContextSelect than 
determining the caller's ClassLoader may be a waste of time. It should probably 
just pass the FQCN.

> High CPU at Java 11 due to the use of StackWalker from 
> StackLocator.getCallerClass
> ----------------------------------------------------------------------------------
>
>                 Key: LOG4J2-2940
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-2940
>             Project: Log4j 2
>          Issue Type: Bug
>          Components: API
>    Affects Versions: 2.13.3
>            Reporter: Peter H
>            Assignee: Ralph Goers
>            Priority: Major
>
> We're encountering an issue similar to [LOG4J2-2792] and [LOG4J2-2880] but it 
> isn't because of the use of Location, but because of the use of dynamically 
> created loggers that we then refer to via 
> {{org.slf4j.LoggerFactory.getLogger(method.getDeclaringClass())}}. This 
> attempts to get the caller class and ends up walking the stack as in the 
> stack trace example below. This is known to be rather problematic (from the 
> performance perspective) and even becomes worse with the time (until the JVM 
> restart, or full GC) - https://bugs.openjdk.java.net/browse/JDK-8222942 for 
> example.
> However instead of doing it this rather expensive way, it may be possible to 
> use an improved approach similar to what jboss-logging library developers 
> used when working on the following issues:
> https://bugzilla.redhat.com/show_bug.cgi?id=1813436
> https://issues.redhat.com/browse/LOGMGR-263
> The related commit: 
> https://github.com/jboss-logging/jboss-logmanager/commit/fc1fc3b509df9797ff06a423878a5b113f6281f8
> Can you check whether something useful may be brought in from that?
> {noformat}at 
> java.lang.StackStreamFactory$AbstractStackWalker.fetchStackFrames([email protected]/Native
>  Method)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.fetchStackFrames([email protected]/StackStreamFactory.java:386)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.getNextBatch([email protected]/StackStreamFactory.java:322)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.peekFrame([email protected]/StackStreamFactory.java:263)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.hasNext([email protected]/StackStreamFactory.java:351)
> at 
> java.lang.StackStreamFactory$StackFrameTraverser.tryAdvance([email protected]/StackStreamFactory.java:593)
> at 
> java.util.stream.ReferencePipeline.forEachWithCancel([email protected]/ReferencePipeline.java:127)
> at 
> java.util.stream.AbstractPipeline.copyIntoWithCancel([email protected]/AbstractPipeline.java:502)
> at 
> java.util.stream.AbstractPipeline.copyInto([email protected]/AbstractPipeline.java:488)
> at 
> java.util.stream.AbstractPipeline.wrapAndCopyInto([email protected]/AbstractPipeline.java:474)
> at 
> java.util.stream.FindOps$FindOp.evaluateSequential([email protected]/FindOps.java:150)
> at 
> java.util.stream.AbstractPipeline.evaluate([email protected]/AbstractPipeline.java:234)
> at 
> java.util.stream.ReferencePipeline.findFirst([email protected]/ReferencePipeline.java:543)
> at 
> org.apache.logging.log4j.util.StackLocator.lambda$getCallerClass$6(StackLocator.java:58)
> at 
> org.apache.logging.log4j.util.StackLocator$$Lambda$1226/0x00000008414fec40.apply(Unknown
>  Source)
> at 
> java.lang.StackStreamFactory$StackFrameTraverser.consumeFrames([email protected]/StackStreamFactory.java:534)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.doStackWalk([email protected]/StackStreamFactory.java:306)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.callStackWalk([email protected]/Native
>  Method)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.beginStackWalk([email protected]/StackStreamFactory.java:370)
> at 
> java.lang.StackStreamFactory$AbstractStackWalker.walk([email protected]/StackStreamFactory.java:243)
> at java.lang.StackWalker.walk([email protected]/StackWalker.java:498)
> at 
> org.apache.logging.log4j.util.StackLocator.getCallerClass(StackLocator.java:57)
> at 
> org.apache.logging.log4j.util.StackLocatorUtil.getCallerClass(StackLocatorUtil.java:67)
> at 
> org.apache.logging.slf4j.Log4jLoggerFactory.getContext(Log4jLoggerFactory.java:45)
> at 
> org.apache.logging.log4j.spi.AbstractLoggerAdapter.getLogger(AbstractLoggerAdapter.java:48)
> at 
> org.apache.logging.slf4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:30)
> at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
> at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
> at LoggerAspect.isLogLevelEnabled(LoggerAspect.java:91)
> ...{noformat}



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to