Программно настроить приложение 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);
        }
    }

с моим новым уровнем корневого регистратора.

Другие вопросы по тегам