Skip to content

Commit 52dbb47

Browse files
authored
Emulate effects of j.u.l.Logger.setLevel (#3125)
Some libraries rely on `j.u.l.Logger.getLevel` returning the value that was set using `j.u.l.Logger.setLevel`. This PR changes #2353: - By default, the **effective** configuration of the logging backend is not modified by `log4j-jul` (same assumption as #2353). - All implementations of `j.u.l.Logger` mutator methods (`setLevel`, `setUseParentHandlers`, `addHandler`, …), must guarantee a change in the corresponding getter (`getLevel`, `getUseParentHandlers`, `getHandlers`, …). - Adds warnings in `ApiLogger` to inform users that calling those methods has no other side effect that change the result of the corresponding getter.
1 parent 031d4da commit 52dbb47

File tree

6 files changed

+196
-43
lines changed

6 files changed

+196
-43
lines changed

log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java

+54-10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
*/
1717
package org.apache.logging.log4j.jul;
1818

19+
import java.util.ResourceBundle;
1920
import java.util.logging.Filter;
21+
import java.util.logging.Handler;
2022
import java.util.logging.Level;
2123
import java.util.logging.LogRecord;
2224
import java.util.logging.Logger;
@@ -41,6 +43,12 @@
4143
*/
4244
public class ApiLogger extends Logger {
4345

46+
private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
47+
private static final String MUTATOR_DISABLED =
48+
"Ignoring call to `j.u.l.Logger.{}({})`, since the Log4j API does not provide methods to modify the underlying implementation.\n"
49+
+ "To modify the configuration using JUL, use an `AbstractLoggerAdapter` appropriate for your logging implementation.\n"
50+
+ "See https://logging.apache.org/log4j/3.x/log4j-jul.html#log4j.jul.loggerAdapter for more information.";
51+
4452
private final WrappedLogger logger;
4553
private static final String FQCN = ApiLogger.class.getName();
4654

@@ -82,29 +90,65 @@ public String getName() {
8290

8391
@Override
8492
public Level getLevel() {
85-
// Returns the effective level instead of the configured one.
86-
// The configured level is not accessible through Log4j API.
87-
return LevelTranslator.toJavaLevel(logger.getLevel());
93+
// The configured level is NOT available through the Log4j API.
94+
// Some libraries, however, rely on the following assertion:
95+
//
96+
// logger.setLevel(level);
97+
// assert level.equals(logger.getLevel());
98+
//
99+
// See https://github.com/apache/logging-log4j2/issues/3119 for more details.
100+
return super.getLevel();
88101
}
89102

90103
@Override
91104
public void setLevel(final Level newLevel) throws SecurityException {
92-
StatusLogger.getLogger()
93-
.error(
94-
"Cannot set JUL log level through log4j-api: " + "ignoring call to Logger.setLevel({})",
95-
newLevel);
105+
LOGGER.warn(MUTATOR_DISABLED, "setLevel", newLevel);
106+
// Some libraries rely on the following assertion:
107+
//
108+
// logger.setLevel(level);
109+
// assert level.equals(logger.getLevel());
110+
//
111+
// See https://github.com/apache/logging-log4j2/issues/3119 for more details.
112+
doSetLevel(newLevel);
96113
}
97114

98115
/**
99-
* Provides access to {@link Logger#setLevel(java.util.logging.Level)}. This method should only be used by child
100-
* classes.
101-
*
116+
* Provides access to {@link Logger#setLevel(java.util.logging.Level)}.
117+
* <p>
118+
* This method should be called by all {@link #setLevel} implementations to check permissions.
119+
* </p>
102120
* @see Logger#setLevel(java.util.logging.Level)
103121
*/
104122
protected void doSetLevel(final Level newLevel) throws SecurityException {
105123
super.setLevel(newLevel);
106124
}
107125

126+
@Override
127+
public void setUseParentHandlers(boolean useParentHandlers) {
128+
LOGGER.warn(MUTATOR_DISABLED, "setLevel", useParentHandlers);
129+
super.setUseParentHandlers(useParentHandlers);
130+
}
131+
132+
@Override
133+
public void addHandler(Handler handler) throws SecurityException {
134+
LOGGER.warn(MUTATOR_DISABLED, "addHandler", handler);
135+
super.addHandler(handler);
136+
}
137+
138+
@Override
139+
public void removeHandler(Handler handler) throws SecurityException {
140+
LOGGER.warn(MUTATOR_DISABLED, "removeHandler", handler);
141+
super.removeHandler(handler);
142+
}
143+
144+
@Override
145+
public void setResourceBundle(ResourceBundle bundle) {
146+
LOGGER.warn(
147+
"Ignoring call to `j.u.l.Logger.setResourceBundle({})`, since `o.a.l.l.jul.LogManager` currently does not support resource bundles.",
148+
bundle);
149+
super.setResourceBundle(bundle);
150+
}
151+
108152
/**
109153
* Unsupported operation.
110154
*

log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java

+112-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import java.util.List;
22+
import java.util.ResourceBundle;
23+
import java.util.logging.ConsoleHandler;
24+
import java.util.logging.Filter;
2225
import java.util.logging.Logger;
2326
import org.apache.logging.log4j.Level;
2427
import org.apache.logging.log4j.core.LogEvent;
@@ -33,18 +36,40 @@
3336
*/
3437
public abstract class AbstractLoggerTest {
3538
public static final String LOGGER_NAME = "Test";
39+
40+
static final java.util.logging.Level[] LEVELS = new java.util.logging.Level[] {
41+
java.util.logging.Level.ALL,
42+
java.util.logging.Level.FINEST,
43+
java.util.logging.Level.FINER,
44+
java.util.logging.Level.FINE,
45+
java.util.logging.Level.CONFIG,
46+
java.util.logging.Level.INFO,
47+
java.util.logging.Level.WARNING,
48+
java.util.logging.Level.SEVERE,
49+
java.util.logging.Level.OFF
50+
};
51+
52+
static java.util.logging.Level getEffectiveLevel(final Logger logger) {
53+
for (final java.util.logging.Level level : LEVELS) {
54+
if (logger.isLoggable(level)) {
55+
return level;
56+
}
57+
}
58+
throw new RuntimeException("No level is enabled.");
59+
}
60+
3661
protected Logger logger;
3762
protected ListAppender eventAppender;
3863
protected ListAppender flowAppender;
3964
protected ListAppender stringAppender;
4065

4166
@Test
42-
public void testGetName() throws Exception {
67+
public void testGetName() {
4368
assertThat(logger.getName()).isEqualTo(LOGGER_NAME);
4469
}
4570

4671
@Test
47-
public void testGlobalLogger() throws Exception {
72+
public void testGlobalLogger() {
4873
final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
4974
root.info("Test info message");
5075
root.config("Test info message");
@@ -58,18 +83,18 @@ public void testGlobalLogger() throws Exception {
5883
}
5984

6085
@Test
61-
public void testGlobalLoggerName() throws Exception {
86+
public void testGlobalLoggerName() {
6287
final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
6388
assertThat(root.getName()).isEqualTo(Logger.GLOBAL_LOGGER_NAME);
6489
}
6590

6691
@Test
67-
public void testIsLoggable() throws Exception {
92+
public void testIsLoggable() {
6893
assertThat(logger.isLoggable(java.util.logging.Level.SEVERE)).isTrue();
6994
}
7095

7196
@Test
72-
public void testLog() throws Exception {
97+
public void testLog() {
7398
logger.info("Informative message here.");
7499
final List<LogEvent> events = eventAppender.getEvents();
75100
assertThat(events).hasSize(1);
@@ -82,7 +107,7 @@ public void testLog() throws Exception {
82107
}
83108

84109
@Test
85-
public void testLogFilter() throws Exception {
110+
public void testLogFilter() {
86111
logger.setFilter(record -> false);
87112
logger.severe("Informative message here.");
88113
logger.warning("Informative message here.");
@@ -96,7 +121,7 @@ public void testLogFilter() throws Exception {
96121
}
97122

98123
@Test
99-
public void testAlteringLogFilter() throws Exception {
124+
public void testAlteringLogFilter() {
100125
logger.setFilter(record -> {
101126
record.setMessage("This is not the message you are looking for.");
102127
return true;
@@ -121,7 +146,7 @@ public void testLogParamMarkers() {
121146
}
122147

123148
@Test
124-
public void testLogUsingCustomLevel() throws Exception {
149+
public void testLogUsingCustomLevel() {
125150
logger.config("Config level");
126151
final List<LogEvent> events = eventAppender.getEvents();
127152
assertThat(events).hasSize(1);
@@ -130,7 +155,7 @@ public void testLogUsingCustomLevel() throws Exception {
130155
}
131156

132157
@Test
133-
public void testLogWithCallingClass() throws Exception {
158+
public void testLogWithCallingClass() {
134159
final Logger log = Logger.getLogger("Test.CallerClass");
135160
log.config("Calling from LoggerTest");
136161
final List<String> messages = stringAppender.getMessages();
@@ -201,6 +226,84 @@ public void testLambdasPercentAndCurlyBraces() {
201226
testLambdaMessages("message{%s}");
202227
}
203228

229+
/**
230+
* This assertion must be true, even if {@code setLevel} has no effect on the logging implementation.
231+
*
232+
* @see <a href="https://github.com/apache/logging-log4j2/issues/3119">GH issue #3119</a>
233+
*/
234+
@Test
235+
public void testSetAndGetLevel() {
236+
final Logger logger = Logger.getLogger(AbstractLoggerTest.class.getName() + ".testSetAndGetLevel");
237+
// The logger under test should have no explicit configuration
238+
assertThat(logger.getLevel()).isNull();
239+
240+
for (java.util.logging.Level level : LEVELS) {
241+
logger.setLevel(level);
242+
assertThat(logger.getLevel()).as("Level set using `setLevel()`").isEqualTo(level);
243+
}
244+
}
245+
246+
/**
247+
* The value of `useParentHandlers` should be recorded even if it is not effective.
248+
*/
249+
@Test
250+
public void testSetUseParentHandlers() {
251+
final Logger logger = Logger.getLogger(AbstractLoggerTest.class.getName() + ".testSetUseParentHandlers");
252+
253+
for (boolean useParentHandlers : new boolean[] {false, true}) {
254+
logger.setUseParentHandlers(useParentHandlers);
255+
assertThat(logger.getUseParentHandlers()).isEqualTo(useParentHandlers);
256+
}
257+
}
258+
259+
/**
260+
* The programmatically configured handlers should be recorded, even if they are not used.
261+
*/
262+
@Test
263+
public void testAddAndRemoveHandlers() {
264+
final Logger logger = Logger.getLogger(AbstractLoggerTest.class.getName() + ".testAddAndRemoveHandlers");
265+
266+
assertThat(logger.getHandlers()).isEmpty();
267+
// Add a handler
268+
ConsoleHandler handler = new ConsoleHandler();
269+
logger.addHandler(handler);
270+
assertThat(logger.getHandlers()).hasSize(1).containsExactly(handler);
271+
// Remove handler
272+
logger.removeHandler(handler);
273+
assertThat(logger.getHandlers()).isEmpty();
274+
}
275+
276+
/**
277+
* The programmatically configured filters should be recorded, even if they are not used.
278+
*/
279+
@Test
280+
public void testSetFilter() {
281+
final Logger logger = Logger.getLogger(AbstractLoggerTest.class.getName() + ".testSetFilter");
282+
283+
assertThat(logger.getFilter()).isNull();
284+
// Set filter
285+
Filter denyAllFilter = record -> false;
286+
logger.setFilter(denyAllFilter);
287+
assertThat(logger.getFilter()).isEqualTo(denyAllFilter);
288+
// Remove filter
289+
logger.setFilter(null);
290+
assertThat(logger.getFilter()).isNull();
291+
}
292+
293+
/**
294+
* The programmatically configured resource bundles should be recorded, even if they are not used.
295+
*/
296+
@Test
297+
public void testSetResourceBundle() {
298+
final Logger logger = Logger.getLogger(AbstractLoggerTest.class.getName() + ".testSetResourceBundle");
299+
300+
assertThat(logger.getResourceBundle()).isNull();
301+
// Set resource bundle
302+
ResourceBundle bundle = ResourceBundle.getBundle("testResourceBundle");
303+
logger.setResourceBundle(bundle);
304+
assertThat(logger.getResourceBundle()).isSameAs(bundle);
305+
}
306+
204307
private void testLambdaMessages(final String string) {
205308
final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
206309
root.info(() -> "Test info " + string);

log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/ApiLoggerTest.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
*/
1717
package org.apache.logging.log4j.jul.test;
1818

19-
import static org.hamcrest.MatcherAssert.assertThat;
20-
import static org.hamcrest.Matchers.equalTo;
19+
import static org.assertj.core.api.Assertions.assertThat;
2120
import static org.junit.Assert.assertNotNull;
2221
import static org.junit.Assert.assertNull;
2322

@@ -46,7 +45,7 @@ public static void tearDownClass() {
4645
public void setUp() {
4746
logger = Logger.getLogger(LOGGER_NAME);
4847
logger.setFilter(null);
49-
assertThat(logger.getLevel(), equalTo(java.util.logging.Level.FINE));
48+
assertThat(getEffectiveLevel(logger)).isEqualTo(java.util.logging.Level.FINE);
5049
eventAppender = ListAppender.getListAppender("TestAppender");
5150
flowAppender = ListAppender.getListAppender("FlowAppender");
5251
stringAppender = ListAppender.getListAppender("StringAppender");

log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java

-21
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,6 @@
3434

3535
public class CoreLoggerTest extends AbstractLoggerTest {
3636

37-
private static final Level[] LEVELS = new Level[] {
38-
Level.ALL,
39-
Level.FINEST,
40-
Level.FINER,
41-
Level.FINE,
42-
Level.CONFIG,
43-
Level.INFO,
44-
Level.WARNING,
45-
Level.SEVERE,
46-
Level.OFF
47-
};
48-
49-
private static Level getEffectiveLevel(final Logger logger) {
50-
for (final Level level : LEVELS) {
51-
if (logger.isLoggable(level)) {
52-
return level;
53-
}
54-
}
55-
throw new RuntimeException("No level is enabled.");
56-
}
57-
5837
@BeforeClass
5938
public static void setUpClass() {
6039
System.setProperty("java.util.logging.manager", LogManager.class.getName());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to you under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
###
18+
# An empty resource bundle for tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="3119" link="https://github.com/apache/logging-log4j2/issues/3119"/>
7+
<description format="asciidoc">
8+
Ensures synchronization between `j.u.l.Logger.getLevel()` and `j.u.l.Logger.setLevel()` methods.
9+
</description>
10+
</entry>

0 commit comments

Comments
 (0)