­

ApiBoot Logging使用RestTemplate透传链路信息

  • 2019 年 11 月 11 日
  • 筆記

在上一篇文章【ApiBoot Logging使用SpringCloud Openfeign透传链路信息】中我们详细的讲解了ApiBoot Logging整合SpringCloud通过Openfeign进行透传链路信息,包括traceId(链路编号)、parentSpanId(上级单元编号)等信息。 ApiBoot Logging不仅仅可以使用Openfeign传递链路信息,还支持RestTemplate方式,本篇文章来详细的讲解下具体的使用方式。

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

搭建Logging Admin

我们需要搭建Logging Admin服务,用于接收业务服务上报的请求日志信息,请参考【将ApiBoot Logging采集的日志上报到Admin】文章内容.

添加ApiBoot统一版本

由于本章采用是Maven 多模块的方式构建源码,所以我们只需要将ApiBoot统一版本的依赖配置在root项目的pom.xml内,如下所示:

<properties>    <java.version>1.8</java.version>    <!--ApiBoot版本号-->    <api.boot.version>2.1.5.RELEASE</api.boot.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>    </dependencies>  </dependencyManagement>

接下来我们营造本篇文章的模拟场景查询用户基本信息时一并查询出用户的账号余额

创建账户服务

创建一个名为account-serviceSpringBoot项目。

添加相关依赖

在项目pom.xml配置文件内添加相关依赖,如下所示:

<dependencies>    <!--SpringBoot Web-->    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--ApiBoot Logging-->    <dependency>      <groupId>org.minbox.framework</groupId>      <artifactId>api-boot-starter-logging</artifactId>    </dependency>  </dependencies>

配置上报的Logging Admin

application.yml配置文件内添加请求日志上报的Logging Admin地址,如下所示:

spring:    application:      name: account-service  server:    port: 9090    api:    boot:      logging:        # 控制台打印请求日志        show-console-log: true        # 美化请求日志        format-console-log-json: true        # Logging Admin地址        admin:          server-address: 127.0.0.1:8081

注意:server-address配置参数不需要添加http://前缀

启用Logging Client

添加完成依赖后我们通过@EnableLoggingClient注解来启用ApiBoot Logging,在AccountServiceApplication类上添加如下所示:

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

@EnableLoggingClient注解就实例化部分ApiBoot Logging内部所需要的类,将实例放置到Spring IOC容器内。

查询账户余额代码实现

我们创建一个名为AccountController的控制器来提供查询账户的余额信息,代码实现如下所示:

/**   * 账户服务实现   *   * @author 恒宇少年   */  @RestController  @RequestMapping(value = "/account")  public class AccountController {        /**       * 示例,内存账户列表       */      static final HashMap<Integer, Double> ACCOUNTS = new HashMap() {{          put(1, 1233.22);          put(2, 69269.22);      }};        /**       * 获取指定账户的余额       *       * @param accountId       * @return       */      @GetMapping(value = "/{accountId}")      public Double getBalance(@PathVariable("accountId") Integer accountId) {          return ACCOUNTS.get(accountId);      }  }

至此我们的账户服务已经编写完成,下面我们来编写用户服务

创建用户服务

我们来创建一个名为user-serviceSpringBoot项目。

添加相关依赖

在项目pom.xml配置文件内添加相关依赖,如下所示:

<dependencies>    <!--SpringBoot Web-->    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--ApiBoot Logging-->    <dependency>      <groupId>org.minbox.framework</groupId>      <artifactId>api-boot-starter-logging</artifactId>    </dependency>  </dependencies>

配置上报的Logging Admin

本章我们使用指定Logging Admin地址的方式配置,修改application.yml配置文件如下所示:

spring:    application:      name: user-service  server:    port: 9091    api:    boot:      logging:        # 控制台打印请求日志        show-console-log: true        # 美化请求日志        format-console-log-json: true        # Logging Admin地址        admin:          server-address: 127.0.0.1:8081

启用Logging Client

添加完依赖后我们需要在XxxApplication入口类上添加@EnableLoggingClient注解来启用ApiBoot Logging,如下所示:

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

实例化RestTemplate对象

user-service需要访问账户服务获取当前用户的余额,所以我们需要在user-service内实例化RestTemplate,这样我们才可以通过RestTemplate访问获取用户账户余额信息,我们直接在UserServiceApplication类内添加实例,如下所示:

    /**       * 实例化RestTemplate       *       * @return {@link RestTemplate}       */      @Bean      @ConditionalOnMissingBean      public RestTemplate restTemplate() {          return new RestTemplate();      }

注解解释:

  • @ConditionalOnMissingBean:这是SpringBoot条件注入其中的一个注解,表示当IOC容器内不存在RestTemplate类型的实例时才会去执行restTemplate()方法创建对象。

查询用户信息代码实现

/**   * 用户基本信息控制器   *   * @author 恒宇少年   */  @RestController  @RequestMapping(value = "/user")  public class UserController {      /**       * 示例,用户列表       */      static final HashMap<Integer, User> USERS = new HashMap() {{          put(1, new User(1, "恒宇少年"));          put(2, new User(2, "于起宇"));      }};      /**       * 注入RestTemplate       */      @Autowired      private RestTemplate restTemplate;        /**       * 获取用户基本信息       *       * @param userId 用户编号       * @return       */      @GetMapping(value = "/{userId}")      public User getUserInfo(@PathVariable("userId") Integer userId) {          ResponseEntity<Double> responseEntity = restTemplate.getForEntity("http://localhost:9090/account/{accountId}", Double.class, userId);          Double balance = responseEntity.getBody();          User user = USERS.get(userId);          if (ObjectUtils.isEmpty(user)) {              throw new RuntimeException("用户:" + userId + ",不存在.");          }          user.setBalance(balance);          return user;      }        @Data      public static class User {          private Integer id;          private String name;          private Double balance;            public User(Integer id, String name) {              this.id = id;              this.name = name;          }      }  }

我们所需要的两个服务都已经编写完成,下面我们来测试RestTemplate是可以透传ApiBoot Logging的链路信息?

运行测试

依次启动logging-admin > user-service > account-service

测试点:透传链路信息

我们使用curl命令访问user-service提供的地址/user,如下所示:

➜ ~ curl http://localhost:9091/user/1  {"id":1,"name":"恒宇少年","balance":1233.22}

下面我看来看下logging-admin控制台接收到的请求日志。

接收user-service请求日志

Receiving Service: 【user-service -> 127.0.0.1】, Request Log Report,Logging Content:[      {          "endTime":1573032865311,          "httpStatus":200,          "requestBody":"",          "requestHeaders":{              "host":"localhost:9091",              "user-agent":"curl/7.64.1",              "accept":"*/*"          },          "requestIp":"0:0:0:0:0:0:0:1",          "requestMethod":"GET",          "requestParam":"{}",          "requestUri":"/user/1",          "responseBody":"{"id":1,"name":"恒宇少年","balance":1233.22}",          "responseHeaders":{},          "serviceId":"user-service",          "serviceIp":"127.0.0.1",          "servicePort":"9091",          "spanId":"f8cff018-42d5-481f-98df-c19b7196b3c3",          "startTime":1573032865130,          "timeConsuming":181,          "traceId":"16ad1dd4-beaa-4110-b4b7-fc7d952d9a57"      }  ]

接收account-service请求日志

Receiving Service: 【account-service -> 127.0.0.1】, Request Log Report,Logging Content:[      {          "endTime":1573032865309,          "httpStatus":200,          "parentSpanId":"f8cff018-42d5-481f-98df-c19b7196b3c3",          "requestBody":"",          "requestHeaders":{              "minbox-logging-x-parent-span-id":"f8cff018-42d5-481f-98df-c19b7196b3c3",              "minbox-logging-x-trace-id":"16ad1dd4-beaa-4110-b4b7-fc7d952d9a57",              "host":"localhost:9090",              "connection":"keep-alive",              "accept":"application/json, application/*+json",              "user-agent":"Java/1.8.0_211"          },          "requestIp":"127.0.0.1",          "requestMethod":"GET",          "requestParam":"{}",          "requestUri":"/account/1",          "responseBody":"1233.22",          "responseHeaders":{},          "serviceId":"account-service",          "serviceIp":"127.0.0.1",          "servicePort":"9090",          "spanId":"63b18b40-5718-431c-972f-78956ce78380",          "startTime":1573032865307,          "timeConsuming":2,          "traceId":"16ad1dd4-beaa-4110-b4b7-fc7d952d9a57"      }  ]
  • 当我们访问user-service服务内的/user路径时,因为是第一次访问ApiBoot Logging会主动创建traceId(链路编号)、spanId(单元编号),因为没有上级单元所以parentSpanIdnull.
  • 而通过查看account-service服务上报的请求日志时,可以看到ApiBoot Logging相关的链路信息是通过HttpHeader的方式进行传递的
    • minbox-logging-x-trace-id -> 链路编号
    • minbox-logging-x-parent-span-id -> 上级单元编号

敲黑板,划重点

ApiBoot Logging在内部自动化实现了RestTemplate的拦截器配置,所以我们只需要创建实例就可以,而不需要主动去配置拦截器信息,具体源码请访问org.minbox.framework.logging.client.http.rest.LoggingRestTemplateInterceptor查看。

不管你一次请求跨度几个服务,都可以将请求入口生成的链路信息进行依次传递,而上下级关系则是根据parentSpanIdspanId进行绑定的。

代码示例 本篇文章示例源码可以通过以下途径获取,目录为SpringBoot2.x/apiboot-logging-using-resttemplate-transparent-traceid