我的编程空间,编程开发者的网络收藏夹
学习永远不晚

微服务分布式架构实现日志链路跟踪的方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

微服务分布式架构实现日志链路跟踪的方法

Logback 背景

Logback是由log4j创始人设计的另一个开源日志组件,官方网站:http://logback.qos.ch。它当前分为下面下个模块:

  • logback-core:其它两个模块的基础模块
  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging
  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

普通debug日志

SQL执行日志

Logback 配置案例

日志级别排序为:TRACE < DEBUG < INFO < WARN < ERROR

  • %d:表示日期
  • %n:换行
  • %thread:表示线程名
  • %level:日志级别
  • %msg:日志消息
  • %file:表示文件名
  • %class:表示文件名
  • %logger:Java类名(含包名,这里设定了36位,若超过36位,包名会精简为类似a.b.c.JavaBean)
  • %line:Java类的行号

 

注意:

%-4relative %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{TRACE_ID}] %-5level %logger{100}.%M\(%line\) - %msg%n

在logback中,%relative表示自应用程序启动以来打印相对时间戳(以毫秒为单位). %-4只是元素的对齐方式.

案例

3452487 2021-08-03 15:19:36.940 [thread-monitor-daemon][] WARN  com.xxxx.common.util.MonitorLogger.warn(27) - 发现超时线程notify-replay-consumer...

由于案例中是守护线程thread-monitor-daemon,所以不记录链路ID。

对在系统设计的时候对于线程的命名规范也是有约束的


这里就不做详细展开后续有机会会分享。
回归正题比如下面的例子中记录了请求的链路ID

19006989 2021-08-04 22:35:25.776 [http-nio-0.0.0.0-8010-exec-10][1fc8pebmgwukw863w2p342rp2936a3r157w0:0:] INFO  com.xxx.framework.eureka.core.listener.EurekaStateChangeListener.listen(58) - 服务实例[XX-PAAS]注册成功,当前服务器已注册服务实例数量[3]

对于上图中显示的系统启动时间、当前时间、当前线程、对应路径按照logback官方配置就可以逐步完善对于的日志信息,但是对于链路ID的生成写入就需要特殊处理。

链路ID设计

对于链路追踪设计我个人比较喜欢两种方案

第一种

在每一次请求中链路编号(traceId)、单元编号(spanId)都是通过HttpHeader的方式进行传递,日志的起始位置会主动生成traceId、spanId,而起始位置的Parent SpanId则是不存在的,值为null。

这样每次通过restTemplate、Openfeign的形式访问其他服务的接口时,就会携带起始位置生成的traceId、spanId到下一个服务单元。

 第二种

 

在每一次请求中链路编号(traceId),没经过一次微服务对于深度(Deep)加1


public static class ThreadTraceListener implements ThreadListener {
    @Override
    public void onThreadBegin(HttpServletRequest request) {
      String traceToken = ThreadLocalUtil.getTranVar(TRACE_ID);
      String fromServer = ThreadLocalUtil.getTranVar(FROM_SERVER);
      int deep;
      String traceId;
      if (StringUtils.isBlank(traceToken)) {
        traceId = IDGenerator.generateID();
        deep = 0;
        traceToken = StringHelper.join(traceId, ":0");
      } else {
        int index = traceToken.lastIndexOf(':');
        traceId = traceToken.substring(0, index);
        deep = Integer.valueOf(traceToken.substring(index + 1));
      }
      ThreadLocalUtil.setLocalVar(TRACE_ID, traceId);
      ThreadLocalUtil.setLocalVar(TRACE_DEEP, deep);
      ThreadLocalUtil.setTranVar(TRACE_ID, StringHelper.join(traceId, ":", deep + 1));
      ThreadLocalUtil.setLocalVar(FROM_SERVER, fromServer);
      ThreadLocalUtil.setTranVar(FROM_SERVER, getCurrentServer());
      MDC.put(TRACE_ID, StringHelper.join(traceToken, ":", fromServer));
    }

    @Override
    public void onThreadEnd(HttpServletRequest request) {
      MDC.remove(TRACE_ID);
    }
  }

针对请求拦截


protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response,
      FilterChain chain) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    // 从Header中装载传递过来的变量
    Map<String, Object> tranVar = new HashMap<String, Object>();
    Enumeration<String> headers = request.getHeaderNames();
    while (headers.hasMoreElements()) {
      String key = headers.nextElement();
      if (!StringUtils.isEmpty(key)
          && key.startsWith(ThreadLocalUtil.TRAN_PREFIX)) {
        tranVar.put(key.substring(ThreadLocalUtil.TRAN_PREFIX.length()),
            request.getHeader(key));
      }
    }
    ThreadLocalHolder.begin(tranVar, request);
    try {
      if (isGateway) {
        response.addHeader("X-TRACE-ID", TraceUtil.getTraceId());
      }
      // 检查RPC调用深度
      checkRpcDeep(request, response);
      // 业务处理
      chain.doFilter(request, response);
      // 记录RPC调用次数
      logRpcCount(request, response);
    } catch (Throwable ex) {
      // 错误处理
      Response<?> result = ExceptionUtil.toResponse(ex);
      Determine determine = ExceptionUtil.determineType(ex);
      ExceptionUtil.doLog(result, determine.getStatus(), ex);
      response.setStatus(determine.getStatus().value());
      response.setCharacterEncoding("UTF-8");
      response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
      response.getWriter().write(JsonUtil.toJsonString(result));
    } finally {
      try {
        doMonitor(request, response, startTime);
        if (TraceUtil.isTraceLoggerOn()) {
          log.warn(StringHelper.join(
              "TRACE-HTTP-", request.getMethod(),
              " URI:", request.getRequestURI(),
              ", dt:", System.currentTimeMillis() - startTime,
              ", rpc:", TraceUtil.getRpcCount(),
              ", status:", response.getStatus()));
        } else if (log.isTraceEnabled()) {
          log.trace(StringHelper.join(request.getMethod(),
              " URI:", request.getRequestURI(),
              ", dt:", System.currentTimeMillis() - startTime,
              ", rpc:", TraceUtil.getRpcCount(),
              ", status:", response.getStatus()));
        }
      } finally {
        ThreadLocalHolder.end(request);
      }
    }
}

到此这篇关于微服务分布式架构实现日志链路跟踪的方法的文章就介绍到这了,更多相关微服务分布式架构日志链路跟踪内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

微服务分布式架构实现日志链路跟踪的方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

SpringCloud分布式微服务云架构第九篇: 服务链路追踪(Finchley版本)

这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件。一、简介Add sleuth to the classpath of a Spring Boot application (see be
2023-06-05

微服务架构中分布式事务实现方案怎样何取舍

提起微服务架构,不可避免的两个话题就是服务治理和分布式事务。数据库和业务模块的垂直拆分为我们带来了系统性能、稳定性和开发效率的提升的同时也引入了一些更复杂的问题,例如在数据一致性问题上,我们不再能够依赖数据库的本地事务,对于一系列的跨库写入
2023-06-05

在ASP.NET Core微服务架构下使用RabbitMQ实现CQRS模式的方法

在ASP.NETCore微服务架构中,使用RabbitMQ实现CQRS模式有助于提升解耦、可扩展性和可靠性。本文介绍了如何完成以下步骤:安装RabbitMQ客户端包。创建消息代理客户端。定义消息队列和路由。发送和接收命令消息。发送和接收查询消息。配置消息路由。本文还强调了使用持久性消息、启用发布确认、监控消息代理和处理异常场景的重要性。
在ASP.NET Core微服务架构下使用RabbitMQ实现CQRS模式的方法
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录