[
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]