如何使Logback日志成为空白行,而不包括模式字符串?

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 &lt;= 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等进行了大量实验之后,我终于找到了一个解决方案,虽然相当笨拙,却具有预期的效果.这是一个两步解决方案:

  1. 修改每个现有Appender中的过滤器,以便它们只允许非空消息.
  2. 创建每个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() &amp;&amp; level &lt;= 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() &amp;&amp; level &lt;= 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() &amp;&amp; level &gt;= 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() &amp;&amp; level &gt;= 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都有自己独立的评估器和模式:一个用于空白消息,一个用于非空消息.

Meg*_*Jar 6

我已经玩了一些这个,我想出了另一种方法来实现我想要的效果.现在,这个解决方案涉及编写自定义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 &lt;= 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"部分,这看起来有点不直观.

无论如何,我希望这证明对任何有类似问题的人都有帮助.我不打算"接受"这个答案,因为我仍然希望有人会提出一个仅适用于我的具体案例的配置解决方案.