You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2022/03/13 05:48:09 UTC
[logging-log4j2] branch release-2.x updated: [LOG4J2-3419] Adds support for custom Log4j 1.x levels (#789)
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/release-2.x by this push:
new 1a364cc [LOG4J2-3419] Adds support for custom Log4j 1.x levels (#789)
1a364cc is described below
commit 1a364ccfcc87f387967077a4c35448a06a0a7298
Author: ppkarwasz <pi...@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`.
---
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 daeec17..4a01bbb 100644
--- a/log4j-1.2-api/pom.xml
+++ b/log4j-1.2-api/pom.xml
@@ -54,6 +54,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 0659478..2e8a487 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 713e463..ca103fe 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 ae733a1..5e20083 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 = new BuilderManager();
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 b6cec96..d7475c6 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 78d9cc1..cde42c7 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) {
@@ -470,24 +531,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.
*
@@ -499,6 +573,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) {
@@ -506,22 +584,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);
@@ -535,7 +645,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) {
@@ -558,7 +668,7 @@ public class OptionConverter {
LOGGER.warn("class [" + clazz + "], level [" + levelName +
"] conversion failed.", e);
}
- return result;
+ return defaultValue;
}
/**
@@ -566,4 +676,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 78a42ce..7c43c15 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
@@ -675,24 +675,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));
+ }
}