Надлежащая практика ведения логов  —  один из наиболее важных аспектов разработки программного обеспечения. Она помогает быстро понять контекст и локализовать проблему при отладке.

Самой большой сложностью может стать отсутствие у команды единой системы логирования. Если каждый придерживается того стиля ведения логов, который считает правильным, то человеку, занимающемуся дебаггингом, будет сложно каждый раз анализировать логи по-новому.

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

В этой статье мы попытаемся найти решения для трех следующих задач.

  1. Отслеживание каждого вызова API по уникальному ID. Это поможет отследить все шаги в едином потоке кода.
  2. Отслеживание любых асинхронных вызовов по уникальному ID вызывающей стороны. Это оказывается затруднительным без передачи уникального ID в качестве параметра метода и логирования его в явном виде. Такое может привести к появлению ручных ошибок.
  3. Логирование API-запросов. Оно необходимо, чтобы определить путь исследуемого API и верифицировать каждый вызов с использованием тела и параметров запроса.

Что такое Spring Cloud Sleuth?

Spring Cloud Sleuth позволяет выполнять распределенную трассировку. Он предоставляет два ID для каждого из логов.

ID трассировки  —  уникальный ID, который отслеживает один запрос. Например, вызову GET API будет присвоен один ID трассировки, который можно задействовать для прослеживания маршрута этого API-вызова от запроса к ответу.

ID диапазона же будет присваиваться каждому уникальному шагу в трассировке при условии, что в одном запросе выполняется n уникальных задач. Об этом на примере  —  далее в статье.

Чтобы подключить Sleuth в проект Spring Boot, нужно добавить в pom-файл следующую зависимость:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

Aspect и Pointcut

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

Аспектно-ориентированное программирование используется для решения общих сквозных проблем. В нашем случае это  —  ведение логов.

Начнем с разработки класса Aspect, который определял бы Pointcut (совокупность точек соединения). Pointcut объявляет условие, соответствующее точке соединения. Точка соединения  —  это определенный шаг выполнения. В нашем случае Pointcut объявляет соответствующую точку как все методы в классе SampleResource.

Мы также объявляем Advice  —  @Before, который применяется перед выполнением метода, объявленного в Pointcut.

Чтобы включить аспектно-ориентированное программирование в SpringBoot, нужно добавить аннотацию к классу Main  —  @EnableAspectJAutoProxy.

В приложении мы логируем метод запроса, путь и параметры пути.

logger.info("Received call :: {} - {} - request param [{}]",request.getMethod(),request.getServletPath(),request.getParameter("name"));

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

Воспользуемся нижеприведенным Curl для тестирования приложения: 

curl --location --request GET 'http://localhost:8090/api/greetings?name=James%20Bond'

Чтобы убедиться, что каждому запросу присваивается новый ID, мы дважды обратимся к упомянутому API.

Обратите внимание: мы также добавили в приложении асинхронный вызов, который сейчас просто печатает сообщение. Cледует ожидать (в идеале), что этому асинхронному вызову также будет присвоен уникальный ID диапазона.

public String sampleAsyncCall(String name) {
CompletableFuture.runAsync(() -> {
logger.info("Inside Async Call");
}, new TraceableExecutorService(beanFactory,executor));
return "Hello " + name;
}

Также обратите внимание, что для включения трассировки следует выполнять асинхронный вызов TraceableExecutorService. Если вместо этого использовать ScheduledExecutorService, ID диапазона и ID трассировки вернутся как null.

Проверим сгенерированные после двойного запроса к GET API логи.

При первом обращении к API запросу присваивается уникальный ID трассировки и ID диапазона. Когда из метода выполняется асинхронный вызов, он также получает уникальный ID диапазона.

При обращении к API во второй раз запросу присваивается новый ID трассировки и ID диапазона, а асинхронному вызову снова присваивается следующий ID диапазона.

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

С помощью Spring Sleuth мы избежали задачи по ручному присвоению уникальных ID каждому запросу, а затем асинхронным вызовам.

Заключение

Мы рассмотрели, как эффективно пользоваться Aspect и Spring Cloud Sleuth для создания эффективной системы ведения логов, которая почти исключает риск человеческих ошибок.

Весь код из статьи доступен на Github.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Ruby Valappil: How to Build an Effective Logging System Using Aspect and Spring Cloud Sleuth

Предыдущая статьяPHP: создание и публикация пакета composer
Следующая статьяУтилиты, которые повышают эффективность приложения React на Typescript