В этой статье подробно объясняется структура ведения журнала в Springboot.

Spring Boot

личный блог:Glmapepr - Отступление от процедурного мира
Колонка самородков:glmapper

Спасибо за внимание, лайки и комментарии.В конце статьи есть QR-код вашего личного публичного номера.Заинтересованные студенты могут подписаться на него или добавить мой личный WeChat для общения и совместного обучения.

Рекомендуемое чтение

содержание

формат журналаКонсольный выводВыход с цветовой кодировкойвыходной файлуровень журналаГруппы журналовПользовательская конфигурация журналаконфигурация SpringProfileзапись инициализации журналаОбработка фазы ApplicationStartingEventОбработка фазы ApplicationEnvironmentPreparedEventОбработка фазы ApplicationPreparedEventContextClosedEvent и ApplicationFailedEventАнализ системы регистрацииЛогика обработки AbstractLoggingSystemЛогика обработки Log4J2LoggingSystemРеализация beforeInitialize в Log4J2LoggingSystemРеализация инициализации в Log4J2LoggingSystemЛогика очистки системы журналанекоторый анализ сценынет файлов конфигурацииНастройте log4j2.xml в каталоге ресурсов.Настройте log4j2-glmapper.xml в ресурсах.резюме

Spring Boot использует Commons Logging для всех внутренних журналов, но сохраняет базовую реализацию журналов. Конфигурации по умолчанию предоставляются для Java Util Logging, Log4J2 и Logback. В каждом случае регистраторы предварительно настроены на использование вывода консоли, а также доступен дополнительный вывод в файл.

По умолчанию, если используется «starters», для ведения журнала используется Logback. Соответствующая маршрутизация Logback также включена, чтобы гарантировать правильную работу зависимых библиотек, использующих ведение журнала Java Util, ведение журнала Commons, Log4J или SLF4J.

Давайте сначала посмотрим на вывод журнала простейшего демонстрационного проекта SpringBoot, чтобы расширить представление о формате журнала, выводе консоли, цвете журнала, конфигурации файла журнала, анализе системы журнала и других аспектах.

Создайте новый проект SpringBoot и запустите его напрямую, ничего не добавляя по умолчанию.Журнал запуска выглядит следующим образом:

2019-12-24 20:41:31.866  INFO 87851 --- [           main] com.glmapper.bridge.boot.BootStrap       : No active profile set, falling back to default profiles: default
2019-12-24 20:41:32.003  INFO 87851 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@314c508a: startup date [Tue Dec 24 20:41:31 CST 2019]; root of context hierarchy
2019-12-24 20:41:32.556  INFO 87851 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2019-12-24 20:41:32.568  INFO 87851 --- [           main] com.glmapper.bridge.boot.BootStrap       : Started BootStrap in 1.035 seconds (JVM running for 2.13)
2019-12-24 20:41:32.569  INFO 87851 --- [       Thread-4] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@314c508a: startup date [Tue Dec 24 20:41:31 CST 2019]; root of context hierarchy
2019-12-24 20:41:32.571  INFO 87851 --- [       Thread-4] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Process finished with exit code 0

формат журнала

Выше приведен вывод журнала Spring Boot по умолчанию, С точки зрения формата журнала он в основном включает следующие элементы:

  • Дата и время: например, 2019-12-24 20:41:31,866 (с точностью до миллисекунды)
  • Уровень журнала: например, INFO (ERROR, WARN, INFO, DEBUG или TRACE.)
  • Текущий процесс: например, 87851
  • --- Разделитель, используемый для обозначения начала фактического сообщения журнала.
  • Название темы: например, Thread-4 (заключено в квадратные скобки (может быть усечено для вывода на консоль)).
  • Имя журнала: обычно это имя исходного класса (обычно сокращенное).
  • сообщение журнала: конкретное сообщение журнала

Например эта запись:

2019-12-24 20:41:31.866  INFO 87851 --- [           main] com.glmapper.bridge.boot.BootStrap       : No active profile set, falling back to default profiles: default

печатается в методе org.springframework.boot.SpringApplication#logStartupProfileInfo с уровнем журнала INFO.

Консольный вывод

SpringBoot по умолчанию будет выводить журналы в консоль.По умолчанию будут регистрироваться сообщения уровня ошибки, уровня предупреждения и уровня информации. также с помощью—-debugпараметр, чтобы запустить приложение для использования уровня «отладки».

java -jar myapp.jar --debug

Уровень отладки также можно включить, указав debug=true в application.properties.

Когда уровень отладки включен, ряд основных регистраторов (встроенный контейнер, Hibernate и Spring Boot) настраивается для вывода дополнительной информации. Включение режима отладки не настраивает приложение для регистрации всех сообщений уровня отладки. Точно так же вы также можете использовать—-traceФлаг для запуска режима уровня трассировки для запуска приложения.

Выход с цветовой кодировкой

Если ваш терминал поддерживает ANSI, вы можете указать цвет, задав значение элемента конфигурации «spring.output.ansi.enable» (при условии, что цвет официально поддерживается). Цветовое кодирование осуществляется с помощью%clrСамый простой — раскрасить выходной журнал в соответствии с уровнем журнала, как показано в следующем примере:

%clr(%5p)

Следующая таблица является официальным описанием таблицы отображения уровня журнала в цвет:

Level Color
FATAL Red
ERROR Red
WARN Yellow
INFO Green
DEBUG Green
TRACE Green

Если вы хотите сделать текст желтым, вы можете использовать следующие настройки:

%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}

В настоящее время поддерживаются следующие цвета и стили: синий, голубой, выцветший, зеленый, пурпурный, красный, желтый.

выходной файл

По умолчанию журналы Spring Boot выводятся только на консоль и не записываются в файлы журналов. Если вы хотите записывать в файл журнала в дополнение к выводу консоли, вам необходимо установить свойства logging.file и logging.path (в application.properties). В следующей таблице показано, как свойства logging.* используются вместе:

logging.file logging.path Example Description
none none журнал консоли
указанный файл none my.log Записывает указанный файл журнала, имя которого может быть точным местоположением или относительно текущего каталога.
none указанный файл /var/log Запишите spring.log в указанный каталог, имя может быть точным или относительным относительно текущего каталога.

Файл журнала сворачивается, когда он достигает 10 МБ, и, как и выходные данные консоли, сообщения уровня ERROR, WARN и INFO регистрируются по умолчанию. Ограничение размера можно изменить с помощью свойства logging.file.max-size. Ранее свернутые файлы будут архивироваться на неопределенный срок, если не установлено свойство logging.file.max-history.

Система ведения журнала инициализируется на ранней стадии жизненного цикла приложения. Поэтому свойство журнала не найдено в файлах свойств, загруженных с помощью аннотации @PropertySource. Кроме того, свойства ведения журнала не зависят от фактической инфраструктуры ведения журнала. Поэтому Spring Boot не управляет определенными ключами конфигурации (такими как logback.configurationFile Logback).

уровень журнала

Все системы журналирования, поддерживаемые в SpringBoot, могут бытьlogging.level.<logger-name>=<level>Установите уровень ведения журнала в среде Spring (например, в application.properties). Уровни журнала в основном включают TRACE, DEBUG, INFO, WARN, ERROR, FATAL и OFF. Кроме того, вы также можете использоватьlogging.level.rootНастройте уровень журнала корневого регистратора. В следующем примере показано, как настроить уровень журнала в application.properties:

logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error

В дополнение к application.properties уровень журнала также можно установить с помощью переменных среды. Например, LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG устанавливает уровень печати журнала в пакете org.springframework.web на DEBUG.

Вышеупомянутый метод работает только для ведения журнала на уровне пакета. так какRelaxed BindingПеременные среды всегда преобразуются в нижний регистр, поэтому таким образом невозможно настроить ведение журнала для отдельных классов. Если вам нужно настроить ведение журнала для класса, вы можете использоватьSPRING_APPLICATION_JSONПеременная.

Группы журналов

Часто полезно сгруппировать связанные регистраторы вместе, чтобы их можно было настроить одновременно, Spring Boot позволяет определять группы журналов в среде Spring. Например, добавьте группу «tomcat» в application.properties.

logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat

Таким образом, мы можем установить уровень журнала для набора журналов с помощью одной строки конфигурации:

logging.level.tomcat=TRACE

Spring Boot включает следующие предопределенные группы журналов, которые можно использовать «из коробки»:

Name Loggers
web org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sql org.springframework.jdbc.core, org.hibernate.SQL

Пользовательская конфигурация журнала

Различные системы журналирования можно активировать, включив соответствующую библиотеку в путь к классам, а также предоставив соответствующий файл конфигурации в корне пути к классам или в среде Spring.logging.configРасположение, указанное свойством, предоставляет соответствующий файл конфигурации для дальнейшей настройки системы ведения журнала.

Например, вы можете использоватьorg.springframework.boot.logging.LoggingSystemСвойство конфигурации заставляет Spring Boot использовать указанную систему ведения журнала. Значение должно быть полным именем класса реализации LoggingSystem; если указано значение none , это означает, что конфигурация ведения журнала Spring Boot полностью отключена. В следующей таблице описаны файлы конфигурации журнала, соответствующие системе ведения журнала в SpringBoot:

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
Log4j2 org.springframework.jdbc.core, org.hibernate.SQL
JDK (Java Util Logging) logging.properties

SpringBoot официально рекомендует использовать его в конфигурации журнала-springнастроен (например, с помощьюlogback-spring.xmlвместоlogback.xml). Spring не имеет полного контроля над инициализацией журнала, если используются стандартные расположения конфигурации.

Кроме того, в официальной документации четко указано, что JUL (ava Util Logging) имеет некоторые известные проблемы с загрузкой классов в сценарии FATJAR, поэтому старайтесь избегать использования JUL в сценарии FATJAR.

Чтобы помочь в настройке системы ведения журналов, Spring установит свойства переменных среды в системные свойства, как показано в следующей таблице:

Spring Environment System Property Comments
logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD слово преобразования, используемое при регистрации исключения
logging.file LOG_FILE Если он определен, он используется в конфигурации ведения журнала по умолчанию.
logging.file.max-size LOG_FILE_MAX_SIZE Максимальный размер файла журнала (если включен LOG_FILE). (Поддерживает только настройки Logback по умолчанию)
logging.file.max-history LOG_FILE_MAX_HISTORY Максимальное количество сохраняемых архивных файлов журнала (если включен LOG_FILE). (Поддерживаются только настройки Logback по умолчанию.)
logging.path LOG_PATH Если он определен, он используется в конфигурации ведения журнала по умолчанию.
logging.pattern.console CONSOLE_LOG_PATTERN Режим ведения журнала для использования на консоли (stdout). (Поддерживаются только настройки Logback по умолчанию.)
logging.pattern.dateformat LOG_DATEFORMAT_PATTERN Дополнительный режим для формата даты журнала. (Поддерживаются только настройки Logback по умолчанию.)
logging.pattern.file FILE_LOG_PATTERN Максимальный размер файла журнала (если включен LOG_FILE). (Поддерживает только настройки Logback по умолчанию)
logging.pattern.level LOG_LEVEL_PATTERN Формат для использования при рендеринге уровней журнала (по умолчанию %5p). (Поддерживаются только настройки Logback по умолчанию.)
PID PID идентификатор текущего процесса

Все поддерживаемые системы журналов могут обращаться к системным свойствам для анализа конфигурации при анализе файлов конфигурации.

Если вы хотите использовать заполнители в свойствах журнала, вам следует использовать синтаксис SpringBoot, а не синтаксис базовой платформы. Обратите внимание, что при использовании Logback вы должны использовать:в качестве разделителя между именем свойства и его значением по умолчанию вместо использования:-.

конфигурация SpringProfile

Позволяет пользователям включать или исключать разделы конфигурации на основе активных профилей Spring. Раздел файла профиля поддерживается в любом месте элемента. Вы можете использовать атрибут имени, чтобы указать, какой файл конфигурации принимает конфигурацию. Может содержать простые имена профилей (например, dev ) или выражения профилей. Выражение профиля допускает более сложную логику профиля, например: "production & (eu-central | eu-west)". Ниже показаны три примера файлов конфигурации:

xml <springProfile name="dev"> <!-- 激活 dev 环境的配置 --> </springProfile>

<springProfile name="dev | pre"> <!-- 激活 dev 和 pre 的环境变量 --> </springProfile>

<springProfile name="!prod"> <!-- 所有非 prod 环境的都激活 --> </springProfile>

## Свойства среды

Маркеры позволяют пользователям передавать свойства из Spring Environment для использования в Logback. Например, доступ к значениям в файле application.properties в конфигурации Logback. Механизм действия аналогичен стандартным тегам Logback. Однако вместо указания прямого значения укажите источник атрибута (из среды). Если вам нужно сохранить свойство в другом месте, кроме локальной области, вы можете использовать свойство области действия для управления этим. Если требуется значение по умолчанию (если свойство не задано в Environment), его можно настроить с помощью свойства defaultValue. В следующем примере показано, как передать свойства для использования в Logback:

xml <springProperty scope="context" name="fluentHost" source="myapp.fluentd.host" defaultValue="localhost"/> <appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender"> <remoteHost>${fluentHost}</remoteHost> ... </appender>

Основываясь на официальной документации SpringBoot, было кратко представлено описание поддержки Logger.Далее будет проанализирован исходный код, чтобы глубоко понять вышеуказанные функции. В этой статье в качестве примера для анализа используется log4j2.

существуетСерия SpringBoot - подробное объяснение механизма событийВ статье действительно упоминалось время инициализации логирования. Вот краткий обзор:

# Application Listeners
org.springframework.context.ApplicationListener=\
// 省略其他
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\

Среди двух прослушивателей журналов основной функцией является LoggingApplicationListener , которая является точкой входа для инициализации журнала в SpringBoot.

запись инициализации журнала

LoggingApplicationListener наследует интерфейс GenericApplicationListener, а его родительским интерфейсом является ApplicationListener.GenericApplicationListener расширяет возможности поддержки для типов событий. Основной проблемой здесь является метод обратного вызова onApplicationEvent.Для нескольких типов событий, упомянутых в этом методе, вы можете обратиться кСерия SpringBoot - подробное объяснение механизма событийВведение к этой статье.

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // ApplicationStartingEvent 
    if (event instanceof ApplicationStartingEvent) {
        onApplicationStartingEvent((ApplicationStartingEvent) event);
    }
    // ApplicationEnvironmentPreparedEvent 
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    // ApplicationPreparedEvent
    else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    // ContextClosedEvent
    else if (event instanceof ContextClosedEvent
            && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    }
    // ApplicationFailedEvent
    else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

Обработка фазы ApplicationStartingEvent

При получении события ApplicationStartingEvent SpringBoot создаст объект loggingSystem через текущий примененный загрузчик классов, а затем выполнит некоторую подготовительную работу перед инициализацией.

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    // 通过当前应用的 classloader 构建 loggingSystem 对象
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    // loggingSystem 初始化之前准备
    this.loggingSystem.beforeInitialize();
}

Здесь мы можем увидеть, как устроена loggingSystem.Этот процесс позволяет нам четко понять, почему, вводя зависимости фреймворка ведения журнала или используяorg.springframework.boot.logging.LoggingSystemКонфигурация может автоматически завершить выбор среды ведения журнала.

public static LoggingSystem get(ClassLoader classLoader) {
    // SYSTEM_PROPERTY=org.springframework.boot.logging.LoggingSystem
    // 这里先从系统变量中获取下 org.springframework.boot.logging.LoggingSystem,看下是否用户自己指定了 LoggingSystem 的类型
    String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
    // 如果 org.springframework.boot.logging.LoggingSystem=xx 有配置值 
    if (StringUtils.hasLength(loggingSystem)) {
        // 是否配置的是 none
        if (NONE.equals(loggingSystem)) {
            // 如果配置的是 none ,则返回 NoOpLoggingSystem
            return new NoOpLoggingSystem();
        }
        // 根据指定的日志类型通过反射创建 loggingSystem 对象
        return get(classLoader, loggingSystem);
    }
    return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
            .map((entry) -> get(classLoader, entry.getValue())).findFirst()
            .orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}

Последний из приведенных выше кодов выполняет ряд операций на основе данных структуры Map в SYSTEMS, в основном оценивая, существует ли entry.getKey() в текущем пути к классам, и если да, то создавая объект типа entry.getValue( ) через отражение; SYSTEMS — это статическая структурная переменная MAP в абстрактном классе LoggingSystem, инициализация которой выполняется в статическом блоке кода:

static {
    Map<String, String> systems = new LinkedHashMap<>();
    // 添加 logback 的 LoggingSystem
    systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");、
     // 添加 log4j2 的 LoggingSystem
    systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
            "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
    // 添加 JUL 的 LoggingSystem
    systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
    SYSTEMS = Collections.unmodifiableMap(systems);
}

Это кажется относительно ясным.Если в текущем пути к классам есть зависимость от logback, log4j2 или JUL, будет создан соответствующий объект LoggingSystem. После того, как объект LoggingSystem будет создан, будет вызван метод beforeInitialize. beforeInitialize — это абстрактный метод, предоставляемый LoggingSystem, а его конкретная реализация реализуется подклассами. Анализ будет проводиться в разделе анализа исходного кода ниже.

Обработка фазы ApplicationEnvironmentPreparedEvent

Получение события ApplicationEnvironmentPreparedEvent указывает на то, что объект Environment создан и переменные среды инициализированы. Таким образом, основная задача здесь — инициализировать структуру ведения журнала.

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    // 这里会再 check 一次loggingSystem 是否已经被创建
    if (this.loggingSystem == null) {
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    }
    // 通过环境和类路径表达的首选项初始化日志系统。
    initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
// initialize
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
    // Spring 环境转移到系统属性
    new LoggingSystemProperties(environment).apply();
    // 解析得到 logFile,依赖 logging.file 和 loggin.path 两个配置值
    this.logFile = LogFile.get(environment);
    if (this.logFile != null) {
        //设置logging.file->LOG_FILE
        // loggin.path -> LOG_PATH
        this.logFile.applyToSystemProperties();
    }
    initializeEarlyLoggingLevel(environment);
    // 根据 log 的配置文件初始化 日志
    initializeSystem(environment, this.loggingSystem, this.logFile);
    // 绑定 logging.group , 设置 logging.level
    initializeFinalLoggingLevels(environment, this.loggingSystem);
    // 注册 logging.register-shutdown-hook 配置的 钩子
    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

На этом этапе выполняется ряд операций по инициализации журнала в соответствии с настроенными нами свойствами и файлами конфигурации, связанными с журналом. Свойства и конфигурации, задействованные здесь, упоминались в предыдущей части статьи.

Обработка фазы ApplicationPreparedEvent

Получение события ApplicationPreparedEvent указывает на то, что приложение готово.Здесь будут зарегистрированы два bean-компонента, один — springBootLoggingSystem, а другой — pringBootLogFile.

private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
    ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
    // 注册 springBootLoggingSystem bean 
    if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
        beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
    }
    // 注册 pringBootLogFile bean
    if (this.logFile != null && !beanFactory.containsBean(LOGFILE_BEAN_NAME)) {
        beanFactory.registerSingleton(LOGFILE_BEAN_NAME, this.logFile);
    }
}

ContextClosedEvent и ApplicationFailedEvent

Событие ContextClosedEvent — это событие, отправляемое при закрытии контейнера Spring, и в основном это некоторые операции по очистке системы журналов, когда контейнер Spring закрывается; ApplicationFailedEvent — это событие, отправляемое, когда приложение не запускается, и система журналов здесь тоже убираться. Конкретная реализация метода очистки обеспечивается каждой подсистемой LoggingSystem.В качестве примера возьмем log4j2.Очистка log4j2 в основном включает в себя выход из процессора моста (упомянутый на предыдущем этапе инициализации), установку LogContext на null и удаление FILTER , который в основном является обратным этапу инициализации процесса.

Анализ системы регистрации

LoggingSystem — это уровень абстрактной инкапсуляции фреймворка логирования с помощью SpringBoot.LoggingSystem позволяет нам легко использовать некоторые фреймворки логирования.Нам нужно только определить файлы конфигурации, соответствующие фреймворку логирования, такие как Logback, Log4j, Log4j2 и т. д., которые можно использовать непосредственно внутри кода.

На приведенном выше рисунке показана структура наследования классов LoggingSystem.Вы можете видеть, что подклассы реализации LoggingSystem включают Logback (LogbackLoggingSystem), Log4j2 (Log4J2LoggingSystem) и встроенный журнал JDK (JavaLoggingSystem). LoggingSystem — это абстрактный класс со следующими методами:

  • beforeInitialize: вещи, которые необходимо обработать перед инициализацией системы журналов.
  • инициализировать: инициализировать систему ведения журнала
  • cleanUp: очистка лог-системы
  • getShutdownHandler: возвращает Runnable для обработки операций, которые необходимо выполнить после закрытия системы ведения журнала при выходе из jvm, и по умолчанию возвращает значение null.
  • setLogLevel: установить уровень логгера

Эти методы были замечены в анализе записи запуска и инициализации журнала выше.Вышеуказанные методы являются либо абстрактными методами, либо пустыми реализациями в LoggingSystem, и все они требуют определенных подклассов для завершения обработки конкретной структуры журнала. Из диаграммы структуры наследования классов мы видим, что существует AbstractLoggingSystem, и все подклассы реализации журнала наследуются от этого класса, и этот класс также является абстрактным классом, который также является подклассом LoggingSystem. Итак, давайте посмотрим, как классы AbstractLoggingSystem и Log4J2LoggingSystem переписывают вышеуказанные методы, что также является основной логикой обработки структуры журнала в SpringBoot.

Логика обработки AbstractLoggingSystem

BeforeInitialize не имеет конкретной логики обработки в AbstractLoggingSystem, это пустой метод, поэтому в основном обратите внимание на метод инициализации.

@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    // 如果指定了日志配置文件,则通过此配置文件进行初始化
    if (StringUtils.hasLength(configLocation)) {
        initializeWithSpecificConfig(initializationContext, configLocation, logFile);
        return;
    }
    // 没有指定配置文件,则使用默认的方式查找配置文件并加载
    initializeWithConventions(initializationContext, logFile);
}
// 通过指定的配置文件初始化
private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation,
            LogFile logFile) {
    // 这里会处理日志配置文件中的占位符
    configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
    // 抽象方法,由具体子类实现(不同的日志框架处理配置文件的方式由其自身决定)
    loadConfiguration(initializationContext, configLocation, logFile);
}
// 通过默认方式查找配置文件并初始化
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
    // 查找配置文件,以 log4j2 为例,默认会在 classpath 下查找文件名为 
    // log4j2.properties、log4j2.yaml, log4j2.yml、log4j2.json,log4j2.jsn,log4j2.xml 的文件
    String config = getSelfInitializationConfig();
    if (config != null && logFile == null) {
        // 发生了自初始化,在属性发生变化时重新初始化
        reinitialize(initializationContext);
        return;
    }
    if (config == null) {
        // 查找 Spring 规则方式的配置,
        // log4j2-spring.properties、log4j2-spring.xml 等
        config = getSpringInitializationConfig();
    }
    if (config != null) {
        loadConfiguration(initializationContext, config, logFile);
        return;
    }
    // 抽象方法,由具体的日志系统实现
    loadDefaults(initializationContext, logFile);
}

Главное в инициализации найти конфигурационный файл, а затем инициализировать лог-систему через конфигурационный файл.Если его не удается найти, использовать метод по умолчанию, предоставляемый лог-системой для инициализации. В приведенном выше коде способы загрузки файлов конфигурации и загрузка реализованы в подклассах по умолчанию. Итак, давайте посмотрим, как это работает в случае с log4j2.

Логика обработки Log4J2LoggingSystem

Log4J2LoggingSystem не является прямым подклассом AbstractLoggingSystem, а является прямым подклассом Slf4JLoggingSystem.Абстрактный класс Slf4JLoggingSystem на самом деле используется для некоторой промежуточной обработки с точки зрения кода, и мы не будем его здесь анализировать.

Реализация beforeInitialize в Log4J2LoggingSystem

@Override
public void beforeInitialize() {
    // 创建、获取 LoggerContext 对象
    LoggerContext loggerContext = getLoggerContext();
    // 判断当前 LoggerContext 是否已经初始化过了,如果已经初始化过了则直接返回
    if (isAlreadyInitialized(loggerContext)) {
        return;
    }
    // 调用父类 Slf4JLoggingSystem 的 beforeInitialize 的方法,父类这个方法主要就是配置JDK Logging 的桥接处理器
    super.beforeInitialize();
    // 给 loggerContext 添加默认的 FILTER
    loggerContext.getConfiguration().addFilter(FILTER);
}

getLoggerContext — это процесс, с помощью которого log4j2 создает сам LoggerContext, поэтому сначала передайте его здесь.

Реализация инициализации в Log4J2LoggingSystem

@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    // 拿到当前 loggerContext
    LoggerContext loggerContext = getLoggerContext();
    // 判断下是否已经初始化过了
    if (isAlreadyInitialized(loggerContext)) {
        return;
    }
    // 移除默认的 FILTER
    loggerContext.getConfiguration().removeFilter(FILTER);
    // 调用父类 initialize,就是在找日志配置文件并且初始化
    super.initialize(initializationContext, configLocation, logFile);
    // 标记已经完成初始化
    markAsInitialized(loggerContext);
}

Основным методом инициализации здесь по-прежнему является логика обработки родительского класса.Как упоминалось ранее, ядром инициализации в AbstractLoggingSystem является процесс загрузки файлов конфигурации (loadConfiguration/loadDefaults), и этот процесс загрузки реализуется подклассами. Итак, давайте посмотрим на процесс загрузки конфигурационных файлов в log4j2.

  • loadConfiguration: когда есть файл конфигурации
protected void loadConfiguration(String location, LogFile logFile) {
    Assert.notNull(location, "Location must not be null");
    try {
        LoggerContext ctx = getLoggerContext();
        // 拿到资源url
        URL url = ResourceUtils.getURL(location);
        // 构建 ConfigurationSource 对象
        ConfigurationSource source = getConfigurationSource(url);
        // 这里会根据配置的类型选择不同的解析器来解析配置文件,比如
        // XmlConfigurationFactory、PropertiesConfigurationFactory...
        // 以指定的 configuration 启动
        ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
    }
}

Краткое резюме: создайте объект ресурса конфигурации ConfigurationSource с помощью указанного адреса файла конфигурации, затем выберите другую фабрику ConfigurationFactory для анализа файла конфигурации в соответствии с типом файла ресурса конфигурации, и, наконец, платформа ведения журнала инициализирует систему ведения журнала на основе этой конфигурации. файл.

  • loadDefaults: случай отсутствия файла конфигурации
@Override
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
    if (logFile != null) {
        // 使用 classpath:org/springframework/boot/logging/log4j2/log4j2-file.xml
        loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile);
    }
    else {
        // 使用 classpath:org/springframework/boot/logging/log4j2/log4j2.xml
        loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile);
    }
}

Краткое резюме: если файл конфигурации ведения журнала не указан или файл конфигурации, соответствующий указанной системе ведения журнала, не найден в пути к классам, для инициализации используется файл конфигурации по умолчанию, предоставленный SpringBoot.

Логика очистки системы журнала

Метод cleanUp также реализуется конкретной LoggingSystem, и его основная функция заключается в очистке ресурсов LoggingSystem.

@Override
public void cleanUp() {
    // 调用父类,移除桥接器
    super.cleanUp();
    LoggerContext loggerContext = getLoggerContext();
    // 标记loggerContext为未初始化状态,并将内部的 externalContext 置为 null
    markAsUninitialized(loggerContext);
    // 移除默认的 FILTER
    loggerContext.getConfiguration().removeFilter(FILTER);
}

некоторый анализ сцены

Это включает в себя некоторые распространенные сценарии использования журналов в повседневной работе по разработке, например, случай, когда в проекте нет конфигурации журнала, случай, когда файл конфигурации журнала настроен в каталоге ресурсов, и случай, когда файлы журнала, которые SpringBoot не могу распознать, были использованы.

нет файлов конфигурации

Конфигурации нет.Из предыдущего анализа видно, что при выполнении метода инициализации ресурсы не могут быть найдены, поэтому для загрузки будет использоваться метод loadDefaults по умолчанию, а метод loadDefaults LogbackLoggingSystem будет использоваться, поскольку logFile нулевой.classpath:org/springframework/boot/logging/log4j2/log4j2.xmlЭтот файл конфигурации:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <Property name="PID">????</Property>
        <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
        <Property name="LOG_LEVEL_PATTERN">%5p</Property>
        <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
        <Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
        <Property name="FILE_LOG_PATTERN">%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
    </Properties>
    <Appenders>
        // 打在控制台
        <Console name="Console" target="SYSTEM_OUT" follow="true">
            <PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
        <Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
        <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
        <logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
        <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
        <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
        <Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
        <logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
        <Root level="info">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

Значение в этом файле конфигурации имеет Appender, который является консолью по умолчанию, поэтому, если файл конфигурации журнала не настроен, журнал будет распечатан на консоли.

Настройте log4j2.xml в каталоге ресурсов.

Этот файл конфигурации может быть распознан SpringBoot, поэтому он будет использоваться для инициализации системы ведения журнала при инициализации журнала. Следующий файл конфигурации настраивает приложение для каждого уровня журнала, поэтому при его использовании журнал будет записываться в разные каталоги журналов в соответствии с уровнем журнала. (PS: обратитесь к предыдущему анализу файла конфигурации журнала, который можно идентифицировать)

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">

    <Properties>
        <Property name="logging.path">./logs</Property>
    </Properties>

    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!--只接受程序中 INFO 级别的日志进行处理 -->
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n" />
        </Console>
        <!--处理DEBUG级别的日志,并把该日志放到logs/debug.log文件中-->
        <!--打印出DEBUG级别日志,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileDebug" fileName="${logging.path}/debug.log"
                     filePattern="logs/?{date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="DEBUG"/>
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
        <RollingFile name="RollingFileInfo" fileName="${logging.path}/info.log"
                     filePattern="logs/?{date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--只接受INFO级别的日志,其余的全部拒绝处理-->
                <ThresholdFilter level="INFO"/>
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理WARN级别的日志,并把该日志放到logs/warn.log文件中-->
        <RollingFile name="RollingFileWarn" fileName="${logging.path}/warn.log"
                     filePattern="logs/?{date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理error级别的日志,并把该日志放到logs/error.log文件中-->
        <RollingFile name="RollingFileError" fileName="${logging.path}/error.log"
                     filePattern="logs/?{date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR"/>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </appenders>

    <loggers>
        <root level="DEBUG">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
            <appender-ref ref="RollingFileDebug"/>
        </root>

        <!--log4j2 自带过滤日志-->
        <Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
        <Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
        <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
        <logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
        <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
        <Logger name="org.crsh.plugin" level="warn" />
        <logger name="org.crsh.ssh" level="warn"/>
        <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
        <Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
        <logger name="org.thymeleaf" level="warn"/>
        <Logger name="org.springframework" level="warn"/>
    </loggers>
</configuration>

Настройте log4j2-glmapper.xml в ресурсах.

Переименуйте приведенный выше файл конфигурации в log4j2-glmapper.xml , поскольку это правило именования не распознается SpringBoot по умолчанию, поэтому оно совпадает со сценарием 1 при загрузке файла конфигурации журнала. Если вы хотите, чтобы этот файл конфигурации распознавался, вы можете указать его с помощью logging.config.

logging.config=classpath:log4j2-glmapper.xml

резюме

В этой статье систематически представлены и проанализированы журналы в SpringBoot.Статья в основном предназначена для понимания обработки системы журналов в SpringBoot, поэтому она не будет уделять слишком много внимания некоторой логике обработки самой системы журналов.Заинтересованные читатели могут сделать собственные исследования или свяжитесь с автором.Общайтесь вместе.

мой публичный аккаунт

微信公众号
Публичный аккаунт WeChat