分布式中traceId链接服务间的日志
使用技术:
网关:SpringCloudGateway
RPC调用:Feign
一:在网关入口处设置header:key-traceId,value-UUID
import com.kw.framework.common.croe.constant.CommonConstant;
import com.kw.framework.gateway.utils.BuildHeaderFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.LinkedHashMap;
import java.util.UUID;@Component
@Slf4j
public class HeaderFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 封装需要向后续封装的header对象LinkedHashMap<String, String> headerMap = new LinkedHashMap<>();headerMap.put(CommonConstant.TRACE_ID, UUID.randomUUID().toString());exchange = BuildHeaderFilter.chainFilterAndSetHeaders(chain, exchange, headerMap);return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}}
二:新建FeignRequestInterceptor实现RequestInterceptor来实现header透传
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;/*** feign请求头传递*/
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {HttpServletRequest httpServletRequest = getHttpServletRequest();if (httpServletRequest != null) {Map<String, String> headers = getHeaders(httpServletRequest);// 传递所有请求头,防止部分丢失Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();// 防止添加重复if(!template.headers().containsKey(entry.getKey())){template.header(entry.getKey(), entry.getValue());}}if (log.isDebugEnabled()) {log.debug("FeignRequestInterceptor:{}", template.toString());}}}private HttpServletRequest getHttpServletRequest() {try {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();} catch (Exception e) {return null;}}private Map<String, String> getHeaders(HttpServletRequest request) {Map<String, String> map = new LinkedHashMap<>();Enumeration<String> enumeration = request.getHeaderNames();if(enumeration!=null){while (enumeration.hasMoreElements()) {String key = enumeration.nextElement();String value = request.getHeader(key);map.put(key, value);}}return map;}}
三:构建服务请求拦截器TraceIdFilter,实现MDC(用于日志打印)赋值,并在返回response的header中返回traceId
import cn.hutool.core.util.StrUtil;
import com.kw.framework.common.croe.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;@Slf4j
@WebFilter(filterName = "traceIdFilter", urlPatterns = "/*")
@Order(0)
public class TraceIdFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest ;String traceId = request.getHeader(CommonConstant.TRACE_ID);MDC.put(CommonConstant.TRACE_ID, StrUtil.isEmpty(traceId)? UUID.randomUUID().toString() :traceId);HttpServletResponse response = (HttpServletResponse) servletResponse;response.setHeader(CommonConstant.TRACE_ID,traceId);filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {MDC.clear();}}
四:在logback.xml中设置日志输出格式:
<property name="log.pattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] - [%X{TRACE_ID}] - [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
完整输出日志配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration><springProperty scope="context" name="log.home" source="spring.application.name"/><!-- 日志存放路径 --><property name="log.path" value="opt/logs/" /><!-- 日志输出格式 --><property name="log.pattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] - [%X{TRACE_ID}] - [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${log.pattern}</pattern></encoder></appender><!-- 系统日志输出 --><appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/${log.home}/sys-info.log</file><!-- 循环政策:基于时间创建日志文件 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 日志文件名格式 --><fileNamePattern>${log.path}/${log.home}/sys-info.%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- 日志最大的历史 30天 --><MaxHistory>30</MaxHistory><maxFileSize>1GB</maxFileSize><totalSizeCap>15GB</totalSizeCap></rollingPolicy><encoder><pattern>${log.pattern}</pattern><charset>UTF-8</charset></encoder></appender><appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/${log.home}/sys-error.log</file><!-- 循环政策:基于时间创建日志文件 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 日志文件名格式 --><fileNamePattern>${log.path}/${log.home}/sys-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- 日志最大的历史 30天 --><MaxHistory>30</MaxHistory><maxFileSize>50MB</maxFileSize></rollingPolicy><encoder><pattern>${log.pattern}</pattern><charset>UTF-8</charset></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 过滤的级别 --><level>ERROR</level><!-- 匹配时的操作:接收(记录) --><onMatch>ACCEPT</onMatch><!-- 不匹配时的操作:拒绝(不记录) --><onMismatch>DENY</onMismatch></filter></appender><!--配置logstash 发送日志数据的地址 --><!-- <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender"><destination>192.168.56.30:5000</destination><encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" /></appender>--><!--springboot的日志 --><include resource="org/springframework/boot/logging/logback/base.xml" /><!--系统操作日志--><root level="info"><appender-ref ref="file_info" /><appender-ref ref="file_error" /><appender-ref ref="console" /><!-- <appender-ref ref="LOGSTASH" />--></root></configuration>
输出结果:
日志输出:
请求返回:
至此,我们就可以在发现问题时在浏览器上拿到对应的traceId,快速定位到日志位置