This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit aef43014f7609cb93bc2dc6c0eba58b3aa46eb19 Author: ppkarwasz <piotr.git...@karwasz.org> AuthorDate: Sun Mar 13 06:48:01 2022 +0100 [LOG4J2-3419] Adds support for custom Log4j 1.x levels (#789) * [LOG4J2-3419] Adds support for custom Log4j 1.x levels Adds support for custom Log4j 1.x level in the form `level_name#class_name`. Levels defined directly in Log4j 2.x can be used in the bridge using the form `level_name#org.apache.logging.log4j.Level`. Conflicts: log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java --- log4j-1.2-api/pom.xml | 4 + .../src/main/java/org/apache/log4j/Category.java | 12 +- .../src/main/java/org/apache/log4j/Hierarchy.java | 3 +- .../src/main/java/org/apache/log4j/Level.java | 26 ++- .../src/main/java/org/apache/log4j/Priority.java | 9 + .../org/apache/log4j/PropertyConfigurator.java | 3 +- .../org/apache/log4j/bridge/LogEventAdapter.java | 21 +- .../builders/filter/LevelMatchFilterBuilder.java | 3 +- .../builders/filter/LevelRangeFilterBuilder.java | 5 +- .../apache/log4j/config/Log4j1Configuration.java | 7 + .../org/apache/log4j/config/PropertySetter.java | 2 +- .../org/apache/log4j/helpers/OptionConverter.java | 244 +++++++++++++++------ .../org/apache/log4j/helpers/UtilLoggingLevel.java | 2 +- .../org/apache/log4j/xml/XmlConfiguration.java | 19 +- .../log4j/helpers/OptionConverterLevelTest.java | 123 +++++++++++ .../apache/log4j/helpers/UtilLoggingLevelTest.java | 36 ++- 16 files changed, 388 insertions(+), 131 deletions(-) diff --git a/log4j-1.2-api/pom.xml b/log4j-1.2-api/pom.xml index c3e5392..c3bf53e 100644 --- a/log4j-1.2-api/pom.xml +++ b/log4j-1.2-api/pom.xml @@ -43,6 +43,10 @@ <artifactId>junit-jupiter-engine</artifactId> </dependency> <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + </dependency> + <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <scope>test</scope> diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java index cce2c86..cfafe8d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java @@ -324,7 +324,7 @@ public class Category implements AppenderAttachable { } public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) { - final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); + final org.apache.logging.log4j.Level lvl = level.getVersion2Level(); final Message msg = createMessage(message); if (logger instanceof ExtendedLogger) { ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t); @@ -513,7 +513,7 @@ public class Category implements AppenderAttachable { } public boolean isEnabledFor(final Priority level) { - return isEnabledFor(org.apache.logging.log4j.Level.toLevel(level.toString())); + return isEnabledFor(level.getVersion2Level()); } public boolean isErrorEnabled() { @@ -661,17 +661,17 @@ public class Category implements AppenderAttachable { } public void setLevel(final Level level) { - setLevel(getLevelStr(level)); + setLevel(level != null ? level.getVersion2Level() : null); } - private void setLevel(final String levelStr) { + private void setLevel(final org.apache.logging.log4j.Level level) { if (LogManager.isLog4jCorePresent()) { - CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr)); + CategoryUtil.setLevel(logger, level); } } public void setPriority(final Priority priority) { - setLevel(getLevelStr(priority)); + setLevel(priority != null ? priority.getVersion2Level() : null); } public void setResourceBundle(final ResourceBundle bundle) { diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java index 8b2f1f9..2fe2d79 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.legacy.core.ContextUtil; import org.apache.log4j.or.ObjectRenderer; import org.apache.log4j.or.RendererMap; @@ -460,7 +461,7 @@ public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRe */ @Override public void setThreshold(final String levelStr) { - final Level level = Level.toLevel(levelStr, null); + final Level level = OptionConverter.toLevel(levelStr, null); if (level != null) { setThreshold(level); } else { diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java index af53154..2da3185 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java @@ -23,6 +23,7 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Locale; +import org.apache.log4j.helpers.OptionConverter; import org.apache.logging.log4j.util.Strings; /** @@ -48,50 +49,50 @@ public class Level extends Priority implements Serializable { * The <code>OFF</code> has the highest possible rank and is * intended to turn off logging. */ - public static final Level OFF = new Level(OFF_INT, "OFF", 0); + public static final Level OFF = new Level(OFF_INT, "OFF", 0, org.apache.logging.log4j.Level.OFF); /** * The <code>FATAL</code> level designates very severe error * events that will presumably lead the application to abort. */ - public static final Level FATAL = new Level(FATAL_INT, "FATAL", 0); + public static final Level FATAL = new Level(FATAL_INT, "FATAL", 0, org.apache.logging.log4j.Level.FATAL); /** * The <code>ERROR</code> level designates error events that * might still allow the application to continue running. */ - public static final Level ERROR = new Level(ERROR_INT, "ERROR", 3); + public static final Level ERROR = new Level(ERROR_INT, "ERROR", 3, org.apache.logging.log4j.Level.ERROR); /** * The <code>WARN</code> level designates potentially harmful situations. */ - public static final Level WARN = new Level(WARN_INT, "WARN", 4); + public static final Level WARN = new Level(WARN_INT, "WARN", 4, org.apache.logging.log4j.Level.WARN); /** * The <code>INFO</code> level designates informational messages * that highlight the progress of the application at coarse-grained * level. */ - public static final Level INFO = new Level(INFO_INT, "INFO", 6); + public static final Level INFO = new Level(INFO_INT, "INFO", 6, org.apache.logging.log4j.Level.INFO); /** * The <code>DEBUG</code> Level designates fine-grained * informational events that are most useful to debug an * application. */ - public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7); + public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7, org.apache.logging.log4j.Level.DEBUG); /** * The <code>TRACE</code> Level designates finer-grained * informational events than the <code>DEBUG</code> level. */ - public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7); + public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7, org.apache.logging.log4j.Level.TRACE); /** * The <code>ALL</code> has the lowest possible rank and is intended to * turn on all logging. */ - public static final Level ALL = new Level(ALL_INT, "ALL", 7); + public static final Level ALL = new Level(ALL_INT, "ALL", 7, org.apache.logging.log4j.Level.ALL); /** * Serialization version id. @@ -99,16 +100,21 @@ public class Level extends Priority implements Serializable { private static final long serialVersionUID = 3491141966387921974L; /** - * Instantiate a Level object. + * Instantiate a Level object. A corresponding Log4j 2.x level is also created. * * @param level The logging level. * @param levelStr The level name. * @param syslogEquivalent The matching syslog level. */ protected Level(final int level, final String levelStr, final int syslogEquivalent) { - super(level, levelStr, syslogEquivalent); + this(level, levelStr, syslogEquivalent, null); } + protected Level(final int level, final String levelStr, final int syslogEquivalent, + final org.apache.logging.log4j.Level version2Equivalent) { + super(level, levelStr, syslogEquivalent); + this.version2Level = version2Equivalent != null ? version2Equivalent : OptionConverter.createLevel(this); + } /** * Convert the string passed as argument to a level. If the diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java index 8f6eee9..33a7891 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java @@ -96,6 +96,7 @@ public class Priority { transient int level; transient String levelStr; transient int syslogEquivalent; + transient org.apache.logging.log4j.Level version2Level; /** * Default constructor for deserialization. @@ -148,6 +149,14 @@ public class Priority { return syslogEquivalent; } + /** + * Gets the Log4j 2.x level associated with this priority + * + * @return a Log4j 2.x level. + */ + public org.apache.logging.log4j.Level getVersion2Level() { + return version2Level; + } /** * Returns {@code true} if this level has a higher or equal diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java index 7f82b47..7069f5f 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java @@ -31,6 +31,7 @@ import java.util.StringTokenizer; import java.util.Vector; import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.config.Log4j1Configuration; import org.apache.log4j.config.PropertiesConfiguration; import org.apache.log4j.config.PropertySetter; import org.apache.log4j.helpers.FileWatchdog; @@ -579,7 +580,7 @@ public class PropertyConfigurator implements Configurator { logger.setLevel(null); } } else { - logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG)); + logger.setLevel(OptionConverter.toLevel(levelStr, Log4j1Configuration.DEFAULT_LEVEL)); } LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java index 5fbb51b..a3ce275 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java @@ -22,13 +22,13 @@ import java.util.Set; import org.apache.log4j.Category; import org.apache.log4j.Level; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.spi.StandardLevel; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; @@ -100,24 +100,7 @@ public class LogEventAdapter extends LoggingEvent { */ @Override public Level getLevel() { - switch (StandardLevel.getStandardLevel(event.getLevel().intLevel())) { - case TRACE: - return Level.TRACE; - case DEBUG: - return Level.DEBUG; - case INFO: - return Level.INFO; - case WARN: - return Level.WARN; - case FATAL: - return Level.FATAL; - case OFF: - return Level.OFF; - case ALL: - return Level.ALL; - default: - return Level.ERROR; - } + return OptionConverter.convertLevel(event.getLevel()); } /** diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java index 60b838f..1c656b3 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.log4j.bridge.FilterWrapper; import org.apache.log4j.builders.AbstractBuilder; import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.Filter; import org.apache.log4j.xml.XmlConfiguration; import org.apache.logging.log4j.Level; @@ -78,7 +79,7 @@ public class LevelMatchFilterBuilder extends AbstractBuilder<Filter> implements private Filter createFilter(String level, boolean acceptOnMatch) { Level lvl = Level.ERROR; if (level != null) { - lvl = Level.toLevel(level, Level.ERROR); + lvl = OptionConverter.toLevel(level, org.apache.log4j.Level.ERROR).getVersion2Level(); } org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch ? org.apache.logging.log4j.core.Filter.Result.ACCEPT diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java index bcdd382..8a0aed1 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.log4j.bridge.FilterWrapper; import org.apache.log4j.builders.AbstractBuilder; import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.Filter; import org.apache.log4j.xml.XmlConfiguration; import org.apache.logging.log4j.Level; @@ -85,10 +86,10 @@ public class LevelRangeFilterBuilder extends AbstractBuilder<Filter> implements Level max = Level.FATAL; Level min = Level.TRACE; if (levelMax != null) { - max = Level.toLevel(levelMax, Level.FATAL); + max = OptionConverter.toLevel(levelMax, org.apache.log4j.Level.FATAL).getVersion2Level(); } if (levelMin != null) { - min = Level.toLevel(levelMin, Level.DEBUG); + min = OptionConverter.toLevel(levelMin, org.apache.log4j.Level.DEBUG).getVersion2Level(); } org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch ? org.apache.logging.log4j.core.Filter.Result.ACCEPT diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java index 7d999bd..ccbade8 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java @@ -16,6 +16,7 @@ */ package org.apache.log4j.config; +import org.apache.log4j.Level; import org.apache.log4j.builders.BuilderManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -36,6 +37,12 @@ public class Log4j1Configuration extends AbstractConfiguration implements Reconf public static final String NULL = "null"; + /** + * The effective level used, when the configuration uses a non-existent custom + * level. + */ + public static final Level DEFAULT_LEVEL = Level.DEBUG; + protected final BuilderManager manager; public Log4j1Configuration(final LoggerContext loggerContext, final ConfigurationSource configurationSource, diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java index f51f173..27a7b0d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java @@ -265,7 +265,7 @@ public class PropertySetter { return Boolean.FALSE; } } else if (Priority.class.isAssignableFrom(type)) { - return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG); + return org.apache.log4j.helpers.OptionConverter.toLevel(v, Log4j1Configuration.DEFAULT_LEVEL); } else if (ErrorHandler.class.isAssignableFrom(type)) { return OptionConverter.instantiateByClassName(v, ErrorHandler.class, null); diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java index 74a38f8..54b01d0 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java @@ -23,12 +23,16 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.log4j.Level; +import org.apache.log4j.Priority; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.spi.Configurator; import org.apache.log4j.spi.LoggerRepository; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.spi.StandardLevel; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; @@ -53,6 +57,27 @@ public class OptionConverter { static int DELIM_START_LEN = 2; static int DELIM_STOP_LEN = 1; private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * A Log4j 1.x level above or equal to this value is considered as OFF. + */ + static final int MAX_CUTOFF_LEVEL = Priority.FATAL_INT + + 100 * (StandardLevel.FATAL.intLevel() - StandardLevel.OFF.intLevel() - 1) + 1; + /** + * A Log4j 1.x level below or equal to this value is considered as ALL. + * + * Log4j 2.x ALL to TRACE interval is shorter. This is {@link Priority#ALL_INT} + * plus the difference. + */ + static final int MIN_CUTOFF_LEVEL = Priority.ALL_INT + Level.TRACE_INT + - (Priority.ALL_INT + StandardLevel.ALL.intLevel()) + StandardLevel.TRACE.intLevel(); + /** + * Cache of currently known levels. + */ + static final ConcurrentMap<String, Level> LEVELS = new ConcurrentHashMap<>(); + /** + * Postfix for all Log4j 2.x level names. + */ + private static final String LOG4J2_LEVEL_CLASS = org.apache.logging.log4j.Level.class.getName(); private static final CharMap[] charMap = new CharMap[] { new CharMap('n', '\n'), @@ -75,55 +100,91 @@ public class OptionConverter { return a; } - public static org.apache.logging.log4j.Level convertLevel(final Level level) { - if (level == null) { - return org.apache.logging.log4j.Level.ERROR; - } - if (level.isGreaterOrEqual(Level.FATAL)) { - return org.apache.logging.log4j.Level.FATAL; - } else if (level.isGreaterOrEqual(Level.ERROR)) { - return org.apache.logging.log4j.Level.ERROR; - } else if (level.isGreaterOrEqual(Level.WARN)) { - return org.apache.logging.log4j.Level.WARN; - } else if (level.isGreaterOrEqual(Level.INFO)) { - return org.apache.logging.log4j.Level.INFO; - } else if (level.isGreaterOrEqual(Level.DEBUG)) { - return org.apache.logging.log4j.Level.DEBUG; - } else if (level.isGreaterOrEqual(Level.TRACE)) { - return org.apache.logging.log4j.Level.TRACE; - } - return org.apache.logging.log4j.Level.ALL; + static int toLog4j2Level(final int v1Level) { + // I don't believe anyone uses values much bigger than FATAL + if (v1Level >= MAX_CUTOFF_LEVEL) { + return StandardLevel.OFF.intLevel(); + } + // Linear transformation up to debug: CUTOFF_LEVEL -> OFF, DEBUG -> DEBUG + if (v1Level > Priority.DEBUG_INT) { + final int offset = Math.round((v1Level - Priority.DEBUG_INT) / 100.0f); + return StandardLevel.DEBUG.intLevel() - offset; + } + // Steeper linear transformation + if (v1Level > Level.TRACE_INT) { + final int offset = Math.round((v1Level - Level.TRACE_INT) / 50.0f); + return StandardLevel.TRACE.intLevel() - offset; + } + if (v1Level > MIN_CUTOFF_LEVEL) { + final int offset = Level.TRACE_INT - v1Level; + return StandardLevel.TRACE.intLevel() + offset; + } + return StandardLevel.ALL.intLevel(); + } + + static int toLog4j1Level(int v2Level) { + if (v2Level == StandardLevel.ALL.intLevel()) { + return Priority.ALL_INT; + } + if (v2Level > StandardLevel.TRACE.intLevel()) { + return MIN_CUTOFF_LEVEL + (StandardLevel.ALL.intLevel() - v2Level); + } + // Inflating by 50 + if (v2Level > StandardLevel.DEBUG.intLevel()) { + return Level.TRACE_INT + 50 * (StandardLevel.TRACE.intLevel() - v2Level); + } + // Inflating by 100 + if (v2Level > StandardLevel.OFF.intLevel()) { + return Priority.DEBUG_INT + 100 * (StandardLevel.DEBUG.intLevel() - v2Level); + } + return Priority.OFF_INT; + } + + static int toSyslogLevel(int v2Level) { + if (v2Level <= StandardLevel.FATAL.intLevel()) { + return 0; + } + if (v2Level <= StandardLevel.ERROR.intLevel()) { + return 3 - (3 * (StandardLevel.ERROR.intLevel() - v2Level)) + / (StandardLevel.ERROR.intLevel() - StandardLevel.FATAL.intLevel()); + } + if (v2Level <= StandardLevel.WARN.intLevel()) { + return 4; + } + if (v2Level <= StandardLevel.INFO.intLevel()) { + return 6 - (2 * (StandardLevel.INFO.intLevel() - v2Level)) + / (StandardLevel.INFO.intLevel() - StandardLevel.WARN.intLevel()); + } + return 7; + } + + public static org.apache.logging.log4j.Level createLevel(final Priority level) { + final String name = level.toString().toUpperCase() + "#" + level.getClass().getName(); + return org.apache.logging.log4j.Level.forName(name, toLog4j2Level(level.toInt())); } + public static org.apache.logging.log4j.Level convertLevel(final Priority level) { + return level != null ? level.getVersion2Level() : org.apache.logging.log4j.Level.ERROR; + } + /** + * @param level + * @return + */ public static Level convertLevel(final org.apache.logging.log4j.Level level) { - if (level == null) { - return Level.ERROR; - } - switch (level.getStandardLevel()) { - case FATAL: - return Level.FATAL; - case WARN: - return Level.WARN; - case INFO: - return Level.INFO; - case DEBUG: - return Level.DEBUG; - case TRACE: - return Level.TRACE; - case ALL: - return Level.ALL; - case OFF: - return Level.OFF; - default: - return Level.ERROR; + // level is standard or was created by Log4j 1.x custom level + Level actualLevel = toLevel(level.name(), null); + // level was created by Log4j 2.x + if (actualLevel == null) { + actualLevel = toLevel(LOG4J2_LEVEL_CLASS, level.name(), null); } + return actualLevel != null ? actualLevel : Level.ERROR; } public static org.apache.logging.log4j.Level convertLevel(final String level, final org.apache.logging.log4j.Level defaultLevel) { - final Level l = toLevel(level, null); - return l != null ? convertLevel(l) : defaultLevel; + final Level actualLevel = toLevel(level, null); + return actualLevel != null ? actualLevel.getVersion2Level() : defaultLevel; } public static String convertSpecialChars(final String s) { @@ -472,24 +533,37 @@ public class OptionConverter { } /** - * Converts a standard or custom priority level to a Level - * object. <p> If <code>value</code> is of form - * "level#classname", then the specified class' toLevel method - * is called to process the specified level string; if no '#' - * character is present, then the default {@link org.apache.log4j.Level} - * class is used to process the level value. - * - * <p>As a special case, if the <code>value</code> parameter is - * equal to the string "NULL", then the value <code>null</code> will - * be returned. + * Converts a standard or custom priority level to a Level object. + * <p> + * If <code>value</code> is of form "level#classname", then the specified class' + * toLevel method is called to process the specified level string; if no '#' + * character is present, then the default {@link org.apache.log4j.Level} class + * is used to process the level value. + * </p> + * + * <p> + * As a special case, if the <code>value</code> parameter is equal to the string + * "NULL", then the value <code>null</code> will be returned. + * </p> + * + * <p> + * As a Log4j 2.x extension, a {@code value} + * "level#org.apache.logging.log4j.Level" retrieves the corresponding custom + * Log4j 2.x level. + * </p> * - * <p> If any error occurs while converting the value to a level, - * the <code>defaultValue</code> parameter, which may be - * <code>null</code>, is returned. + * <p> + * If any error occurs while converting the value to a level, the + * <code>defaultValue</code> parameter, which may be <code>null</code>, is + * returned. + * </p> * - * <p> Case of <code>value</code> is insignificant for the level level, but is + * <p> + * Case of <code>value</code> is insignificant for the level level, but is * significant for the class name part, if present. - * @param value The value to convert. + * </p> + * + * @param value The value to convert. * @param defaultValue The default value. * @return the value of the result. * @@ -501,6 +575,10 @@ public class OptionConverter { } value = value.trim(); + final Level cached = LEVELS.get(value); + if (cached != null) { + return cached; + } final int hashIndex = value.indexOf('#'); if (hashIndex == -1) { @@ -508,22 +586,54 @@ public class OptionConverter { return null; } // no class name specified : use standard Level class - return Level.toLevel(value, defaultValue); + final Level standardLevel = Level.toLevel(value, defaultValue); + if (standardLevel != null && value.equals(standardLevel.toString())) { + LEVELS.putIfAbsent(value, standardLevel); + } + return standardLevel; } - Level result = defaultValue; - final String clazz = value.substring(hashIndex + 1); final String levelName = value.substring(0, hashIndex); + final Level customLevel = toLevel(clazz, levelName, defaultValue); + if (customLevel != null && levelName.equals(customLevel.toString()) + && clazz.equals(customLevel.getClass().getName())) { + LEVELS.putIfAbsent(value, customLevel); + } + return customLevel; + } + + /** + * Converts a custom priority level to a Level object. + * + * <p> + * If {@code clazz} has the special value "org.apache.logging.log4j.Level" a + * wrapper of the corresponding Log4j 2.x custom level object is returned. + * </p> + * + * @param clazz a custom level class, + * @param levelName the name of the level, + * @param defaultValue the value to return in case an error occurs, + * @return the value of the result. + */ + public static Level toLevel(final String clazz, final String levelName, final Level defaultValue) { + // This is degenerate case but you never know. if ("NULL".equalsIgnoreCase(levelName)) { return null; } - LOGGER.debug("toLevel" + ":class=[" + clazz + "]" - + ":pri=[" + levelName + "]"); + LOGGER.debug("toLevel" + ":class=[" + clazz + "]" + ":pri=[" + levelName + "]"); + // Support for levels defined in Log4j2. + if (LOG4J2_LEVEL_CLASS.equals(clazz)) { + final org.apache.logging.log4j.Level v2Level = org.apache.logging.log4j.Level.getLevel(levelName.toUpperCase()); + if (v2Level != null) { + return new LevelWrapper(v2Level); + } + return defaultValue; + } try { final Class<?> customLevel = LoaderUtil.loadClass(clazz); @@ -537,7 +647,7 @@ public class OptionConverter { final Object[] params = new Object[]{levelName, defaultValue}; final Object o = toLevelMethod.invoke(null, params); - result = (Level) o; + return (Level) o; } catch (final ClassNotFoundException e) { LOGGER.warn("custom level class [" + clazz + "] not found."); } catch (final NoSuchMethodException e) { @@ -560,7 +670,7 @@ public class OptionConverter { LOGGER.warn("class [" + clazz + "], level [" + levelName + "] conversion failed.", e); } - return result; + return defaultValue; } /** @@ -568,4 +678,14 @@ public class OptionConverter { */ private OptionConverter() { } + + private static class LevelWrapper extends Level { + + private static final long serialVersionUID = -7693936267612508528L; + + protected LevelWrapper(org.apache.logging.log4j.Level v2Level) { + super(toLog4j1Level(v2Level.intLevel()), v2Level.name(), toSyslogLevel(v2Level.intLevel()), v2Level); + } + + } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java index 9917c63..8c1c92e 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java @@ -215,7 +215,7 @@ public class UtilLoggingLevel extends Level { return INFO; } - if (s.equals("CONFI")) { + if (s.equals("CONFIG")) { return CONFIG; } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java index 4c817ec..4c5f017 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java @@ -683,24 +683,13 @@ public class XmlConfiguration extends Log4j1Configuration { } } else { String className = subst(element.getAttribute(CLASS_ATTR)); + final Level level; if (EMPTY_STR.equals(className)) { - logger.setLevel(OptionConverter.convertLevel(priStr, org.apache.logging.log4j.Level.DEBUG)); + level = OptionConverter.toLevel(priStr, DEFAULT_LEVEL); } else { - LOGGER.debug("Desired Level sub-class: [{}]", className); - try { - Class<?> clazz = LoaderUtil.loadClass(className); - Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM); - Level pri = (Level) toLevelMethod.invoke(null, priStr); - logger.setLevel(OptionConverter.convertLevel(pri)); - } catch (Exception e) { - if (e instanceof InterruptedException || e instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - LOGGER.error("Could not create level [" + priStr + - "]. Reported error follows.", e); - return; - } + level = OptionConverter.toLevel(className, priStr, DEFAULT_LEVEL); } + logger.setLevel(level != null ? level.getVersion2Level() : null); } LOGGER.debug("{} level set to {}", catName, logger.getLevel()); } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java new file mode 100644 index 0000000..da7852a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java @@ -0,0 +1,123 @@ +/* + * 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.helpers; + +import static org.apache.log4j.helpers.OptionConverter.toLog4j1Level; +import static org.apache.log4j.helpers.OptionConverter.toLog4j2Level; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.log4j.Level; +import org.apache.log4j.Priority; +import org.apache.log4j.bridge.LogEventAdapter; +import org.apache.logging.log4j.spi.StandardLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class OptionConverterLevelTest { + + static Stream<Arguments> standardLevels() { + return Arrays.stream(StandardLevel.values()) + .map(Enum::name) + .map(name -> Arguments.of(Level.toLevel(name), org.apache.logging.log4j.Level.toLevel(name))); + } + + /** + * Test if the standard levels are transformed correctly. + * + * @param log4j1Level + * @param log4j2Level + */ + @ParameterizedTest + @MethodSource("standardLevels") + public void testStandardLevelConversion(final Level log4j1Level, final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level, OptionConverter.convertLevel(log4j1Level)); + assertEquals(log4j1Level, OptionConverter.convertLevel(log4j2Level)); + } + + /** + * Test if the conversion works at an integer level. + * + * @param log4j1Level + * @param log4j2Level + */ + @ParameterizedTest + @MethodSource("standardLevels") + public void testStandardIntLevelConversion(final Level log4j1Level, + final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level.intLevel(), toLog4j2Level(log4j1Level.toInt())); + assertEquals(log4j1Level.toInt(), toLog4j1Level(log4j2Level.intLevel())); + } + + @Test + public void testMaxMinCutoff() { + // The cutoff values are transformed into ALL and OFF + assertEquals(StandardLevel.ALL.intLevel(), toLog4j2Level(OptionConverter.MIN_CUTOFF_LEVEL)); + assertEquals(StandardLevel.OFF.intLevel(), toLog4j2Level(OptionConverter.MAX_CUTOFF_LEVEL)); + // Maximal and minimal Log4j 1.x values different from ALL or OFF + int minTransformed = toLog4j1Level(toLog4j2Level(OptionConverter.MIN_CUTOFF_LEVEL + 1)); + assertEquals(OptionConverter.MIN_CUTOFF_LEVEL + 1, minTransformed); + int maxTransformed = toLog4j1Level(toLog4j2Level(OptionConverter.MAX_CUTOFF_LEVEL - 1)); + assertEquals(OptionConverter.MAX_CUTOFF_LEVEL - 1, maxTransformed); + // Maximal and minimal Log4j 2.x value different from ALL or OFF + minTransformed = toLog4j2Level(toLog4j1Level(StandardLevel.OFF.intLevel() + 1)); + assertEquals(StandardLevel.OFF.intLevel() + 1, minTransformed); + maxTransformed = toLog4j2Level(toLog4j1Level(StandardLevel.ALL.intLevel() - 1)); + assertEquals(StandardLevel.ALL.intLevel() - 1, maxTransformed); + } + + /** + * Test if the values in at least the TRACE to FATAL range are transformed + * correctly. + */ + @Test + public void testUsefulRange() { + for (int intLevel = StandardLevel.OFF.intLevel(); intLevel <= StandardLevel.TRACE.intLevel(); intLevel++) { + assertEquals(intLevel, toLog4j2Level(toLog4j1Level(intLevel))); + } + for (int intLevel = Level.TRACE_INT; intLevel < OptionConverter.MAX_CUTOFF_LEVEL; intLevel = intLevel + 100) { + assertEquals(intLevel, toLog4j1Level(toLog4j2Level(intLevel))); + } + } + + /** + * Levels defined in Log4j 2.x should have an equivalent in Log4j 1.x. Those are + * used in {@link LogEventAdapter}. + */ + @Test + public void testCustomLog4j2Levels() { + final int infoDebug = (StandardLevel.INFO.intLevel() + StandardLevel.DEBUG.intLevel()) / 2; + final org.apache.logging.log4j.Level v2Level = org.apache.logging.log4j.Level.forName("INFO_DEBUG", infoDebug); + final Level v1Level = OptionConverter.toLevel("INFO_DEBUG#" + org.apache.logging.log4j.Level.class.getName(), null); + assertNotNull(v1Level); + assertEquals(v2Level, v1Level.getVersion2Level()); + final int expectedLevel = (Priority.INFO_INT + Priority.DEBUG_INT) / 2; + assertEquals(expectedLevel, v1Level.toInt()); + // convertLevel + assertEquals(v1Level, OptionConverter.convertLevel(v2Level)); + // Non-existent level + assertNull(OptionConverter.toLevel("WARN_INFO#" + org.apache.logging.log4j.Level.class.getName(), null)); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java index 6ac556a..2d49b43 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java @@ -17,29 +17,41 @@ package org.apache.log4j.helpers; -import junit.framework.TestCase; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Stream; + +import org.apache.log4j.Level; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Unit tests for UtilLoggingLevel. */ -public class UtilLoggingLevelTest extends TestCase { - - /** - * Create new instance of test. - * - * @param testName test name - */ - public UtilLoggingLevelTest(final String testName) { - super(testName); - } +public class UtilLoggingLevelTest { /** * Test toLevel("fiNeSt"). */ public void testToLevelFINEST() { - assertSame(UtilLoggingLevel.FINEST, UtilLoggingLevel.toLevel("fiNeSt")); + assertEquals(UtilLoggingLevel.FINEST, UtilLoggingLevel.toLevel("fiNeSt")); + } + + static Stream<Arguments> namesAndLevels() { + return UtilLoggingLevel.getAllPossibleLevels() + .stream() + .map(level -> Arguments.of(level.toString() + "#" + UtilLoggingLevel.class.getName(), level)); } + @ParameterizedTest + @MethodSource("namesAndLevels") + public void testOptionConverterToLevel(final String name, final UtilLoggingLevel level) { + assertEquals(level, OptionConverter.toLevel(name, Level.ALL)); + // Comparison of Log4j 2.x levels + assertEquals(level.getVersion2Level(), org.apache.logging.log4j.Level.getLevel(name)); + } }