В предыдущей статье я поверхностно описал о том, как и для чего можно использовать глобальные trace ID, внедрённые с помощью механизма, предоставленного библиотекой Spring Cloud Sleuth.
Сегодня хочу рассмотреть, как можно использовать trace ID с помощью Mapped Diagnostic Context (MDC), поддерживаемого Logback и Log4J. Для разнообразия, добавим в логирование также IP клиента, направившего REST-запрос.
1. Подключаем в pom.xml зависимости:
2. Добавляем файл конфигурации logback-spring.xml:
3. Делаем класс конфигурации, который создаст FilterRegistrationBean для сервлета, и задаём имена HTTP заголовков и ключей MDC:
4. Делаем фильтр. В нем извлекаем из REST-запроса HTTP заголовки "X-Token" (наш trace ID) и "X-Forvarded-For" (IP). Если заголовок "X-Token" отсутствует, то будет создан новый trace ID (генерируем рандомный UUID):
5. Создаём "Hello!"-контроллер:
6. Запускаем приложение и направляем ему два запроса:
1) Без заголовка "X-Token".
В ответ получаем "Hello!" и видим в логах запись с IP и сгенерированным токеном:
2) С заголовком "X-Token".
В ответ получаем "Hello!" и видим в логах запись с IP и переданным в заголовке токеном:
Код на GitHub
Сегодня хочу рассмотреть, как можно использовать trace ID с помощью Mapped Diagnostic Context (MDC), поддерживаемого Logback и Log4J. Для разнообразия, добавим в логирование также IP клиента, направившего REST-запрос.
1. Подключаем в pom.xml зависимости:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
2. Добавляем файл конфигурации logback-spring.xml:
<configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <property name="LOG_FILE" value="log/demo.mdc"/> <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%-5level [%X{Slf4jMDCFilter.ID}] [%X{Slf4jMDCFilter.ClientIP}]) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{Slf4jMDCFilter.ID} %X[Slf4jMDCFilter.ClientIP]] ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" /> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_FILE}.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.log</FileNamePattern> <MaxHistory>7</MaxHistory> </rollingPolicy> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> </appender> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="FILE"/> <appender-ref ref="CONSOLE"/> </root> </configuration>
3. Делаем класс конфигурации, который создаст FilterRegistrationBean для сервлета, и задаём имена HTTP заголовков и ключей MDC:
@Data @Configuration @ConfigurationProperties(prefix = slf4jfilter") public class Slf4jMDCFilterConfiguration { public static final String DEFAULT_RESPONSE_TOKEN_HEADER = "X-Token"; public static final String DEFAULT_REQUEST_TOKEN_HEADER = "X-Token"; public static final String DEFAULT_MDC_ID_TOKEN_KEY = "Slf4jMDCFilter.ID"; public static final String DEFAULT_MDC_CLIENT_IP_KEY = "Slf4jMDCFilter.ClientIP"; private String responseHeader = DEFAULT_RESPONSE_TOKEN_HEADER; private String mdcTokenKey = DEFAULT_MDC_ID_TOKEN_KEY; private String mdcClientIpKey = DEFAULT_MDC_CLIENT_IP_KEY; private String requestHeader = DEFAULT_REQUEST_TOKEN_HEADER; @Bean public FilterRegistrationBean servletRegistrationBean() { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(); final Slf4jMDCFilter slf4jMDCFilter = new Slf4jMDCFilter(responseHeader, mdcTokenKey, mdcClientIpKey, requestHeader); registrationBean.setFilter(slf4jMDCFilter); registrationBean.setOrder(2); return registrationBean; } }
4. Делаем фильтр. В нем извлекаем из REST-запроса HTTP заголовки "X-Token" (наш trace ID) и "X-Forvarded-For" (IP). Если заголовок "X-Token" отсутствует, то будет создан новый trace ID (генерируем рандомный UUID):
@Data @EqualsAndHashCode(callSuper = false) @EnableConfigurationProperties(Slf4jMDCFilterConfiguration.class) @Component public class Slf4jMDCFilter extends OncePerRequestFilter { private final String responseHeader; private final String mdcTokenKey; private final String mdcClientIpKey; private final String requestHeader; public Slf4jMDCFilter() { responseHeader = Slf4jMDCFilterConfiguration.DEFAULT_RESPONSE_TOKEN_HEADER; mdcTokenKey = Slf4jMDCFilterConfiguration.DEFAULT_MDC_ID_TOKEN_KEY; mdcClientIpKey = Slf4jMDCFilterConfiguration.DEFAULT_MDC_CLIENT_IP_KEY; requestHeader = Slf4jMDCFilterConfiguration.DEFAULT_REQUEST_TOKEN_HEADER; } public Slf4jMDCFilter(final String responseHeader, final String mdcTokenKey, final String mdcClientIPKey, final String requestHeader) { this.responseHeader = responseHeader; this.mdcTokenKey = mdcTokenKey; this.mdcClientIpKey = mdcClientIPKey; this.requestHeader = requestHeader; } @Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws java.io.IOException, ServletException { try { final String token = extractToken(request); final String clientIP = extractClientIP(request); MDC.put(mdcClientIpKey, clientIP); MDC.put(mdcTokenKey, token); if (!StringUtils.isEmpty(responseHeader)) { response.addHeader(responseHeader, token); } chain.doFilter(request, response); } finally { MDC.remove(mdcTokenKey); MDC.remove(mdcClientIpKey); } } private String extractToken(final HttpServletRequest request) { final String token; if (!StringUtils.isEmpty(requestHeader) && !StringUtils.isEmpty(request.getHeader(requestHeader))) { token = request.getHeader(requestHeader); } else { token = UUID.randomUUID().toString().toUpperCase().replace("-", ""); } return token; } private String extractClientIP(final HttpServletRequest request) { final String clientIP; if (request.getHeader("X-Forwarded-For") != null) { clientIP = request.getHeader("X-Forwarded-For").split(",")[0]; } else { clientIP = request.getRemoteAddr(); } return clientIP; } @Override protected boolean isAsyncDispatch(final HttpServletRequest request) { return false; } @Override protected boolean shouldNotFilterErrorDispatch() { return false; } }
5. Создаём "Hello!"-контроллер:
@Slf4j @RestController public class MyController { @GetMapping("/hello") public String getHello() { log.info("Called getHello"); return "Hello!"; } }
6. Запускаем приложение и направляем ему два запроса:
1) Без заголовка "X-Token".
curl http://localhost:8080/hello
В ответ получаем "Hello!" и видим в логах запись с IP и сгенерированным токеном:
2019-01-19 16:13:52.965 INFO [2B239745F1A4496CB5BF148FE9E1EDB9] [127.0.0.1] 31702 --- [nio-8080-exec-1] r.e.demo.mdc.controller.MyController : Called getHello
2) С заголовком "X-Token".
curl -H "X-Token: 228dc00d216b" http://localhost:8080/hello
В ответ получаем "Hello!" и видим в логах запись с IP и переданным в заголовке токеном:
2019-01-19 16:16:33.889 INFO [228dc00d216b] [127.0.0.1] 31702 --- [nio-8080-exec-3] r.e.demo.mdc.controller.MyController : Called getHello
Код на GitHub
Комментариев нет:
Отправить комментарий