Журналирование JVM-приложений #
Васильев Андрей Михайлович, 2024
Версии презентации
Отслеживание работы приложения #
Серверное приложение обычно запускается на выделенном компьютере и функционирует постоянно (или согласно графику обслуживания пользователей)
Во время работы приложения могут возникнуть разные ситуации:
- Падение приложения
- Увеличение времени обработки запросов от пользователя
- Окончание свободной оперативной памяти
- Исчерпание свободного пространства на файловой системы
- Невозможность выполнить запрос к СУБД
- Невозможность выполнить HTTP-запрос к нижестоящей службе
Обычно при продуктовом запуске у разработчиков нет возможности физически подключиться к серверу, чтобы изучить все детали некорректного поведения приложения
Внешние и внутренние проблемы #
Выполним классификацию возможных причин критических ситуаций:
- Внутренние причины, обусловленные кодом приложения или используемых библиотек
- Внешние причины, обусловленные состоянием окружения, в котором запущено
приложение
- Доступность ресурсов сервера
- Доступность внешних систем
Системы мониторинга #
Задача отслеживания состояния серверов в целом решена: создан ряд продуктов, которые позволяют собрать информацию о состоянии вычислительной системы:
- Объём используемой оперативной памяти
- Объём используемого пространства на файловой системе
- Объём утилизации процессорного времени
- Количество запущенных активных процессов
Данные собираются в единую систему, которая:
- Позволяет визуализировать текущую нагрузку на систему
- Позволяет оповещать эксплуатанта в случае наступления или приближения критического состояния системы
Архитектура систем мониторинга #
На каждом компьютере, за которым необходимо следить, запускается агент системы мониторинга, который собирает информацию и отправляет на центральный узел
Отслеживание состояния приложения #
Некоторые системы мониторинга позволяют отправлять метрики не только от собственных агентов, но также и от внешних приложений, например веб-приложений
Каждое приложение обладает важными характеристиками относительно задачи, которую оно решает, именно их стоит закладывать в метрики, а не обобщённые данные
Общие метрики веб-сервера, запущенного на JVM:
- Состояние виртуальной машины JVM (объём выделенной оперативной памяти, объём кеша, количество потоков, состояние сборщика мусора, открытые файлы)
- Информация об обработке запроса пользователя:
- Данные о клиенте, выполнившем запрос
- Адрес и параметры запроса от клиента
- Статус ответа
- Время обработки запроса
- Информация о времени выполнении операции над базой данных: время выполнения операции, объём переданных данных и т.д.
Журналирование работы приложения #
При отсутствии системы монитроринга можно воспользоваться локальной альтернативой — журналированием работы приложения в файлы-журналы
Журналы в данном случае являются файлами, расположенными на файловой системе
- Файлы с журналами открываются в момент запуска приложения
- Журналы пишутся всё время работы приложения
Необходимо выполнять ротацию журналов — удаление излишних записей из журнала, когда всё хранилище заполнено или старые данные уже не актуальны
Журналирование в JVM #
Ввиду длинной жизни платформы JVM было предложено много решений для выполнения журналирования внутри приложения:
- Simple Logging Facade for Java 1.x
- Simple Logging Facade for Java 2.x
- Apache Commons Logging
- Logback
- Java Util Logging
Каждый разработчик библиотеки может выбирать любое решение для записи своих журналов
- Разработчики http4k решили не внедрять систему локального журналирования
- В библиотеку встроена поддержка трассировки запросов в формате Zipkin
- Каждая большая библиотека предоставляет средства интеграции (адаптеры) для решений, использующие другие решения
Simple Logging Facade for Java 2 #
- Конкретные компоненты приложения зависят от общего фасада slf4j
- Внутри конкретного приложения регистрируются и настраиваются плагины для вывода журналов на нужные данному приложению пути сохранения данных
Данная библиотека является достаточно гибкой и она используется при разработке Android-приложений, для неё существуют обёртки на языке Kotlin
Формирование сообщений в SLF4J 2 #
Подключение SLF4J к приложению #
В файле build.gradle.kts
или build.properties.json
необходимо добавить
зависимость от API
"org.slf4j:slf4j-api"
Использование библиотеки #
- В конкретном классе необходимо получить доступ к объекту для записи журнала
- В нужных местах класса добавить вызов методов для записи в журнал
class SomeHandler() : HttpHandler {
private val logger = LoggerFactory.getLogger(SomeHandler::class.java)
override fun invoke(request: Request): Response {
logger.atInfo().log("Обрабатываем очень важный запрос")
}
}
Журналирование обработки запросов #
Журналирование общей информации по запросам можно доверить фильтру на уровне всего приложения, т.к. он имеет всю необходимую информацию
val logger = LoggerFactory.getLogger("ru.yarsu.WebApplication")
val loggingFilter = Filter { next: HttpHandler ->
{ request: Request ->
// Собрать данные о запросе
// Вычислить время обработки запроса
val result = next(request)
// Собрать данные об ответе
// Выполнить журналирование информации
logger.atInfo().setMessage("Request")
.addKeyValue("URI", request.uri).log()
result
}
}
- Для получения объекта для записи в журнал используем строковый идентификатор
- Для добавления информации в структурированном виде используем вызов addKeyValue с передачей пары ключ-значение
Выбор уровня журналирования #
Библиотеки журналирования предоставляют несколько уровней важности сообщений: trace, debug, info, warn, error
При записи данных в журнал можно указать желаемый уровень сообщений
Сообщение\Журналирование | TRACE | DEBUG | INFO | WARN | ERROR | OFF |
---|---|---|---|---|---|---|
TRACE | + | - | - | - | - | - |
DEBUG | + | + | - | - | - | - |
INFO | + | + | + | - | - | - |
WARN | + | + | + | + | - | - |
ERROR | + | + | + | + | + | - |
- При работе приложения обычным уровнем журналирования является
INFO
- В случае возникновения ошибки пользователь может расширить уровень журналирования, чтобы передать расширенный журнал разработчику для исправления проблемы
Запись журналов в файл с помощью Logback #
Необходимо добавить зависимость от logback-classic в build.gradle.kts
или
build.properties.json
:
"ch.qos.logback:logback-classic"
В момент запуска приложения Logback пытается выполнить конфигурацию автоматически:
- Путём загрузки класса реализующего интерфейс Configurator
- Путём загрузки файла
logback-test.scmo
из ресурсов приложения - Путём загрузки файла
logback.xml
из ресурсов приложения
SCMO-файлы являются бинарным представлением конфигурации, оптимизированы для ускорения запуска
Пример конфигурационного файла logback.xml #
- Сохраняем журнал в каталог
logs
, в файлapp.log
- Каждый отдельный файл не более 10 мегабайт
- Весь архив не более 100 мегабайт
- Данные кодируются в JSON для обеспечения машинной обработки
- Уровень журналирования информации —
debug
<configuration>
<property name="HOME_LOG" value="logs/app.log"/>
<appender name="FILE-ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${HOME_LOG}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<!-- Максимальный размер архива журнала 10 МБ -->
<maxFileSize>10MB</maxFileSize>
<!-- Максимальный общий объём архива 100 МБ -->
<totalSizeCap>100MB</totalSizeCap>
<!-- Хранить не более 60 дней -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
</appender>
<root level="debug">
<appender-ref ref="FILE-ROLLING"/>
</root>
</configuration>