Журналирование JVM-приложений

Журналирование JVM-приложений #

Васильев Андрей Михайлович, 2024

Версии презентации


Отслеживание работы приложения #

Серверное приложение обычно запускается на выделенном компьютере и функционирует постоянно (или согласно графику обслуживания пользователей)

Во время работы приложения могут возникнуть разные ситуации:

  • Падение приложения
  • Увеличение времени обработки запросов от пользователя
  • Окончание свободной оперативной памяти
  • Исчерпание свободного пространства на файловой системы
  • Невозможность выполнить запрос к СУБД
  • Невозможность выполнить HTTP-запрос к нижестоящей службе

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


Внешние и внутренние проблемы #

Выполним классификацию возможных причин критических ситуаций:

  • Внутренние причины, обусловленные кодом приложения или используемых библиотек
  • Внешние причины, обусловленные состоянием окружения, в котором запущено приложение
    • Доступность ресурсов сервера
    • Доступность внешних систем

Системы мониторинга #

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

  • Объём используемой оперативной памяти
  • Объём используемого пространства на файловой системе
  • Объём утилизации процессорного времени
  • Количество запущенных активных процессов

Данные собираются в единую систему, которая:

  • Позволяет визуализировать текущую нагрузку на систему
  • Позволяет оповещать эксплуатанта в случае наступления или приближения критического состояния системы

Архитектура систем мониторинга #

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

diagram


Отслеживание состояния приложения #

Некоторые системы мониторинга позволяют отправлять метрики не только от собственных агентов, но также и от внешних приложений, например веб-приложений

Каждое приложение обладает важными характеристиками относительно задачи, которую оно решает, именно их стоит закладывать в метрики, а не обобщённые данные

Общие метрики веб-сервера, запущенного на JVM:

  • Состояние виртуальной машины JVM (объём выделенной оперативной памяти, объём кеша, количество потоков, состояние сборщика мусора, открытые файлы)
  • Информация об обработке запроса пользователя:
    • Данные о клиенте, выполнившем запрос
    • Адрес и параметры запроса от клиента
    • Статус ответа
    • Время обработки запроса
  • Информация о времени выполнении операции над базой данных: время выполнения операции, объём переданных данных и т.д.

Журналирование работы приложения #

При отсутствии системы монитроринга можно воспользоваться локальной альтернативой — журналированием работы приложения в файлы-журналы

Журналы в данном случае являются файлами, расположенными на файловой системе

diagram

  • Файлы с журналами открываются в момент запуска приложения
  • Журналы пишутся всё время работы приложения

Необходимо выполнять ротацию журналов — удаление излишних записей из журнала, когда всё хранилище заполнено или старые данные уже не актуальны


Журналирование в 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 #

diagram

  • Конкретные компоненты приложения зависят от общего фасада slf4j
  • Внутри конкретного приложения регистрируются и настраиваются плагины для вывода журналов на нужные данному приложению пути сохранения данных

Данная библиотека является достаточно гибкой и она используется при разработке Android-приложений, для неё существуют обёртки на языке Kotlin


Формирование сообщений в SLF4J 2 #

Подключение SLF4J к приложению #

В файле build.gradle.kts или build.properties.json необходимо добавить зависимость от API

"org.slf4j:slf4j-api"

Использование библиотеки #

  1. В конкретном классе необходимо получить доступ к объекту для записи журнала
  2. В нужных местах класса добавить вызов методов для записи в журнал
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 пытается выполнить конфигурацию автоматически:

  1. Путём загрузки класса реализующего интерфейс Configurator
  2. Путём загрузки файла logback-test.scmo из ресурсов приложения
  3. Путём загрузки файла 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>

© A. M. Васильев, 2024, CC BY-SA 4.0, andrey@crafted.su