личный блог:Glmapepr - Отступление от процедурного мира
Колонка самородков:glmapperСпасибо за внимание, лайки и комментарии.В конце статьи есть QR-код вашего личного публичного номера.Заинтересованные студенты могут подписаться на него или добавить мой личный WeChat для общения и совместного обучения.
Рекомендуемое чтение
- Анализ технологии SpringBoot Series-FatJar
- SpringBoot Series — Анализ процесса запуска
- SpringBoot Series — Анализ механизма событий
- Серия SpringBoot — жизненный цикл и расширение компонентов Bean
- SpringBoot Series — Анализ структуры логов
- SpringBoot Series — анализ доступа к ресурсам
- SpringBoot Series — Анализ встроенного веб-контейнера
содержание
формат журналаКонсольный выводВыход с цветовой кодировкойвыходной файлуровень журналаГруппы журналовПользовательская конфигурация журналаконфигурация 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, поэтому она не будет уделять слишком много внимания некоторой логике обработки самой системы журналов.Заинтересованные читатели могут сделать собственные исследования или свяжитесь с автором.Общайтесь вместе.