使用技术:
网关: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 {
@Override
public 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);
}
@Override
public 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 {
@Override
public 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 {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public 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);
}
@Override
public 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,快速定位到日志位置