[ 
https://issues.apache.org/jira/browse/LOG4J2-589?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14147080#comment-14147080
 ] 

Remko Popma edited comment on LOG4J2-589 at 9/25/14 1:22 AM:
-------------------------------------------------------------

I'm assuming that users want to write code like this:
{code}
public class Driver {
    public static void main(String[] args) throws Exception {
        final Logger logger = LogManager.getLogger("LOG4J2-589");
        logger.error("an error message");
        logger.info("an info message");
        logger.log(Level.getLevel("DIAG"), "a DIAG message"); // either use 
Level.getLevel
        logger.log(Level.forName("NOTICE", 450), "a NOTICE message"); // or 
Level.forName should work too
    }
}
{code}

I'm also assuming that users want to use custom levels in configuration (the 
goal of this Jira):
{code}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace">
  <CustomLevel name="DIAG" intLevel="350" />
  <CustomLevel name="NOTICE" intLevel="450" />
  <CustomLevel name="VERBOSE" intLevel="550" />

  <Appenders>
    <Console name="errorAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p ERRORAPPENDER - %m%n" />
    </Console>
    <Console name="diagAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p DIAGAPPENDER - %m%n" />
    </Console>
    <Console name="noticeAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p NOTICEAPPENDER - %m%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="errorAppender" level="error" />
      <AppenderRef ref="diagAppender" level="diag" />
      <AppenderRef ref="noticeAppender" level="notice" />
    </Root>
  </Loggers>
</Configuration>
{code}

Currently the configuration process ignores the {{<CustomLevel>}} elements and 
chokes on the unknown level in {{<AppenderRef ref="diagAppender" level="diag" 
/>}}:
{noformat}
2014-09-25 08:09:12,317 WARN Error while converting string [diag] to type 
[class org.apache.logging.log4j.Level]. Using default value [null]. 
java.lang.IllegalArgumentException: Unknown level constant [DIAG].
        at org.apache.logging.log4j.Level.valueOf(Level.java:283)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter.convert(TypeConverters.java:230)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter.convert(TypeConverters.java:226)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters.convert(TypeConverters.java:336)
        at 
org.apache.logging.log4j.core.config.plugins.visitors.AbstractPluginVisitor.convert(AbstractPluginVisitor.java:130)
        at 
org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor.visit(PluginAttributeVisitor.java:44)
        at 
org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.generateParameters(PluginBuilder.java:246)
        at 
org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:135)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:756)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:691)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:683)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:683)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.doConfigure(AbstractConfiguration.java:358)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:159)
        at 
org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:385)
        at 
org.apache.logging.log4j.core.LoggerContext.reconfigure(LoggerContext.java:444)
        at 
org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:151)
        at 
org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:85)
        at 
org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:37)
        at org.apache.logging.log4j.LogManager.getContext(LogManager.java:176)
        at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:427)
        at log4j2_589_levelconfig.Driver.main(Driver.java:33)
{noformat}

A solution would be to have this plugin in core:
{code}
@Plugin(name = "CustomLevel", category = "Core")
public final class CustomLevelPlugin {
    private CustomLevelPlugin() {
    }
    @PluginFactory
    public static Level createLevel(
            @PluginAttribute("name") final String levelName,
            @PluginAttribute("intLevel") final int intLevel) {

        Level result = Level.forName(levelName, intLevel);
        return result;
    }
}
{code}
So it is not the case that the level only exists in configuration.
The plugin instantiates the Level object with a call to Level.forName().
After the <CustomLevel> element is processed this Level can be used by the 
configuration process, as well as by the user application.

Of course, a custom level  needs to be defined _before_ it can be used.If the 
user makes a mistake and does not define the level in the configuration, then
* the configuration process chokes if config elements refer to undefined 
levels, like  {{<AppenderRef ref="diagAppender" level="diag" />}} - that 
appender/logger will not do anything
* user code that uses Level.getName instead of Level.forName will fail with a 
NPE in Logger$PrivateConfig.filter(Logger.java:314)

An alternative to this plugin is to ask users to manually call 
{{Level.forName("DIAG", 350)}} etc before the first call to 
LogManager.getLogger. This is the workaround I mentioned in my first comment on 
06/Aug/14 14:21. That also solves the problem that a custom level must be 
defined before it can be used in config elements like {{<AppenderRef 
ref="diagAppender" level="diag" />}}.

But isn't the configuration alternative more elegant?


was (Author: [email protected]):
I'm assuming that users want to write code like this:
{code}
public class Driver {
    public static void main(String[] args) throws Exception {
        final Logger logger = LogManager.getLogger("LOG4J2-589");
        logger.error("an error message");
        logger.info("an info message");
        logger.log(Level.getLevel("DIAG"), "a DIAG message"); // either use 
Level.getLevel
        logger.log(Level.forName("NOTICE", 450), "a NOTICE message"); // or 
Level.forName should work too
    }
}
{code}

I'm also assuming that users want to use custom levels in configuration (the 
goal of this Jira):
{code}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace">
  <CustomLevel name="DIAG" intLevel="350" />
  <CustomLevel name="NOTICE" intLevel="450" />
  <CustomLevel name="VERBOSE" intLevel="550" />

  <Appenders>
    <Console name="errorAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p ERRORAPPENDER - %m%n" />
    </Console>
    <Console name="diagAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p DIAGAPPENDER - %m%n" />
    </Console>
    <Console name="infoAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p INFOAPPENDER - %m%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="errorAppender" level="error" />
      <AppenderRef ref="diagAppender" level="diag" />
      <AppenderRef ref="infoAppender" level="debug" />
    </Root>
  </Loggers>
</Configuration>
{code}

Currently the configuration process ignores the {{<CustomLevel>}} elements and 
chokes on the unknown level in {{<AppenderRef ref="diagAppender" level="diag" 
/>}}:
{noformat}
2014-09-25 08:09:12,317 WARN Error while converting string [diag] to type 
[class org.apache.logging.log4j.Level]. Using default value [null]. 
java.lang.IllegalArgumentException: Unknown level constant [DIAG].
        at org.apache.logging.log4j.Level.valueOf(Level.java:283)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter.convert(TypeConverters.java:230)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter.convert(TypeConverters.java:226)
        at 
org.apache.logging.log4j.core.config.plugins.convert.TypeConverters.convert(TypeConverters.java:336)
        at 
org.apache.logging.log4j.core.config.plugins.visitors.AbstractPluginVisitor.convert(AbstractPluginVisitor.java:130)
        at 
org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor.visit(PluginAttributeVisitor.java:44)
        at 
org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.generateParameters(PluginBuilder.java:246)
        at 
org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:135)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:756)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:691)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:683)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:683)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.doConfigure(AbstractConfiguration.java:358)
        at 
org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:159)
        at 
org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:385)
        at 
org.apache.logging.log4j.core.LoggerContext.reconfigure(LoggerContext.java:444)
        at 
org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:151)
        at 
org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:85)
        at 
org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:37)
        at org.apache.logging.log4j.LogManager.getContext(LogManager.java:176)
        at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:427)
        at log4j2_589_levelconfig.Driver.main(Driver.java:33)
{noformat}

A solution would be to have this plugin in core:
{code}
@Plugin(name = "CustomLevel", category = "Core")
public final class CustomLevelPlugin {
    private CustomLevelPlugin() {
    }
    @PluginFactory
    public static Level createLevel(
            @PluginAttribute("name") final String levelName,
            @PluginAttribute("intLevel") final int intLevel) {
        Level result = Level.forName(levelName, intLevel);
        return result;
    }
}
{code}
So the level does not only exist in configuration: the plugin ensures the Level 
object with the correct name/intLevel is instantiated. After that this Level 
can be used by components that are part of the configuration process, as well 
as by the user application.

If the level is not defined in the configuration, then
* the configuration process will choke if other config elements refer to this 
level, like  {{<AppenderRef ref="diagAppender" level="diag" />}}
* user code that uses Level.forName will work, but user code that uses 
Level.getName will fail with a NPE in 
Logger$PrivateConfig.filter(Logger.java:314)

> Allow the use of custom levels in configuration
> -----------------------------------------------
>
>                 Key: LOG4J2-589
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-589
>             Project: Log4j 2
>          Issue Type: Improvement
>          Components: Configurators
>    Affects Versions: 2.0-rc1
>            Reporter: James Hutton
>              Labels: configuration, custom, level
>             Fix For: 2.2
>
>
> Previous title: Use forName instead of getLevel and valueOf for configuration
> Without this one cannot use custom log levels in configuration without 
> forking a large amount of code.  Either the forName method needs to be 
> removed and custom log levels should be explicitly forbidden, or support 
> should be consistent.
> Classes that would need to be modified:
> BaseConfiguration, NullConfiguration, and DefaultConfiguration.



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to