Надлежащая практика ведения логов — один из наиболее важных аспектов разработки программного обеспечения. Она помогает быстро понять контекст и локализовать проблему при отладке.
Самой большой сложностью может стать отсутствие у команды единой системы логирования. Если каждый придерживается того стиля ведения логов, который считает правильным, то человеку, занимающемуся дебаггингом, будет сложно каждый раз анализировать логи по-новому.
С другой стороны, если бы, создавая программу, можно было добавить определенный фреймворк логирования, который отлавливал бы наиболее важные для отладки кода аспекты, то для ошибок, связанных с “человеческим фактором”, осталось бы меньше места.
В этой статье мы попытаемся найти решения для трех следующих задач.
- Отслеживание каждого вызова API по уникальному ID. Это поможет отследить все шаги в едином потоке кода.
- Отслеживание любых асинхронных вызовов по уникальному ID вызывающей стороны. Это оказывается затруднительным без передачи уникального ID в качестве параметра метода и логирования его в явном виде. Такое может привести к появлению ручных ошибок.
- Логирование 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.
Читайте также:
- Внешнее конфигурирование базы данных Spring Boot с помощью AWS Secrets Manager
- Spring Data — сила доменных событий
- Типы операций обновления в MongoDB с использованием Spring Boot
Читайте нас в Telegram, VK и Дзен
Перевод статьи Ruby Valappil: How to Build an Effective Logging System Using Aspect and Spring Cloud Sleuth