-
Notifications
You must be signed in to change notification settings - Fork 11.1k
Closed
Labels
P3no SLOno SLOpackage=generaltype=enhancementMake an existing feature betterMake an existing feature better
Description
Related discussions that already exist: #829
Added internal logging facade to provide user optionality for using logging implementation.
In some special scenarios (javaagent + jboss), when using guava in javaagent, some APIs of guava will cause j.u.l.LogManager to be initialized prematurely, and eventually cause jboss to fail to start.
Consider adding a log facade to support users to choose what they want The desired log implementation.
related discussion: Failed initializing module org.jboss.as.logging
Metadata
Metadata
Assignees
Labels
P3no SLOno SLOpackage=generaltype=enhancementMake an existing feature betterMake an existing feature better
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
cpovirk commentedon Jun 20, 2023
Thanks for the report and for digging up the links.
The downsides in #829 would still be concerns, but maybe there are other things we could do to help.
System.errinstead of tojava.util.logging)? (Such a feature could also be useful for "SEVERE: input future failed" spams stdout/stderr #2134.)ben-manes commentedon Jun 20, 2023
The platform logger would be ideal, where you could use
System.Loggerand it routes to the proper backend (where jul is the default). Unfortunately that was introduced in Java 9 and Android chose to not support it and break SE compatibility instead (I do not know why).cpovirk commentedon Jun 20, 2023
Huh, thanks, I'd totally forgotten about that. @dogourd, any idea whether that would solve your particular problem with Guava+JBoss?
(Even if not, it's something for us to consider for general configurability, especially building on what I hope will be the success of conditionally using our first Java 9+ API in Guava, as we were discussing last week.)
dogourd commentedon Jun 21, 2023
Actually, it doesn't work. The reason for the failure to start JBoss is that we manipulated
j.u.l.LogManagerbefore JBoss. In Java, this should only happen during thepremainexecution phase of a javaagent. However, a javaagent does not have the authority to choose the Java version; it is determined by the target application.I think adding a switch is a viable solution, but personally, I'm not fond of using System property to control it. Its scope is too broad as both the javaagent and the application may rely on Guava but be loaded by isolated class loaders. Moreover, we only want to control the logging behavior of Guava components within the javaagent, whereas the System property approach would affect all Guava instances under different class loaders.
ben-manes commentedon Jun 21, 2023
System.Loggerresolves usingjdk.internal.logger.LazyLoggers, which is bootstrap aware. Since Guava only logs in exceptional conditions, initializing the logger but not using it should be fine. Following that logic, a memoizing supplier for the loggers would have the same effect, so using the cost of invoking through the supplier would be minimal while deferring the initialization of the logger until the first need. Then the if the java agent is not forcing a log message it would be compatible. This is basically whatLazyLoggerAccessordoes as an adapter to defer initialization if created during VM boot.dogourd commentedon Jun 21, 2023
@ben-manes Yeah, based on the scenarios I've encountered so far (where
CacheBuilderandcom.google.common.base.Platformclasses are loaded), it should do the trick. However, it does introduce some randomness because it doesn't explicitly control the initialization behavior; it depends on the runtime execution conditions.cpovirk commentedon Jun 21, 2023
Ah, interesting.
The logger in
Platformhas been unused for a while, but I've never had enough motivation to rip it out. I'm now testing a change to do that.I should be able to change the
CacheBuilderlogger to be lazily loaded, too.I might worry a little about doing that for all our loggers, if only because of the extra classes it would require, which would be unfortunate for our Android users. But we could make the change only in guava-jre (at the cost of a small diff vs. guava-android), or we could reduce the cost by declaring a single class in each package that contains all the loggers for the package. But it should be fine to try out with one class, especially one that already contains a large number of supporting classes.
If you comment out the loggers (and their usages) in those two classes, does everything work? I could try to deal with all the loggers now, but I'm trying to take care of a few different Guava things at once, so it's nice if I know where I can take the quick and dirty approach :)
ben-manes commentedon Jun 21, 2023
For this use-case, how worthwhile is it for guava to support it? I would think that an agent should have minimal dependencies to avoid surprises. If you only need the cache then you could try caffeine, which uses the platform logger. Another option is to shade and strip out the logging by bytecode rewriting.
cpovirk commentedon Jun 21, 2023
Thanks, those are both things I intended to say but forgot :( Definitely use Caffeine if you can. And, as I'll be pointing out in the release notes, we don't test Guava usage from a Java agent (as you can tell from the crashes... :)), so I don't know how likely it is to continue working. It's easy enough for us to work around the specific problem we've been discussing here, but you might encounter problems in the future, and if things get hard to fix, we might give up.
dogourd commentedon Jun 22, 2023
To put it further, can I think of this class as a
LoggerFactory? Can we read the configuration from the current classloader or the thread context classloader here to control Guava's logging behavior?Yes, based on my current situation, removing these two loggers can solve the problem.
Yes, actually I lean more towards this being a JBoss issue. Any component within the Javaagent that is capable of bridging to
julcould potentially conflict with it. However, the JBoss team believes they have mentioned in the error message that this is related toj.u.l.LogManagerso it is an expected outcome.While debugging the initialization process of
j.u.l.LogManager, I found that in my case, not only Guava but also apache httpclient initializes it. However, the fortunate thing is that apache httpclient has a logging facade called commons-logging, which allows me to completely bypass the initialization ofjulthrough some configuration. But so far, I haven't found a straightforward way to achieve this in Guava. My options are either to give up some APIs or build a downstream branch to handle this.I completely understand your point, and I agree. Guava indeed provides many excellent and practical features, such as
base,hash,io,collect,cache, and more. Honestly, I really don't want to give it up either.BTW, in the discussion so far, there have been multiple mentions of using
Caffeineas a cache implementation. However, for this particular issue, it's not feasible.Caffeine 3.xrequires JDK 11, which is not an acceptable requirement when developing a Javaagent. Javaagents are always expected to be compatible with older Java versions. As forCaffeine 2.x, although I haven't tried it yet, I anticipate running into the same issue since it also directly relies onjulaccording to the related API documentation.Regarding whether it's worth it for Guava to address this problem, I have my doubts as well. I have another solution in mind where I can make my Javaagent run in a separate thread with a delayed start, ensuring that it initializes after JBoss startup. It would appear like an Attach operation. This is currently the least invasive solution, although it may sacrifice some functionality of the Javaagent due to the limitations imposed by the JVM on retransformClass.
ben-manes commentedon Jun 22, 2023
@dogourd have you considered shading the dependency and using a rule to remove the logger during the byte code transform?
dogourd commentedon Jun 23, 2023
@ben-manes I'm afraid I don't want to go that route. I'd rather ditch some of the Guava APIs before the bytecode transformation is completed.
CacheBuilderlogger until it is needed. #6580Delay initializing the `CacheBuilder` logger until it is needed.
cpovirk commentedon Jun 23, 2023
(I have changes to address both loggers out for review.)
While I wouldn't present it as a configurable factory, I would say that we might be able to put in some kind of conditional along the lines of "
if (!inPremain()) { log(...); }." Is that something we can detect from the classloader that loadsLoggerFactoryor from the context class loader? I think that's what you were suggesting? I could believe that the context class loader would be better, since it might change over time(??), in which case we might be able to log later in the process's lifetime?Delay initializing the `CacheBuilder` logger until it is needed.
dogourd commentedon Jun 25, 2023
If you don't wanna use
ClassLoader.getResource, I think you might need to maintain a weak-referenced map for classLoader, something like this:I've come across similar code in some libraries before, but, um, it's hard for me to say whether it's good or bad, you know.
Remove obsolete GWT-RPC logging code.
Remove obsolete GWT-RPC logging code.
cpovirk commentedon Jun 30, 2023
The changes to remove/delay the loggers landed in 32.1.0, but because we got the Gradle metadata wrong in that release, I suggest going straight to 32.1.1, which also contains the fix for that problem.