I have a Java application that's set up to use SLF4J/Logback. I can't seem to find a simple way to make Logback output a completely blank line between two other log entries. The blank line should not include the encoder's pattern; it should just be BLANK. I've searched all over the Web for a simple way to do this, but came up empty.
I have the following setup:
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 (test code)
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();
}
}
This produces the following output:
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.
As you can see, none of these logging statements work the way I need.
- If I just log an empty string, the encoder's pattern is still prepended to the message, even though the message is empty.
- If I embed a newline character at the beginning or in the middle of the string, everything after that will be missing the pattern prefix, because the pattern is only applied once, at the beginning of the message.
- If I embed a newline character at the end of the string, it does create the desired blank line, but this is still only a partial solution; it still doesn't allow me to output a blank line BEFORE a logged message.
After much experimentation with Evaluators, Markers, etc, I finally arrived at a solution that, while quite unwieldy, has the desired effect. It's a two-step solution:
- Modify the filters in each existing Appender, so that they only allow non-empty messages.
- Create a duplicate of each Appender; modify the duplicates so that their filters only allow empty messages, and their patterns only contain a newline token.
The resulting file looks like this:
logback.xml (modified)
<?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>
With this setup, my previous test code produces the following output:
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.
Notice that the logging statement with an empty message now creates a blank line, as desired. So this solution works. However, as I said above, it's quite unwieldy to have to create a duplicate of every Appender, and it's certainly not very scalable. Not to mention, it seems like major overkill to do all this work to achieve such a simple result.
And so, I submit my problem to Stack Overflow, with the question: Is there a better way to do this?
P.S. As a final note, a configuration-only solution would be preferable; I'd like to avoid having to write custom Java classes (Filters, Markers, etc) to get this effect, if possible. Reason being, the project I'm working on is a kind of "meta project" -- it's a program that generates OTHER programs, based on user criteria, and those generated programs are where Logback will live. So any custom Java code I write would have to be copied over to those generated programs, and I'd rather not do that if I can avoid it.
EDIT: I think what it really boils down to is this: Is there a way to embed conditional logic into an Appender's layout pattern? In other words, to have an Appender that uses a standard layout pattern, but conditionally modify (or ignore) that pattern in certain instances? Essentially, I want to tell my Appender, "Use these filter(s) and this output target, and use this pattern IF condition X is true, otherwise use this other pattern." I know that certain conversion terms (like %caller
and %exception
) allow you to attach an Evaluator to them, so that the term is only displayed if the Evaluator returns true
. Problem is, most terms don't support that feature, and I certainly don't know of any way to apply an Evaluator to the ENTIRE pattern at once. Hence, the need for splitting each Appender into two, each with its own separate evaluator and pattern: one for blank messages, and one for non-blank messages.