Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
import org.w3c.dom.Element;

/**
* Build a Level match failter.
* Build a Level range filter.
* In this class, order of {@link Level} is log4j1 way, i.e.,
* {@link Level#ALL} and {@link Level#OFF} have minimum and maximum order, respectively.
* (see: LOG4J2-2315)
*/
@Plugin(name = "org.apache.log4j.varia.LevelRangeFilter", category = CATEGORY)
public class LevelRangeFilterBuilder extends AbstractBuilder<Filter> implements FilterBuilder {
Expand Down Expand Up @@ -63,7 +66,7 @@ public Filter parse(Element filterElement, XmlConfiguration config) {
levelMax.set(getValueAttribute(currentElement));
break;
case LEVEL_MIN:
levelMax.set(getValueAttribute(currentElement));
levelMin.set(getValueAttribute(currentElement));
break;
case ACCEPT_ON_MATCH:
acceptOnMatch.set(getBooleanValueAttribute(currentElement));
Expand All @@ -83,19 +86,23 @@ public Filter parse(PropertiesConfiguration config) {
}

private Filter createFilter(String levelMax, String levelMin, boolean acceptOnMatch) {
Level max = Level.FATAL;
Level min = Level.TRACE;
Level max = Level.OFF;
Level min = Level.ALL;
if (levelMax != null) {
max = OptionConverter.toLevel(levelMax, org.apache.log4j.Level.FATAL).getVersion2Level();
max = OptionConverter.toLevel(levelMax, org.apache.log4j.Level.OFF).getVersion2Level();
}
if (levelMin != null) {
min = OptionConverter.toLevel(levelMin, org.apache.log4j.Level.DEBUG).getVersion2Level();
min = OptionConverter.toLevel(levelMin, org.apache.log4j.Level.ALL).getVersion2Level();
}
org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch
? org.apache.logging.log4j.core.Filter.Result.ACCEPT
: org.apache.logging.log4j.core.Filter.Result.NEUTRAL;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it better to output warning log if max < min here?
Current released implementation should work if we swap levelMin and levelMax in log4j.properties.
This is degradation for people who configure log4j like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

There was no warning in Log4j 1.x, so I wouldn't add it here. You can add a warning in the Log4j2 factory LevelRangeFilter#createFilter (using the StatusLogger).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I won't add warning log in Log4j2 factory too because if someone misconfigures LogLevelRangeFilter, no logs will be output - they should be able to notice misconfiguration easily.

return FilterWrapper.adapt(LevelRangeFilter.createFilter(min, max, onMatch,
// XXX: LOG4J2-2315
// log4j1 order: ALL < TRACE < DEBUG < ... < FATAL < OFF
// log4j2 order: ALL > TRACE > DEBUG > ... > FATAL > OFF
// So we create as LevelRangeFilter.createFilter(minLevel=max, maxLevel=min, ...)
return FilterWrapper.adapt(LevelRangeFilter.createFilter(max, min, onMatch,
org.apache.logging.log4j.core.Filter.Result.DENY));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.log4j.builders.filter;

import static org.junit.jupiter.api.Assertions.*;

import java.io.StringReader;
import java.util.Properties;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.bridge.FilterWrapper;
import org.apache.log4j.spi.Filter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter.Result;
import org.apache.logging.log4j.core.filter.LevelRangeFilter;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

public class LevelRangeFilterBuilderTest {

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testAcceptOnMatchTrue(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, true);

assertResult(Result.DENY, levelRangeFilter, Level.ALL);
assertResult(Result.DENY, levelRangeFilter, Level.DEBUG);
assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO);
assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN);
assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR);
assertResult(Result.DENY, levelRangeFilter, Level.FATAL);
assertResult(Result.DENY, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testAcceptOnMatchFalse(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, false);

assertResult(Result.DENY, levelRangeFilter, Level.ALL);
assertResult(Result.DENY, levelRangeFilter, Level.DEBUG);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.INFO);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.WARN);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.ERROR);
assertResult(Result.DENY, levelRangeFilter, Level.FATAL);
assertResult(Result.DENY, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testAcceptOnMatchNull(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, null);

assertResult(Result.DENY, levelRangeFilter, Level.ALL);
assertResult(Result.DENY, levelRangeFilter, Level.DEBUG);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.INFO);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.WARN);
assertResult(Result.NEUTRAL, levelRangeFilter, Level.ERROR);
assertResult(Result.DENY, levelRangeFilter, Level.FATAL);
assertResult(Result.DENY, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testMinLevelNull(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(null, Level.ERROR, true);

assertResult(Result.ACCEPT, levelRangeFilter, Level.ALL);
assertResult(Result.ACCEPT, levelRangeFilter, Level.DEBUG);
assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO);
assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN);
assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR);
assertResult(Result.DENY, levelRangeFilter, Level.FATAL);
assertResult(Result.DENY, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testMaxLevelNull(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, null, true);

assertResult(Result.DENY, levelRangeFilter, Level.ALL);
assertResult(Result.DENY, levelRangeFilter, Level.DEBUG);
assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO);
assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN);
assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR);
assertResult(Result.ACCEPT, levelRangeFilter, Level.FATAL);
assertResult(Result.ACCEPT, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testMinMaxLevelSame(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.INFO, true);

assertResult(Result.DENY, levelRangeFilter, Level.ALL);
assertResult(Result.DENY, levelRangeFilter, Level.DEBUG);
assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO);
assertResult(Result.DENY, levelRangeFilter, Level.WARN);
assertResult(Result.DENY, levelRangeFilter, Level.ERROR);
assertResult(Result.DENY, levelRangeFilter, Level.FATAL);
assertResult(Result.DENY, levelRangeFilter, Level.OFF);
}

@ParameterizedTest
@ArgumentsSource(TestLevelRangeFilterBuilderProvider.class)
public void testMinMaxLevelNull(TestLevelRangeFilterBuilder builder) throws Exception {
LevelRangeFilter levelRangeFilter = builder.build(null, null, true);

assertResult(Result.ACCEPT, levelRangeFilter, Level.ALL);
assertResult(Result.ACCEPT, levelRangeFilter, Level.DEBUG);
assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO);
assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN);
assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR);
assertResult(Result.ACCEPT, levelRangeFilter, Level.FATAL);
assertResult(Result.ACCEPT, levelRangeFilter, Level.OFF);
}

private static void assertResult(Result expected, LevelRangeFilter filter, Level level) {
assertSame(expected, filter.filter(null, level, null, (Object) null, null));
}

private static class TestLevelRangeFilterBuilderProvider implements ArgumentsProvider {

@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
Arguments.of(new TestLevelRangeFilterFromXmlBuilder()),
Arguments.of(new TestLevelRangeFilterFromPropertyBuilder())
);
}
}

private interface TestLevelRangeFilterBuilder {

LevelRangeFilter build(Level levelMin, Level levelMax, Boolean acceptOnMatch) throws Exception;
}

private static class TestLevelRangeFilterFromXmlBuilder implements TestLevelRangeFilterBuilder {

@Override
public LevelRangeFilter build(Level levelMin, Level levelMax, Boolean acceptOnMatch) throws Exception {
LevelRangeFilterBuilder builder = new LevelRangeFilterBuilder();
Filter filter = builder.parse(generateTestXml(levelMin, levelMax, acceptOnMatch), null);
org.apache.logging.log4j.core.Filter wrappedFilter = ((FilterWrapper) filter).getFilter();
return (LevelRangeFilter) wrappedFilter;
}

private static Element generateTestXml(Level levelMin, Level levelMax, Boolean acceptOnMatch) throws Exception {

StringBuilder sb = new StringBuilder();
sb.append("<filter class=\"org.apache.log4j.varia.LevelRangeFilter\">\n");
if (levelMin != null) {
sb.append(String.format("<param name=\"LevelMin\" value=\"%s\"/>\n", levelMin));
}
if (levelMax != null) {
sb.append(String.format("<param name=\"LevelMax\" value=\"%s\"/>\n", levelMax));
}
if (acceptOnMatch != null) {
sb.append(String.format("<param name=\"AcceptOnMatch\" value=\"%b\"/>\n", acceptOnMatch));
}
sb.append("</filter>");

return DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new InputSource(new StringReader(sb.toString())))
.getDocumentElement();
}
}

private static class TestLevelRangeFilterFromPropertyBuilder implements TestLevelRangeFilterBuilder {

@Override
public LevelRangeFilter build(Level levelMin, Level levelMax, Boolean acceptOnMatch) {
Properties properties = new Properties();
if (levelMin != null) {
properties.setProperty("foobar.levelMin", levelMin.name());
}
if (levelMax != null) {
properties.setProperty("foobar.levelMax", levelMax.name());
}
if (acceptOnMatch != null) {
properties.setProperty("foobar.acceptOnMatch", acceptOnMatch.toString());
}
LevelRangeFilterBuilder builder = new LevelRangeFilterBuilder("foobar", properties);
Filter filter = builder.parse(null);
org.apache.logging.log4j.core.Filter wrappedFilter = ((FilterWrapper) filter).getFilter();
return (LevelRangeFilter) wrappedFilter;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -611,4 +611,33 @@ protected void testEnhancedRollingFileAppender(Configuration configuration) {
assertEquals(11, defaultRolloverStrategy.getMinIndex());
assertEquals(20, defaultRolloverStrategy.getMaxIndex());
}

protected void testLevelRangeFilter() throws Exception {
try (final LoggerContext ctx = configure("config-1.2/log4j-LevelRangeFilter")) {
final Configuration config = ctx.getConfiguration();
final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class);
// List appender
final Appender appender = config.getAppender("LIST");
assertNotNull(appender);
final ListAppender legacyAppender = (ListAppender) ((Adapter) appender).getAppender();
// deny
logger.trace("TRACE");
assertEquals(0, legacyAppender.getEvents().size());
// deny
logger.debug("DEBUG");
assertEquals(0, legacyAppender.getEvents().size());
// accept
logger.info("INFO");
assertEquals(1, legacyAppender.getEvents().size());
// accept
logger.warn("WARN");
assertEquals(2, legacyAppender.getEvents().size());
// accept
logger.error("ERROR");
assertEquals(3, legacyAppender.getEvents().size());
// deny
logger.fatal("FATAL");
assertEquals(3, legacyAppender.getEvents().size());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,38 @@ public void testConsoleAppenderLevelRangeFilter() throws Exception {
final Filterable filterable = (Filterable) appender;
final CompositeFilter filter = (CompositeFilter) filterable.getFilter();
final org.apache.logging.log4j.core.Filter[] filters = filter.getFiltersArray();
final LevelRangeFilter customFilterReal = (LevelRangeFilter) filters[0];
assertEquals(Level.ALL, customFilterReal.getMinLevel());
final LevelRangeFilter defaultFilter = (LevelRangeFilter) filters[1];
assertEquals(Level.TRACE, defaultFilter.getMinLevel());
final LevelRangeFilter filter1 = (LevelRangeFilter) filters[0];
// XXX: LOG4J2-2315
assertEquals(Level.OFF, filter1.getMinLevel());
assertEquals(Level.ALL, filter1.getMaxLevel());
final LevelRangeFilter filter2 = (LevelRangeFilter) filters[1];
assertEquals(Level.ERROR, filter2.getMinLevel());
assertEquals(Level.INFO, filter2.getMaxLevel());
final LevelRangeFilter filter3 = (LevelRangeFilter) filters[2];
assertEquals(Level.OFF, filter3.getMinLevel());
assertEquals(Level.ALL, filter3.getMaxLevel());

final ListAppender legacyAppender = (ListAppender) ((AppenderAdapter.Adapter) appender).getAppender();
final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class);

// deny
logger.trace("TRACE");
assertEquals(0, legacyAppender.getEvents().size());
// deny
logger.debug("DEBUG");
assertEquals(0, legacyAppender.getEvents().size());
// accept
logger.info("INFO");
assertEquals(1, legacyAppender.getEvents().size());
// accept
logger.warn("WARN");
assertEquals(2, legacyAppender.getEvents().size());
// accept
logger.error("ERROR");
assertEquals(3, legacyAppender.getEvents().size());
// deny
logger.fatal("FATAL");
assertEquals(3, legacyAppender.getEvents().size());
}
}

Expand Down Expand Up @@ -326,4 +354,9 @@ public void testEnhancedRollingFileAppender() throws Exception {
}
}

@Override
@Test
public void testLevelRangeFilter() throws Exception {
super.testLevelRangeFilter();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,9 @@ public void testEnhancedRollingFileAppender() throws Exception {
}
}

@Override
@Test
public void testLevelRangeFilter() throws Exception {
super.testLevelRangeFilter();
}
}
Loading