Программно настроить приложение LogBack
У меня есть приложение для входа в систему, определенное в logback.xml, это приложение для БД, но мне любопытно, есть ли способ настроить приложение в Java, используя мой собственный пул соединений, определенный как bean-компонент.
Я нахожу похожие вещи, но никогда не отвечаю.
7 ответов
Вот простой пример, который работает для меня (обратите внимание, что я использую FileAppender в этом примере)
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;
public class Loggerutils {
public static void main(String[] args) {
Logger foo = createLoggerFor("foo", "foo.log");
Logger bar = createLoggerFor("bar", "bar.log");
foo.info("test");
bar.info("bar");
}
private static Logger createLoggerFor(String string, String file) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
ple.setContext(lc);
ple.start();
FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
fileAppender.setFile(file);
fileAppender.setEncoder(ple);
fileAppender.setContext(lc);
fileAppender.start();
Logger logger = (Logger) LoggerFactory.getLogger(string);
logger.addAppender(fileAppender);
logger.setLevel(Level.DEBUG);
logger.setAdditive(false); /* set to true if root should log too */
return logger;
}
}
В качестве справки, когда вы пытаетесь изменить код, отвечающий за создание регистраторов, существует множество правил, которые должны быть выполнены, чтобы регистратор работал.
Эти правила были описаны в отличной и полезной статье. Программная настройка slf4j / logback:
Теперь у меня есть опыт программной настройки slf4j/logback.
задача
Программа должна открывать отдельный файл журнала для каждого обработанного входного файла.
Решение для задачи
Вместо того, чтобы настраивать обратный вызов через xml, нужно "вручную" создать экземпляры кодировщиков, добавщиков и регистраторов, а затем настроить и связать их вместе.
Предостережение 1
Logback сходит с ума при попытке разделить кодировщик (например, PatternLayoutEncoder) между приложениями.
Решение для предостережения 1
Создайте отдельный кодировщик для каждого приложения.
Предостережение 2
Logback отказывается что-либо регистрировать, если кодеры и добавщики не связаны с контекстом ведения журнала.
Решение для предостережения 2
Вызовите setContext для каждого кодировщика и приложения, передав в качестве параметра LoggerFactory.
Предостережение 3
Logback отказывается что-либо регистрировать, если кодировщики и приложения не запущены.
Решение для предостережения 3
кодировщики и аппендеры должны быть запущены в правильном порядке, т.е. сначала кодировщики, затем аппендеры.
Предостережение 4
Объекты RollingPolicy (то есть TimeBasedRollingPolicy) выдают странные сообщения об ошибках, такие как "формат даты не распознан", когда они не привязаны к тому же контексту, что и appender.
Решение для предостережения 4
Вызовите setContext для RollingPolicy так же, как для кодировщиков и дополнителей.
Вот рабочий пример "ручной" настройки входа в систему:
package testpackage
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy
import org.slf4j.LoggerFactory
class TestLogConfig {
public static void main(String[] args) {
LoggerContext logCtx = LoggerFactory.getILoggerFactory();
PatternLayoutEncoder logEncoder = new PatternLayoutEncoder();
logEncoder.setContext(logCtx);
logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
logEncoder.start();
ConsoleAppender logConsoleAppender = new ConsoleAppender();
logConsoleAppender.setContext(logCtx);
logConsoleAppender.setName("console");
logConsoleAppender.setEncoder(logEncoder);
logConsoleAppender.start();
logEncoder = new PatternLayoutEncoder();
logEncoder.setContext(logCtx);
logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
logEncoder.start();
RollingFileAppender logFileAppender = new RollingFileAppender();
logFileAppender.setContext(logCtx);
logFileAppender.setName("logFile");
logFileAppender.setEncoder(logEncoder);
logFileAppender.setAppend(true);
logFileAppender.setFile("logs/logfile.log");
TimeBasedRollingPolicy logFilePolicy = new TimeBasedRollingPolicy();
logFilePolicy.setContext(logCtx);
logFilePolicy.setParent(logFileAppender);
logFilePolicy.setFileNamePattern("logs/logfile-%d{yyyy-MM-dd_HH}.log");
logFilePolicy.setMaxHistory(7);
logFilePolicy.start();
logFileAppender.setRollingPolicy(logFilePolicy);
logFileAppender.start();
Logger log = logCtx.getLogger("Main");
log.additive = false;
log.level = Level.INFO;
log.addAppender(logConsoleAppender);
log.addAppender(logFileAppender);
}
}
Вы можете настроить appenders программно. Почти все аппендеры тестируются с использованием программной конфигурации. Отсюда следует, что в исходном коде проекта logback есть много примеров программной настройки приложения. Для приложения с ядром входа в систему, смотрите под logback-core/src/test/java
и для logback-классического appender смотрите под logback-classic/src/test/java
,
Просто, если бы кто-то искал конкретный пример программной конфигурации.
Здесь я настраиваю кодировку ConsoleAppender:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
ConsoleAppender<ILoggingEvent> appender =
(ConsoleAppender) lc.getLogger("appconsole").getAppender("STDOUT");
LayoutWrappingEncoder<ILoggingEvent> enc =
(LayoutWrappingEncoder<ILoggingEvent>) appender.getEncoder();
enc.setCharset(Charset.forName("utf-8"));
И мой logback.xml:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>866</charset>
<pattern>[%level] %msg%n</pattern>
</encoder>
</appender>
<logger name="appconsole">
<appender-ref ref="STDOUT" />
</logger>
Почему мне нужно программно настроить логгер? Потому что я упаковываю свое приложение (Spring Boot) в файл jar. Следовательно, файл Logback.xml оказывается скрытым внутри фляги. Однако распаковывать и менять его неудобно. И мне не нужен файл logback.xml, кроме моего app.jar. У меня есть только файл app.yaml, который содержит все свойства конфигурации для приложения.
В текущих версиях (logback=1.3.0-alpha16,slf4j=2.0.0-alpha7) вы можете использовать SPI для программного построения вашей конфигурации.
Вам нужен текстовый файл в вашей банке с именем: расположенный в
META-INF/services
внутри вашей банки, чтобы Logback забрал ее. Файл должен содержать полное квалифицированное имя класса вашего класса, который выполняет инициализацию.
Затем создайте класс, который выполняет инициализацию следующим образом:
package example
import java.nio.charset.Charset;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.spi.ContextAwareBase;
public class ProgrammaticLoggingConfigurator extends ContextAwareBase implements Configurator {
@Override
public void configure(LoggerContext loggerContext) {
PatternLayout pattern = new PatternLayout();
pattern.setContext(loggerContext);
pattern.setPattern("[%level] %msg%n");
pattern.start();
LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
encoder.setContext(loggerContext);
encoder.setCharset(Charset.forName("utf-8"));
encoder.setLayout(pattern);
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
consoleAppender.setContext(loggerContext);
consoleAppender.setName("console");
consoleAppender.setEncoder(encoder);
consoleAppender.start();
Logger log = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
log.setAdditive(false);
log.setLevel(Level.DEBUG);
log.addAppender(consoleAppender);
}
}
Итак, в этом примере должен быть файл с именем
ch.qos.logback.classic.spi.Configurator
с содержанием:
example.ProgrammaticLoggingConfigurator
чтобы его забрали.
Загвоздка в том, что нам нужно привести его к типу ch.qos.logback.classic.Logger, а затем программно установить аппендеры.
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
log.addAppender(mockAppender);
Не разрешено комментировать (пока?), Я просто хотел бы добавить три совета;
что касается оговорок выше, если у вас есть проблемы, просто добавьте вызов
StatusPrinter.print(context);
после того, как все настроено, то есть после добавления ваших аппендеров root/"Main" appender: он скажет вам, что не так.
Мне очень нравится разделять уровни журналирования в разных файлах; при поиске ошибок я начинаю с поиска в файле ошибок и т. д., настраивая их как
tot_[app name].log : Level.INFO deb_[app name].log : Level.DEBUG err_[app name].log : Level.ERROR
маршрутизация с помощью простого частного класса фильтра, такого как
private static class ThresholdLoggerFilter extends Filter<ILoggingEvent> {
private final Level level;
private ThresholdLoggerFilter(Level level){
this.level = level;
}
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getLevel().isGreaterOrEqual(level)) {
return FilterReply.NEUTRAL;
} else {
return FilterReply.DENY;
}
}
}
а потом просто позвони myFilter.start()
а также myAppender.addFilter(myFilter);
,
И, наконец, собирая все вместе, я, как правило, хочу иметь возможность динамически изменять уровни журналов, когда в установке реализован простой интерфейс, такой как
public interface LoggingService { void setRootLogLevel(Level level); }
сохранение корневого уровня ведения журнала в некотором файле свойств, который отслеживается таким образом, чтобы при наличии какого-либо действительного ввода я просто назвал эту службу реализованной так:
@Override
public void setRootLogLevel(Level level) {
if (context != null && context.isStarted()) {
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(level);
}
}
с моим новым уровнем корневого регистратора.