SpringCloud使用traceId跟踪日志解决方案

查看日志场景

  1. 接口通过网关,访问服务1
  2. 接口通过网关,访问服务1,服务1访问服务2
  3. 定时任务,访问服务1

实现逻辑过程

  1. HTTP接口请求经过网关时,利用过滤器,将生成的traceId加到到RequestHeader中
  2. 通过网关请求到服务中,利用MVC拦截器取出Header中的traceId,并且将traceId值使用Log中MDC类写入到日志中。
  3. 服务1,通过Feign请求其他服务之前,取出MDC类中的traceId赋值到RequestHeader中,被请求服务使用2中的方式取出traceId并记录到日志中。
  4. 服务器安装filebeat(或其他日志收集软件)收集日志,发送给ElasticSearch。
  5. 通过Kiabana查看日志内容。通过网关或其他方式定位到存在问题的请求中的traceId,通过traceId查看请求内的所有日志

相关代码

Zuul网关部分代码

TracePreFilter.java 过滤器

生成traceId,将traceId加入到RequestHeader中,带入到下游请求中。

@Component
public class TracePreFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return -1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        String traceIdVal = LogHelper.getTraceId();
        MDC.put(LogCollectionConstants.traceId, traceIdVal);
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader(LogCollectionConstants.traceId, traceIdVal);
        return null;
    }
}

LogFilter.java 过滤器

记录请求的详细信息,请求参数,返回值,时长等信息

@Component
@Slf4j
public class LogFilter extends ZuulFilter {

    public static final String START_TIME_KEY = "start_time";

    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        try {
            HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
            RequestContext.getCurrentContext().set(START_TIME_KEY, System.currentTimeMillis());
            HttpEntity httpEntity = new HttpEntity();

            httpEntity.setMethod(request.getMethod());
            httpEntity.setUrl(request.getRequestURI());
            httpEntity.setIp(request.getRemoteAddr());
            HashMap<String, Object> parameter = showParams(request);
            httpEntity.setParameter(JSON.toJSONString(parameter));
            httpEntity.setUserAgent(request.getHeader("user-agent"));
            String body = "";
            InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();
            byte[] bytes = StreamUtils.copyToByteArray(stream);
            body = new String(bytes, StandardCharsets.UTF_8);
            httpEntity.setResult(body);
            long startTime = (long) RequestContext.getCurrentContext().get(START_TIME_KEY);
            httpEntity.setLaunchTime(new Date(startTime));
            httpEntity.setDuration(System.currentTimeMillis() - startTime);
            httpEntity.setTraceId(RequestContext.getCurrentContext().getZuulRequestHeaders().get(LogCollectionConstants.traceId));
            log.info("接口统计 {}",JSON.toJSONString(httpEntity));
            RequestContext.getCurrentContext().setResponseBody(body);
        } catch (Exception e) {
            log.error("日志统计失败", e);
            return true;
        }
        return true;
    }

    public static HashMap<String, Object> showParams(HttpServletRequest request) {
        HashMap<String, Object> map = new HashMap<>();
        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            if (paramValues.length > 0) {
                String paramValue = paramValues[0];
                if (paramValue.length() != 0) {
                    map.put(paramName, paramValue);
                }
            }
        }
        return map;
    }
}

服务代码

WebMvcConfig.java 过滤器

注册过滤器 ,将上游请求中traceId值取出。使用MDC类将内容记录到日志中

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}
public class TraceInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceIdVal = request.getHeader(LogCollectionConstants.traceId);
        if (StringUtils.isNotEmpty(traceIdVal)) {
            MDC.put(LogCollectionConstants.traceId, traceIdVal);
        }
        else {
            MDC.remove(LogCollectionConstants.traceId);
        }
        return true;
    }
}

HystrixConfig.java 熔断配置

@Configuration
public class HystrixConfig extends LogHystrixConfig {
    public HystrixConfig(){
        super();
    }
}
public class LogHystrixConfig {
    public static final Logger log = LoggerFactory.getLogger(LogHystrixConfig.class);

    public LogHystrixConfig(){
        try {
            HystrixConcurrencyStrategy target = new MdcHystrixConcurrencyStrategy();
            HystrixConcurrencyStrategy strategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (strategy instanceof MdcHystrixConcurrencyStrategy) {
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins
                    .getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
                    .getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
                    .getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
                    .getPropertiesStrategy();

            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
            HystrixPlugins.getInstance()
                    .registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        }
        catch (Exception e) {
            log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
        }
    }
}

FeignInterceptorConfig.java Feign配置

@Configuration
public class FeignInterceptorConfig extends LogFeignInterceptorConfig implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(LogCollectionConstants.traceId, super.getTraceId());
    }
}
public class LogFeignInterceptorConfig {

    public String getTraceId() {
        return MDC.get(LogCollectionConstants.traceId);
    }
}

定时任务AOP

定时任务在开始执行之前,给MDC类 赋值traceId

@Aspect
@Component
@Slf4j
public class ScheduledAspect {

    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void proxyAspect() {
    }

    @Before("proxyAspect()")
    public void before(JoinPoint joinPoint) throws Throwable {
        String traceId= LogHelper.getTraceId();
        MDC.put(LogCollectionConstants.traceId, traceId);
    }
}

logback.xml 日志配置文件

使用{traceId}写入MDC中的traceId的值

    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }) [%X{traceId}] %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="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- }  [%X{traceId}] --- [%t] : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

测试效果图

1、接口通过网关,访问服务1

在Service1中写入测试接口

@RestController
@Slf4j
public class UserController {

    @GetMapping("/user/getlist")
    public List<String> getlist() {
        log.info("测试接口user");
        List<String> list = asList("user1", "user2", "user3");
        log.warn("测测测");
        return list;
    }

}

通过网关请求接口效果如下

网关中打印日志 通过日志找到traceId为40417dd85d224eda8d67925dce335b6b


Service1中的日志

2、接口通过网关,访问服务1,服务1访问服务2

测试代码
Service1中

@RestController
@Slf4j
public class UserController {
    @Autowired
    private DemoApiClient demoApiClient;

    @GetMapping("/user/getlist2")
    public List<String> getlist2() {
        log.info("测试接口getlist2");
        List<String> result = demoApiClient.getlist2();
        log.info("请求Service2中接口成功,返回数据");
        return result;
    }
}

Service2中


@RestController
@Slf4j
public class DemoController {
    @GetMapping("/demo/getlist2")
    public List<String> getlist2() {
        log.info("通过了其他的服务请求过来");
        List<String> list = asList("haha", "hehe", "youyou");
        log.info("请求完了");
        return list;
    }
}

通过网关查到traceId为f87e568753004b7e830e76497936fb08


Service1中打印日志


Service2中打印日志

3、定时任务,访问服务1

在Service1中加入测试代码

@Component
@Slf4j
public class TestJob {

    @Autowired
    private DemoApiClient demoApiClient;

    @Scheduled(fixedRate = 2000)
    public void execute() {
        List<String> result = demoApiClient.getlist2();
        log.info("定时任务执行 打印获取数据结果 {} ", result);

    }
}

在任务中找到一条执行记录中的traceId:56fda9d194e241f9a32e6c1ecf61ee58

在Service2中查看打印日志

日志采集

filebeat

日志采集工具,以本文为例展示配置文件

filebeat部分配置内容

# ============================== Filebeat inputs ===============================
 
filebeat.inputs:
 
- type: log
 
  enabled: true #设置成true才能启用log这个任务
 
  paths:
    - /usr/local/webapp/pro-user-ceshi/logs/info/*.log # 配置读取文件的地址
    - /usr/local/webapp/pro-user-ceshi/logs/warn//*.log 
  include_lines: ['\[[0-9a-f]{8}([0-9a-f]{4}){3}[0-9a-f]{12}\]']  #配置读取哪些行的日志
  exclude_lines: ['.*: ==>  Preparing:.*','.*: ==> Parameters:.*','.*: <==      Total:.*']  #配置排除哪些行的日志
   
  fields:
    type: pro-user-ceshi
  multiline.pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:| SQL参数'  #配合多长文本 本条是 以空格,Caused by,SQL参数 开头的都合并上一行
  multiline.negate: false
  multiline.match: after
 
- type: log
  enabled: false # 关闭状态下
  paths:
    - /usr/local/webapp/pro-user-ceshi2/logs/sql/*.log
     
  include_lines: ['\[[0-9a-f]{8}([0-9a-f]{4}){3}[0-9a-f]{12}\]']
  exclude_lines: ['.*: ==>  Preparing:.*','.*: ==> Parameters:.*','.*: <==      Total:.*']
   
  fields:
    type: pro-user-ceshi
  multiline.pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:|\\$'
  multiline.negate: false
  multiline.match: after
 
 
- type: filestream
 
  # Change to true to enable this input configuration.
  enabled: false
 
  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /var/log/*.log

# ============================== Filebeat modules ==============================
 
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
# ======================= Elasticsearch template setting =======================
 
setup.template.settings:
  index.number_of_shards: 1

 
# ---------------------------- Elasticsearch Output ----------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["192.168.1.132:9600"]  #es地址
  indices: 
    - index: "system-service-pro-user-ceshi_logs_%{+yyyy.MM.dd}" #es设置索引名称
      when.equals:
        fields.type: "pro-user-ceshi"
    - index: "system-pro-user-ceshi2_logs_%{+yyyy.MM.dd}"
      when.equals:
        fields.type: "pro-user-ceshi2"


完整代码在 https://gitee.com/momentzhj/log-collection

原文地址:https://www.cnblogs.com/jiage/p/14388275.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Nacos 中的参数有很多,如:命名空间、分组名、服务名、保护阈值、服务路由类型、临时实例等,那这些参数都是什么意思?又该如何设置?接下来我们一起来盘它。 1.命名空间 在 Nacos 中通过命名空间(Namespace)+ 分组(Group)+服务名(Name)可以定位到一个唯一的服务实例。 命名
Nacos 支持两种 HTTP 服务请求,一个是 REST Template,另一个是 Feign Client。之前的文章咱们介绍过 Rest Template 的调用方式,主要是通过 Ribbon(负载均衡) + RestTemplate 实现 HTTP 服务调用的,请求的核心代码是这样的: @
Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能。 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连接管理和请求转发的功能,让程序的开发者无需过多的关注服务提供者的稳定性和健康程度以及调用地址,因为这
Spring Cloud Alibaba 是阿里巴巴提供的一站式微服务开发解决方案,目前已被 Spring Cloud 官方收录。而 Nacos 作为 Spring Cloud Alibaba 的核心组件之一,提供了两个非常重要的功能:服务注册中心(服务注册和发现)功能,和统一配置中心功能。 Nac
在 Nacos 的路由策略中有 3 个比较重要的内容:权重、保护阈值和就近访问。因为这 3 个内容都是彼此独立的,所以今天我们就单独拎出“保护阈值”来详细聊聊。 保护阈值 保护阈值(ProtectThreshold):为了防止因过多实例故障,导致所有流量全部流入剩余健康实例,继而造成流量压力将剩余健
前两天遇到了一个问题,Nacos 中的永久服务删除不了,折腾了一番,最后还是顺利解决了。以下是原因分析和解决方案,建议先收藏,以备不时之需。 临时实例和持久化实例是 Nacos 1.0.0 中新增了一个特性。临时实例和持久化实例最大的区别是健康检查的方式:临时实例使用客户端主动上报的健康检查模式,而
Spring Cloud Alibaba 技术体系中的 Nacos,提供了两个重要的功能:注册中心(服务注册与发现)功能和配置中心功能。 其中注册中心解决了微服务调用中,服务提供者和服务调用者的解耦,让程序开发者可以无需过多的关注服务提供者和调用者的运行细节,只需要通过 Nacos 的注册中心就可以
负载均衡通器常有两种实现手段,一种是服务端负载均衡器,另一种是客户端负载均衡器,而我们今天的主角 Ribbon 就属于后者——客户端负载均衡器。 服务端负载均衡器的问题是,它提供了更强的流量控制权,但无法满足不同的消费者希望使用不同负载均衡策略的需求,而使用不同负载均衡策略的场景确实是存在的,所以客
本篇文章为大家展示了如何解决Spring Cloud 服务冲突问题,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、背景...
本篇内容主要讲解“spring cloud服务的注册与发现怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“spri...
本篇内容介绍了“Dubbo怎么实现Spring Cloud服务治理 ”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
本篇内容主要讲解“SpringCloud相关面试题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringCloud相...
如何分析Spring Cloud Ribbon、Spring Cloud Feign以及断路器,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希
这篇文章主要讲解了“springcloud微服务的组成部分有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“s...
这篇文章主要讲解了“SpringCloud的OpenFeign项目怎么创建”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习...
本篇内容主要讲解“spring cloud oauth3整合JWT后获取用户信息不全怎么办”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带...
怎样解析微服务架构SpringCloud,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。...
这篇文章主要介绍spring cloud中API网关的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、服务网关简介1、外观模式客户端...
本篇内容介绍了“Spring Cloud微服务的相关问题有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
本文小编为大家详细介绍“spring cloud config整合gitlab如何搭建分布式的配置中心”,内容详细,步骤清晰,细节处理妥当,希望这篇“spring cloud config整合gi...