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));
+    }
 }