-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Implement custom JUL bridge #96872
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
Implement custom JUL bridge #96872
Conversation
The log4j JUL bridge turned out to have issues because it relied on java beans. This commit implements a custom bridge between JUL and Log4j. closes elastic#94613
Pinging @elastic/es-core-infra (Team:Core/Infra) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hahah much more easy to understand. That's a quite simple imp and we need no external dependency.
Once we move to own logging framework this can easily be ported to directly use ESLogger or similar.
Just the question: How did the beans shit break ES? |
public static void install() { | ||
var rootJulLogger = java.util.logging.LogManager.getLogManager().getLogger(""); | ||
// clear out any other handlers, so eg we don't also print to stdout | ||
for (var existingHandler : rootJulLogger.getHandlers()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it a good idea to remove elements while executing the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an array, so it doesn't actually change as remove is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be safe. the getHandlers return a copied array of handlers
Level.ALL | ||
); | ||
|
||
private static final TreeMap<Integer, Level> sortedLevelMap = new TreeMap<>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also write:
sortedLevelMap = levelMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().intValue(), Map.Entry::getValue,
(k1, k2) -> throw new UnsupportedOperationException(), TreeMap::new));
(I hate that theres no Collector method that allows to specify custom map factory, but uses default merge function. 🤮
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do have Maps.toUnmodifiableSortedMap
that collects to a NavigableMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I used the Maps method and changed to NavigableMap
PropertyChangeListener was unavailable at runtime because we don't read the java.desktop module. |
@elasticsearchmachine run elasticsearch-ci/part-2 |
I addressed the feedback, and switched this to use the ES logging api instead of log4j directly. |
import java.util.stream.Collectors; | ||
|
||
/** | ||
* A Java Util Logging handler that writes log messages to log4j. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be EsLogger instead log4j
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good, I am worried that we perhaps are incorrectly formatting parameterised messages? or have I missed something?
I made some suggestions in rjernst#4
public static void install() { | ||
var rootJulLogger = java.util.logging.LogManager.getLogManager().getLogger(""); | ||
// clear out any other handlers, so eg we don't also print to stdout | ||
for (var existingHandler : rootJulLogger.getHandlers()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be safe. the getHandlers return a copied array of handlers
Level level = translateJulLevel(record.getLevel()); | ||
Throwable thrown = record.getThrown(); | ||
if (thrown == null) { | ||
logger.log(level, record.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally artificial to me, but turns out that jul allows a message to be null
https://github.com/qos-ch/slf4j/blob/master/jul-to-slf4j/src/main/java/org/slf4j/bridge/SLF4JBridgeHandler.java#L309
this is how jul-slf4j bridge handles this.
do we want the same?
if (thrown == null) { | ||
logger.log(level, record.getMessage()); | ||
} else { | ||
logger.log(level, record::getMessage, thrown); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also we need to take into account formatting
I would do something along the lines here https://github.com/qos-ch/slf4j/blob/master/jul-to-slf4j/src/main/java/org/slf4j/bridge/SLF4JBridgeHandler.java#L271
so logger.log(level, ()-> MessageFormat.formatMessage(record.getMessage,record.getParameters), thrown)
} | ||
|
||
private Level translateJulLevel(java.util.logging.Level julLevel) { | ||
Level log4jLevel = levelMap.get(julLevel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still log4j 🤨💪
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM,
we assert about log4j-api in testing, which I think is ok. Making this working with ES api would probably require changes to the test framework.
|
||
package org.elasticsearch.common.logging; | ||
|
||
import org.apache.logging.log4j.Level; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is fine, but perhaps we should be making sure that the bridge works between jul-es_logging_api
I removed log4j from the impl, but it remains in tests because that is what the mock logger works on. We can followup with a way for the ES logger to be mocked. |
The log4j JUL bridge turned out to have issues because it relied on java beans. This commit implements a custom bridge between JUL and Log4j.
closes #94613