Skip to content

Conversation

rjernst
Copy link
Member

@rjernst rjernst commented Jun 15, 2023

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

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
@rjernst rjernst added :Core/Infra/Logging Log management and logging utilities >refactoring v8.9.0 labels Jun 15, 2023
@rjernst rjernst requested review from ChrisHegarty and pgomulka June 15, 2023 13:51
@elasticsearchmachine elasticsearchmachine added the Team:Core/Infra Meta label for core/infra team label Jun 15, 2023
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

Copy link
Contributor

@uschindler uschindler left a 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.

@uschindler
Copy link
Contributor

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()) {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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<>(
Copy link
Contributor

@uschindler uschindler Jun 15, 2023

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. 🤮

Copy link
Member

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

Copy link
Member Author

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

@rjernst
Copy link
Member Author

rjernst commented Jun 15, 2023

How did the beans shit break ES?

PropertyChangeListener was unavailable at runtime because we don't read the java.desktop module.

@rjernst
Copy link
Member Author

rjernst commented Jun 15, 2023

@elasticsearchmachine run elasticsearch-ci/part-2

@rjernst
Copy link
Member Author

rjernst commented Jun 15, 2023

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.
Copy link
Contributor

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

Copy link
Contributor

@pgomulka pgomulka left a 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()) {
Copy link
Contributor

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());
Copy link
Contributor

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);
Copy link
Contributor

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)

@rjernst
Copy link
Member Author

rjernst commented Jun 16, 2023

@pgomulka You are right, I added handling for null and formatted messages, see e8900c0

}

private Level translateJulLevel(java.util.logging.Level julLevel) {
Level log4jLevel = levelMap.get(julLevel);
Copy link
Contributor

Choose a reason for hiding this comment

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

Still log4j 🤨💪

Copy link
Contributor

@pgomulka pgomulka left a 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;
Copy link
Contributor

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

@rjernst
Copy link
Member Author

rjernst commented Jun 20, 2023

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.

@rjernst rjernst merged commit 7d8aac3 into elastic:main Jun 20, 2023
floragunncom pushed a commit to floragunncom/search-guard that referenced this pull request Mar 3, 2024
floragunncom pushed a commit to floragunncom/search-guard that referenced this pull request Mar 3, 2024
floragunncom pushed a commit to floragunncom/search-guard that referenced this pull request Mar 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Core/Infra/Logging Log management and logging utilities >refactoring Team:Core/Infra Meta label for core/infra team v8.9.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configure Java Util Logging bridge

5 participants