Skip to content

Commit 7d8aac3

Browse files
authored
Implement custom JUL bridge (#96872)
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
1 parent 7572936 commit 7d8aac3

File tree

11 files changed

+289
-267
lines changed

11 files changed

+289
-267
lines changed

qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public void testLoggingLevelsFromSettings() throws IOException, UserException {
144144
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
145145
final Configuration config = ctx.getConfiguration();
146146
final Map<String, LoggerConfig> loggerConfigs = config.getLoggers();
147-
assertThat(loggerConfigs.size(), equalTo(3));
147+
assertThat(loggerConfigs.size(), equalTo(5));
148148
assertThat(loggerConfigs, hasKey(""));
149149
assertThat(loggerConfigs.get("").getLevel(), equalTo(rootLevel));
150150
assertThat(loggerConfigs, hasKey("foo"));

server/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ tasks.named("thirdPartyAudit").configure {
248248

249249
tasks.named("dependencyLicenses").configure {
250250
mapping from: /lucene-.*/, to: 'lucene'
251+
mapping from: /log4j-.*/, to: 'log4j'
251252
dependencies = project.configurations.runtimeClasspath.fileCollection {
252253
it.group.startsWith('org.elasticsearch') == false ||
253254
// keep the following org.elasticsearch jars in

server/licenses/log4j-core-LICENSE.txt

Lines changed: 0 additions & 202 deletions
This file was deleted.

server/licenses/log4j-core-NOTICE.txt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.common.logging;
10+
11+
import org.elasticsearch.common.util.Maps;
12+
import org.elasticsearch.logging.Level;
13+
import org.elasticsearch.logging.LogManager;
14+
import org.elasticsearch.logging.Logger;
15+
16+
import java.text.MessageFormat;
17+
import java.util.Locale;
18+
import java.util.Map;
19+
import java.util.NavigableMap;
20+
import java.util.logging.Handler;
21+
import java.util.logging.LogRecord;
22+
23+
/**
24+
* A Java Util Logging handler that writes log messages to the Elasticsearch logging framework.
25+
*/
26+
class JULBridge extends Handler {
27+
28+
private static final Map<java.util.logging.Level, Level> levelMap = Map.of(
29+
java.util.logging.Level.OFF,
30+
Level.OFF,
31+
java.util.logging.Level.SEVERE,
32+
Level.ERROR,
33+
java.util.logging.Level.WARNING,
34+
Level.WARN,
35+
java.util.logging.Level.INFO,
36+
Level.INFO,
37+
java.util.logging.Level.FINE,
38+
Level.DEBUG,
39+
java.util.logging.Level.FINEST,
40+
Level.TRACE,
41+
java.util.logging.Level.ALL,
42+
Level.ALL
43+
);
44+
45+
private static final NavigableMap<Integer, Level> sortedLevelMap = levelMap.entrySet()
46+
.stream()
47+
.collect(Maps.toUnmodifiableSortedMap(e -> e.getKey().intValue(), Map.Entry::getValue));
48+
49+
public static void install() {
50+
var rootJulLogger = java.util.logging.LogManager.getLogManager().getLogger("");
51+
// clear out any other handlers, so eg we don't also print to stdout
52+
for (var existingHandler : rootJulLogger.getHandlers()) {
53+
rootJulLogger.removeHandler(existingHandler);
54+
}
55+
rootJulLogger.addHandler(new JULBridge());
56+
}
57+
58+
private JULBridge() {}
59+
60+
@Override
61+
public void publish(LogRecord record) {
62+
Logger logger = LogManager.getLogger(record.getLoggerName());
63+
Level level = translateJulLevel(record.getLevel());
64+
Throwable thrown = record.getThrown();
65+
66+
String rawMessage = record.getMessage();
67+
final String message;
68+
if (rawMessage == null) {
69+
message = "<null message>";
70+
} else {
71+
message = new MessageFormat(rawMessage, Locale.ROOT).format(record.getParameters());
72+
}
73+
74+
if (thrown == null) {
75+
logger.log(level, message);
76+
} else {
77+
logger.log(level, () -> message, thrown);
78+
}
79+
}
80+
81+
private Level translateJulLevel(java.util.logging.Level julLevel) {
82+
Level esLevel = levelMap.get(julLevel);
83+
if (esLevel != null) {
84+
return esLevel;
85+
}
86+
// no matching known level, so find the closest level by int value
87+
var closestEntry = sortedLevelMap.lowerEntry(julLevel.intValue());
88+
assert closestEntry != null; // not possible since ALL is min int
89+
return closestEntry.getValue();
90+
}
91+
92+
@Override
93+
public void flush() {}
94+
95+
@Override
96+
public void close() {}
97+
}

0 commit comments

Comments
 (0)