ApiBoot Logging使用SpringCloud Ope

作者: 恒宇少年 | 来源:发表于2019-11-06 14:10 被阅读0次

    ApiBoot Logging可以无缝整合SpringCloud来采集请求日志,目前支持RestTemplateOpenfeign两种方式,我们本章来讲解下在使用Openfeign完成服务之间请求相互调用的一条链路请求日志是否可以都采集到。

    免费教程专题

    恒宇少年在博客整理三套免费学习教程专题,由于文章偏多特意添加了阅读指南,新文章以及之前的文章都会在专题内陆续填充,希望可以帮助大家解惑更多知识点。

    博客原文地址:http://blog.yuqiyu.com/apiboot-logging-using-openfeign-transparent-traceid.html

    搭建Eureka Server

    我们先来搭建一个Eureka Server,请访问【搭建服务注册中心Eureka Server】文章内容查看具体搭建流程。

    搭建Logging Admin

    我们需要搭建一个Logging Admin用于接收Logging Client上报的请求日志,请访问【ApiBoot Logging整合SpringCloud Eureka负载均衡上报日志】查看具体的搭建流程。

    我们本章来模拟提交订单的业务逻辑,涉及到两个服务,分别是:商品服务订单服务,接下来我们需要来创建这两个服务。

    本章源码采用Maven多模块的形式进行编写,请拉至文章末尾查看下载本章源码。

    添加ApiBoot & SpringCloud统一版本

    由于是采用Maven 多模块项目,存在继承关系,我们只需要在root模块添加版本依赖即可,其他子模块就可以直接使用,如下所示:

    <properties>
      <java.version>1.8</java.version>
      <!--ApiBoot版本号-->
      <api.boot.version>2.1.5.RELEASE</api.boot.version>
      <!--SpringCloud版本号-->
      <spring.cloud.version>Greenwich.SR3</spring.cloud.version>
    </properties>
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.minbox.framework</groupId>
          <artifactId>api-boot-dependencies</artifactId>
          <version>${api.boot.version}</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <version>${spring.cloud.version}</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    

    创建公共Openfeign接口定义

    学习过Openfeign的同学应该都知道,Openfeign可以继承实现,我们只需要创建一个公共的服务接口定义,在实现该接口的服务进行业务实现,在调用该接口的地方直接注入即可。
    下面我们创建一个名为common-openfeign的公共依赖项目,pom.xml添加依赖如下所示:

    <dependencies>
      <!--SpringBoot Web-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <optional>true</optional>
      </dependency>
      <!--Openfeign-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <optional>true</optional>
      </dependency>
    </dependencies>
    

    在提交订单时我们简单模拟需要获取商品的单价,所以在common-openfeign项目内我们要提供一个查询商品单价的服务接口,创建一个名为GoodClient的接口如下所示:

    package org.minbox.chapter.common.openfeign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * 商品服务接口定义
     *
     * @author 恒宇少年
     */
    @FeignClient(name = "good-service")
    @RequestMapping(value = "/good")
    public interface GoodClient {
        /**
         * 获取商品价格
         *
         * @param goodId 商品编号
         * @return
         */
        @GetMapping(value = "/{goodId}")
        Double getGoodPrice(@PathVariable("goodId") Integer goodId);
    }
    

    注解解释:

    • @FeignClientSpringCloud Openfeign提供的接口客户端定义注解,通过value或者name来指定GoodClient访问的具体ServiceID,这里我们配置的value值为good-service项目spring.application.name配置参数(ServiceID = spring.application.name)。

    这样当我们通过注入GoodClient接口调用getGoodPrice方法时,底层通过OpenfeignHttp代理访问good-service的对应接口。

    创建商品服务

    下面我们再来创建一个名为good-serviceSpringBoot项目。

    添加相关依赖

    pom.xml项目配置文件内添加如下依赖:

    <dependencies>
      <!--ApiBoot Logging Client-->
      <dependency>
        <groupId>org.minbox.framework</groupId>
        <artifactId>api-boot-starter-logging</artifactId>
      </dependency>
    
      <!--SpringBoot Web-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    
      <!--Eureka Client-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    
      <!--公共Openfeign接口定义依赖-->
      <dependency>
        <groupId>org.minbox.chapter</groupId>
        <artifactId>common-openfeign</artifactId>
        <version>0.0.1-SNAPSHOT</version>
      </dependency>
    </dependencies>
    

    可以看到我们在good-service项目依赖内添加了我们在上面创建的common-openfeign依赖模块,因为GoodClient服务接口的实现是在good-service项目内,我们需要添加common-openfeign依赖后创建对应的XxxController并实现GoodClient接口完成对应的业务逻辑实现。

    商品业务实现

    这里我们简单做个示例,将价格固定返回,实现GoodClient的控制器如下所示:

    package org.minbox.chapter.good.service;
    
    import org.minbox.chapter.common.openfeign.GoodClient;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 商品服务接口实现
     *
     * @author 恒宇少年
     * @see GoodClient
     */
    @RestController
    public class GoodController implements GoodClient {
        @Override
        public Double getGoodPrice(Integer goodId) {
            if (goodId == 1) {
                return 15.6;
            }
            return 0D;
        }
    }
    

    注册到Eureka Server

    我们需要将good-service注册到Eureka Server,修改application.yml配置文件如下所示:

    # ServiceID
    spring:
      application:
        name: good-service
    # 端口号
    server:
      port: 8082
    # Eureka Config
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10000/eureka/
      instance:
        prefer-ip-address: true
    

    配置上报的Logging Admin

    我们需要将good-service的请求日志上报到Logging Admin,采用SpringCloud ServiceID的方式配置,修改application.yml配置文件如下所示:

    api:
      boot:
        logging:
          # 控制台打印日志
          show-console-log: true
          # 美化打印日志
          format-console-log-json: true
          # 配置Logging Admin 服务编号
          discovery:
            service-id: logging-admin
    

    启用Eureka Client & Logging

    最后我们在XxxApplication入口类添加注解来启用Eureka Client以及Logging Client,如下所示:

    /**
     * 商品服务
     *
     * @author 恒宇少年
     */
    @SpringBootApplication
    @EnableLoggingClient
    @EnableDiscoveryClient
    public class GoodServiceApplication {
        /**
         * logger instance
         */
        static Logger logger = LoggerFactory.getLogger(GoodServiceApplication.class);
    
        public static void main(String[] args) {
            SpringApplication.run(GoodServiceApplication.class, args);
            logger.info("{}服务启动成功.", "商品");
        }
    }
    

    至此我们的商品服务已经准备完成.

    创建订单服务

    创建一个名为order-serviceSpringBoot项目(建议参考源码,本章采用Maven多模块创建)。

    添加相关依赖

    修改pom.xml添加相关依赖如下所示:

    <dependencies>
      <!--ApiBoot Logging Client-->
      <dependency>
        <groupId>org.minbox.framework</groupId>
        <artifactId>api-boot-starter-logging</artifactId>
      </dependency>
    
      <!--SpringBoot Web-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    
      <!--Eureka Client-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    
      <!--Openfeign-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
    
      <!--公共Openfeign接口定义依赖-->
      <dependency>
        <groupId>org.minbox.chapter</groupId>
        <artifactId>common-openfeign</artifactId>
        <version>0.0.1-SNAPSHOT</version>
      </dependency>
    </dependencies>
    

    订单业务实现

    我们来模拟一个提交订单的场景,创建一个名为OrderController的控制器,如下所示:

    /**
     * 订单控制器
     *
     * @author 恒宇少年
     */
    @RestController
    @RequestMapping(value = "/order")
    public class OrderController {
        /**
         * 商品接口定义注入
         * {@link GoodClient}
         */
        @Autowired
        private GoodClient goodClient;
    
        @PostMapping
        public String submit(Integer goodId, Integer buyCount) {
            Double goodPrice = goodClient.getGoodPrice(goodId);
            Double orderAmount = goodPrice * buyCount;
            //...
            return "订单提交成功,订单总金额:" + orderAmount;
        }
    }
    

    注册到Eureka Server

    将我们创建的order-service注册到Eureka Server,修改application.yml配置文件如下所示:

    spring:
      application:
        name: order-service
    server:
      port: 8081
    # Eureka Config
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10000/eureka/
      instance:
        prefer-ip-address: true
    

    配置上报的Logging Admin

    我们需要将order-service的请求日志上报到Logging Admin,采用SpringCloud ServiceID的方式配置,修改application.yml配置文件如下所示:

    api:
      boot:
        logging:
          # 控制台打印日志
          show-console-log: true
          # 美化打印日志
          format-console-log-json: true
          # 配置Logging Admin 服务编号
          discovery:
            service-id: logging-admin
    

    启用Eureka Client & Logging

    修改order-service入口类OrderServiceApplication,添加启用Eureka ClientLogging Client的注解,如下所示:

    /**
     * 订单服务
     *
     * @author 恒宇少年
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableLoggingClient
    @EnableFeignClients(basePackages = "org.minbox.chapter.common.openfeign")
    public class OrderServiceApplication {
        /**
         * logger instance
         */
        static Logger logger = LoggerFactory.getLogger(OrderServiceApplication.class);
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
            logger.info("{}服务启动成功.", "");
        }
    }
    

    注解解释:

    • @EnableFeignClients:该注解是Openfeign提供的启用自动扫描Client的配置,我们通过basePackages(基础包名)的方式进行配置扫描包下配置@FeignClient注解的接口,并为每个接口生成对应的代理实现并添加到Spring IOC容器。

      org.minbox.chapter.common.openfeign包名在common-openfeign项目内。

    运行测试

    依次启动项目,eureka-server > logging-admin > good-service > order-service

    通过curl命令访问order-service内的提交订单地址:/order,如下所示:

    ➜ ~ curl -X POST http://localhost:8081/order\?goodId\=1\&buyCount\=3
    订单提交成功,订单总金额:46.8
    

    可以看到我们已经可以成功的获取订单的总金额,我们在/order请求方法内调用good-service获取商品的单价后计算得到订单总金额。

    测试点:链路信息传递

    我们通过控制台输出的日志信息来确认下链路信息(traceId、spanId)的透传是否正确。

    收到order-service上报的日志

    Receiving Service: 【order-service -> 127.0.0.1】, Request Log Report,Logging Content:[
        {
            "endTime":1573009439840,
            "httpStatus":200,
            "requestBody":"",
            "requestHeaders":{
                "host":"localhost:8081",
                "user-agent":"curl/7.64.1",
                "accept":"*/*"
            },
            "requestIp":"0:0:0:0:0:0:0:1",
            "requestMethod":"POST",
            "requestParam":"{\"buyCount\":\"3\",\"goodId\":\"1\"}",
            "requestUri":"/order",
            "responseBody":"订单提交成功,订单总金额:46.8",
            "responseHeaders":{},
            "serviceId":"order-service",
            "serviceIp":"127.0.0.1",
            "servicePort":"8081",
            "spanId":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4",
            "startTime":1573009439301,
            "timeConsuming":539,
            "traceId":"3e20cc72-c880-4575-90ed-d54a6b4fe555"
        }
    ]
    

    收到good-service上报的日志

    Receiving Service: 【good-service -> 127.0.0.1】, Request Log Report,Logging Content:[
        {
            "endTime":1573009439810,
            "httpStatus":200,
            "parentSpanId":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4",
            "requestBody":"",
            "requestHeaders":{
                "minbox-logging-x-parent-span-id":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4",
                "minbox-logging-x-trace-id":"3e20cc72-c880-4575-90ed-d54a6b4fe555",
                "host":"10.180.98.156:8082",
                "connection":"keep-alive",
                "accept":"*/*",
                "user-agent":"Java/1.8.0_211"
            },
            "requestIp":"10.180.98.156",
            "requestMethod":"GET",
            "requestParam":"{}",
            "requestUri":"/good/1",
            "responseBody":"15.6",
            "responseHeaders":{},
            "serviceId":"good-service",
            "serviceIp":"127.0.0.1",
            "servicePort":"8082",
            "spanId":"6339664e-097d-4a01-a734-935de52a7d44",
            "startTime":1573009439787,
            "timeConsuming":23,
            "traceId":"3e20cc72-c880-4575-90ed-d54a6b4fe555"
        }
    ]
    

    结果分析:

    • 请求日志的入口为order-service所以并不存在parentSpanId(上级单元编号),而spanId(单元编号)、traceId(链路编号)也是新生成的。

    • 本次请求会经过good-service服务,因此parentSpanId则是order-service生成的spanIdtraceId同样也是order-service生成的,透传HttpHeader方式进行传递,表示在同一条请求链路上。

    敲黑板,划重点

    ApiBoot Logging支持使用Openfeign传递链路信息,内部通过Openfeign拦截器实现,源码详见:org.minbox.framework.logging.client.http.openfeign.LoggingOpenFeignInterceptor

    traceId(链路编号)、parentSpanId(单元编号)通过HttpHeader的形式传递到目标访问服务,服务通过请求日志拦截器进行提取并设置链路绑定关系。

    • traceId传递时HttpHeader名称为:minbox-logging-x-trace-id.
    • parentSpanId传递时HttpHeader名称为:minbox-logging-x-parent-span-id

    代码示例

    如果您喜欢本篇文章请为源码仓库点个Star,谢谢!!!
    本篇文章示例源码可以通过以下途径获取,目录为SpringBoot2.x/apiboot-logging-using-openfeign-transparent-traceid

    作者个人 博客
    使用开源框架 ApiBoot 助你成为Api接口服务架构师

    相关文章

      网友评论

        本文标题:ApiBoot Logging使用SpringCloud Ope

        本文链接:https://www.haomeiwen.com/subject/pisubctx.html