You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by "Remko Popma (JIRA)" <ji...@apache.org> on 2014/09/25 06:05:33 UTC

[jira] [Comment Edited] (LOG4J2-589) Allow filtering on custom levels in configuration

    [ 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 4:04 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 filter on custom levels in configuration (the goal of this Jira):
{code}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace">
  <!-- define custom levels so that loggers & appenders can filter on them -->
  <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">
      <!-- filter on custom level -->
      <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: remkop@yahoo.com):
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?

> Allow filtering on 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: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org