У меня есть приложение 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>
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, и он, конечно, не очень масштабируемый. Не говоря уже о том, что для достижения такого простого результата кажется серьезным излишеством сделать эту работу.
Итак, я отправляю свою проблему в Qaru с вопросом: Есть ли лучший способ сделать это?
P.S. В качестве заключительного замечания было бы предпочтительным решение только для конфигурации; Я хотел бы избежать необходимости писать собственные классы Java (фильтры, маркеры и т.д.), Чтобы получить этот эффект, если это возможно. Разумеется, проект, над которым я работаю, является своего рода "метапроектом" - это программа, которая генерирует ДРУГИЕ программы, основанные на пользовательских критериях, и те сгенерированные программы, где будет работать Logback. Поэтому любой пользовательский код Java, который я пишу, должен быть скопирован в эти сгенерированные программы, и я бы предпочел не делать этого, если бы мог его избежать.
EDIT: Я думаю, что это действительно сводится к следующему: есть ли способ вставить условную логику в шаблон макета Appender? Другими словами, иметь Appender, который использует стандартный шаблон макета, но условно модифицировать (или игнорировать) этот шаблон в определенных случаях? По сути, я хочу сказать моему Appender: "Используйте эти фильтры (фильтры) и эту целевую точку вывода и используйте этот шаблон IF условие X истинно, иначе используйте этот другой шаблон". Я знаю, что определенные термины преобразования (например, %caller
и %exception
) позволяют вам присоединить к ним Оценщик, так что этот термин отображается только в том случае, если Evaluator возвращает true
. Проблема в том, что большинство терминов не поддерживают эту функцию, и я, конечно, не знаю, как применить метод Evaluator к шаблону ENTIRE сразу. Следовательно, необходимо разделить каждого Appender на два, каждый со своим собственным отдельным оценщиком и шаблоном: один для пустых сообщений и один для непустых сообщений.