我有一个设置为使用 SLF4J/Logback 的 Java 应用程序。我似乎找不到一种简单的方法来使 Logback 在其他两个日志条目之间输出一个完全空白的行。空行不应包含编码器的模式;它应该只是空白。我在网上搜索了一个简单的方法来做到这一点,但结果是空的。
我有以下设置:
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,并提出以下问题:有没有更好的方法来做到这一点?
PS 作为最后一点,仅配置的解决方案会更可取;如果可能的话,我想避免编写自定义 Java 类(过滤器、标记等)来获得这种效果。原因是,我正在从事的项目是一种“元项目”——它是一个根据用户标准生成其他程序的程序,而这些生成的程序就是 Logback 所在的地方。因此,我编写的任何自定义 Java 代码都必须复制到那些生成的程序中,如果可以避免的话,我宁愿不这样做。
编辑:我认为它真正归结为:有没有办法将条件逻辑嵌入到 Appender 的布局模式中?换句话说,有一个 Appender 使用标准布局模式,但在某些情况下有条件地修改(或忽略)该模式?本质上,我想告诉我的 Appender,“使用这些过滤器和这个输出目标,如果条件 X 为真,则使用这个模式,否则使用这个其他模式。” 我知道某些转换术语(例如%caller
和%exception
)允许您将 Evaluator 附加到它们,以便仅在 Evaluator 返回时才显示该术语true
. 问题是,大多数术语不支持该功能,我当然不知道有什么方法可以一次将 Evaluator 应用于整个模式。因此,需要将每个 Appender 分成两部分,每一个都有自己独立的评估器和模式:一个用于空白消息,一个用于非空白消息。