Meg*_*Jar 10 java logging conditional newline logback
我有一个Java应用程序,它设置为使用SLF4J/Logback.我似乎无法找到一种简单的方法使Logback输出在两个其他日志条目之间完全空行.空白行不应包含编码器的图案; 它应该是BLANK.我在网上搜索了一个简单的方法来做到这一点,但是空洞了.
我有以下设置:
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>
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这会产生以下输出:
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.
Run Code Online (Sandbox Code Playgroud)
如您所见,这些日志记录语句都不能按我需要的方式工作.
在对Evaluators,Markers等进行了大量实验之后,我终于找到了一个解决方案,虽然相当笨拙,却具有预期的效果.这是一个两步解决方案:
生成的文件如下所示:
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>
Run Code Online (Sandbox Code Playgroud)
使用此设置,我之前的测试代码生成以下输出:
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.
Run Code Online (Sandbox Code Playgroud)
请注意,带有空消息的日志记录语句现在会根据需要创建一个空行.所以此解决方案有效.但是,正如我上面所说的,必须创建每个Appender的副本是非常笨拙的,而且它当然不是很可扩展.更不用说,为了实现这么简单的结果,完成所有这些工作似乎是一种重大的过度杀伤力.
所以,我将问题提交给Stack Overflow,问题是:有更好的方法吗?
PS作为最后一点,仅配置解决方案将是更可取的; 如果可能的话,我想避免编写自定义Java类(过滤器,标记等)以获得此效果.原因是,我正在研究的项目是一种"元项目" - 它是一个根据用户标准生成OTHER程序的程序,而那些生成的程序是Logback将存在的地方.因此,我编写的任何自定义Java代码都必须复制到那些生成的程序中,如果我能避免它,我宁愿不这样做.
编辑:我认为它真正归结为:有没有办法将条件逻辑嵌入到Appender的布局模式中?换句话说,要使用一个使用标准布局模式的Appender,但在某些情况下有条件地修改(或忽略)该模式?基本上,我想告诉我的Appender,"使用这些过滤器和此输出目标,并使用此模式IF条件X为真,否则使用此其他模式." 我知道某些转换术语(例如%caller和%exception)允许您将Evaluator附加到它们,因此只有在Evaluator返回时才会显示该术语true.问题是,大多数术语不支持该功能,我当然不知道有任何方法可以立即将评估器应用于整个模式.因此,需要将每个Appender分成两个,每个Appender都有自己独立的评估器和模式:一个用于空白消息,一个用于非空消息.
我已经玩了一些这个,我想出了另一种方法来实现我想要的效果.现在,这个解决方案涉及编写自定义Java代码,这意味着它实际上对我的具体情况没有帮助(因为,正如我上面所说,我需要一个仅配置的解决方案).但是,我想我也可以发布它,因为(a)它可以帮助其他人有同样的问题,并且(b)它似乎在许多其他用例除了添加空行之外也有用.
无论如何,我的解决方案是编写我自己的Converter类,命名ConditionalCompositeConverter,用于表示编码器/布局模式中的通用"if-then"逻辑(例如"如果Y为真,则仅显示X").与%replace转换字一样,它扩展CompositeConverter(因此可能包含子转换器); 它还需要一个或多个评估员,它们提供测试条件.源代码如下:
ConditionalCompositeConverter.java
package converter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.pattern.CompositeConverter;
import ch.qos.logback.core.status.ErrorStatus;
public class ConditionalCompositeConverter extends CompositeConverter<ILoggingEvent>
{
private List<EventEvaluator<ILoggingEvent>> evaluatorList = null;
private int errorCount = 0;
@Override
@SuppressWarnings("unchecked")
public void start()
{
final List<String> optionList = getOptionList();
final Map<?, ?> evaluatorMap = (Map<?, ?>) getContext().getObject(CoreConstants.EVALUATOR_MAP);
for (String evaluatorStr : optionList)
{
EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap.get(evaluatorStr);
if (ee != null)
{
addEvaluator(ee);
}
}
if ((evaluatorList == null) || (evaluatorList.isEmpty()))
{
addError("At least one evaluator is expected, whereas you have declared none.");
return;
}
super.start();
}
@Override
public String convert(ILoggingEvent event)
{
boolean evalResult = true;
for (EventEvaluator<ILoggingEvent> ee : evaluatorList)
{
try
{
if (!ee.evaluate(event))
{
evalResult = false;
break;
}
}
catch (EvaluationException eex)
{
evalResult = false;
errorCount++;
if (errorCount < CoreConstants.MAX_ERROR_COUNT)
{
addError("Exception thrown for evaluator named [" + ee.getName() + "].", eex);
}
else if (errorCount == CoreConstants.MAX_ERROR_COUNT)
{
ErrorStatus errorStatus = new ErrorStatus(
"Exception thrown for evaluator named [" + ee.getName() + "].",
this, eex);
errorStatus.add(new ErrorStatus(
"This was the last warning about this evaluator's errors. " +
"We don't want the StatusManager to get flooded.", this));
addStatus(errorStatus);
}
}
}
if (evalResult)
{
return super.convert(event);
}
else
{
return CoreConstants.EMPTY_STRING;
}
}
@Override
protected String transform(ILoggingEvent event, String in)
{
return in;
}
private void addEvaluator(EventEvaluator<ILoggingEvent> ee)
{
if (evaluatorList == null)
{
evaluatorList = new ArrayList<EventEvaluator<ILoggingEvent>>();
}
evaluatorList.add(ee);
}
}
Run Code Online (Sandbox Code Playgroud)
然后我在配置文件中使用此转换器,如下所示:
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<conversionRule conversionWord="onlyShowIf"
converterClass="converter.ConditionalCompositeConverter" />
<evaluator name="NOT_EMPTY_EVAL">
<expression>!message.isEmpty()</expression>
</evaluator>
<!-- 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>%onlyShowIf(%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg){NOT_EMPTY_EVAL}%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>%onlyShowIf(%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg){NOT_EMPTY_EVAL}%n</pattern>
</encoder>
<target>System.err</target>
</appender>
<!-- Root logger. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="STDERR" />
</root>
</configuration>
Run Code Online (Sandbox Code Playgroud)
我认为这比以前的解决方案更优雅,因为它允许我使用单个Appender来处理空白和非空白消息.的%onlyShowIf转换字告诉追加程序解析提供的模式像往常一样,除非该消息是空的,在这种情况下跳过整个事情.然后在转换单词结束后有换行符号,以确保打印换行符,无论消息是否为空.
这个解决方案的唯一缺点是主模式(包含子转换器)必须在FIRST中传递,作为括号内的参数,而评估器必须在最后通过花括号中的选项列表传递; 这意味着这个"if-then"结构必须在"if"部分之前具有"then"部分,这看起来有点不直观.
无论如何,我希望这证明对任何有类似问题的人都有帮助.我不打算"接受"这个答案,因为我仍然希望有人会提出一个仅适用于我的具体案例的配置解决方案.
| 归档时间: |
|
| 查看次数: |
7787 次 |
| 最近记录: |