我有以下设置: logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- STDOUT (System.out) appender for messages with level "INFO" and below. -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>return level <= INFO;</expression>
</evaluator>
<OnMatch>NEUTRAL</OnMatch>
<OnMismatch>DENY</OnMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<target>System.out</target>
</appender>
<!-- STDERR (System.err) appender for messages with level "WARN" and above. -->
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<target>System.err</target>
</appender>
<!-- Root logger. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="STDERR" />
</root>
</configuration>
LogbackMain.java(测试代码)
package pkg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackMain
{
private static final Logger log = LoggerFactory.getLogger(LogbackMain.class);
public LogbackMain()
{
log.info("Message A: Single line message.");
log.info("Message B: The message after this one will be empty.");
log.info("");
log.info("Message C: The message before this one was empty.");
log.info("\nMessage D: Message with a linebreak at the beginning.");
log.info("Message E: Message with a linebreak at the end.\n");
log.info("Message F: Message with\na linebreak in the middle.");
}
/**
* @param args
*/
public static void main(String[] args)
{
new LogbackMain();
}
}
这将产生以下输出:
16:36:14.152 [main] INFO pkg.LogbackMain - Message A: Single line message.
16:36:14.152 [main] INFO pkg.LogbackMain - Message B: The message after this one will be empty.
16:36:14.152 [main] INFO pkg.LogbackMain -
16:36:14.152 [main] INFO pkg.LogbackMain - Message C: The message before this one was empty.
16:36:14.152 [main] INFO pkg.LogbackMain -
Message D: Message with a linebreak at the beginning.
16:36:14.152 [main] INFO pkg.LogbackMain - Message E: Message with a linebreak at the end.
16:36:14.152 [main] INFO pkg.LogbackMain - Message F: Message with
a linebreak in the middle.
正如您所看到的,这些日志记录语句都不能按照我需要的方式工作。
- 如果只记录一个空字符串,编码器的模式仍会添加到消息中,即使消息为空也是如此。
- 如果在字符串开头或中间嵌入换行符,则该换行符之后的所有内容都会缺少模式前缀,因为模式仅应用于消息的开头一次。
- 如果在字符串末尾嵌入换行符,则确实会创建所需的空白行,但这仍然只是部分解决方案;它仍然不允许我在记录的消息之前输出一个空白行。
通过对评估器、标记等进行大量实验后,我终于找到了一个解决方案,虽然相当笨拙,但具有所需的效果。它是一个两步解决方案:
- 修改每个现有 Appender 中的过滤器,以仅允许非空消息。
- 创建每个 Appender 的副本;修改这些副本,使它们的过滤器仅允许空消息,并且它们的模式仅包含换行符令牌。
生成的文件如下所示:
logback.xml(已修改)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- STDOUT (System.out) appender for non-empty messages with level "INFO" and below. -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>return !message.isEmpty() && level <= INFO;</expression>
</evaluator>
<OnMatch>NEUTRAL</OnMatch>
<OnMismatch>DENY</OnMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<target>System.out</target>
</appender>
<!-- STDOUT (System.out) appender for empty messages with level "INFO" and below. -->
<appender name="STDOUT_EMPTY" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>return message.isEmpty() && level <= INFO;</expression>
</evaluator>
<OnMatch>NEUTRAL</OnMatch>
<OnMismatch>DENY</OnMismatch>
</filter>
<encoder>
<pattern>%n</pattern>
</encoder>
<target>System.out</target>
</appender>
<!-- STDERR (System.err) appender for non-empty messages with level "WARN" and above. -->
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>return !message.isEmpty() && level >= WARN;</expression>
</evaluator>
<OnMatch>NEUTRAL</OnMatch>
<OnMismatch>DENY</OnMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<target>System.err</target>
</appender>
<!-- STDERR (System.err) appender for empty messages with level "WARN" and above. -->
<appender name="STDERR_EMPTY" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>return message.isEmpty() && level >= WARN;</expression>
</evaluator>
<OnMatch>NEUTRAL</OnMatch>
<OnMismatch>DENY</OnMismatch>
</filter>
<encoder>
<pattern>%n</pattern>
</encoder>
<target>System.err</target>
</appender>
<!-- Root logger. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="STDOUT_EMPTY" />
<appender-ref ref="STDERR" />
<appender-ref ref="STDERR_EMPTY" />
</root>
</configuration>
使用这个设置,我的先前的测试代码会产生以下输出:
17:00:37.188 [main] INFO pkg.LogbackMain - Message A: Single line message.
17:00:37.188 [main] INFO pkg.LogbackMain - Message B: The message after this one will be empty.
17:00:37.203 [main] INFO pkg.LogbackMain - Message C: The message before this one was empty.
17:00:37.203 [main] INFO pkg.LogbackMain -
Message D: Message with a linebreak at the beginning.
17:00:37.203 [main] INFO pkg.LogbackMain - Message E: Message with a linebreak at the end.
17:00:37.203 [main] INFO pkg.LogbackMain - Message F: Message with
a linebreak in the middle.
注意,现在使用空消息的日志记录语句会创建一个空行,这是期望的结果。因此,此解决方案有效。但是,正如我上面所说的那样,必须创建每个Appender的副本非常繁琐,并且肯定不具有可扩展性。更不用说,为了实现如此简单的结果而做出所有这些工作似乎过度了。
因此,我将我的问题提交到Stack Overflow,并提出问题:有更好的方法吗?
P.S. 最终注释,最好是仅使用配置就能解决的方法;如果可能的话,我希望避免编写自定义Java类(过滤器、标记等)来实现此效果。原因是,我正在开发的项目是一种“元项目”--它是一个根据用户条件生成其他程序的程序,而那些生成的程序就是Logback的位置。因此,我编写的任何自定义Java代码都必须复制到那些生成的程序中,如果可能的话,我宁愿避免这样做。
编辑: 我认为真正关键的是:是否有一种方法可以将条件逻辑嵌入到Appender的布局模式中?换句话说,是否有一个Appender可以使用标准布局模式,但在某些情况下有条件地修改(或忽略)该模式?本质上,我想告诉我的Appender,“使用这些过滤器和这个输出目标,并在条件X为真时使用此模式,否则使用另一个模式。”我知道某些转换术语(如%caller
和%exception
)允许您将评估器附加到它们上面,以便只有当评估器返回true
时才显示该术语。问题是,大多数术语不支持该功能,我当然不知道任何一种将评估器一次应用于整个模式的方法。因此,每个Appender需要拆分成两个,每个都具有自己单独的评估器和模式:一个用于空消息,另一个用于非空消息。