ApiBoot Logging
可以无缝整合 SpringCloud
来采集请求日志,目前支持 RestTemplate
、 Openfeign
两种方式,我们本章来讲解下在使用 Openfeign
完成服务之间请求相互调用的一条链路请求日志是否可以都采集到。
博客原文地址: http://blog.yuqiyu.com/apiboot-logging-using-openfeign-transparent-traceid.html
我们先来搭建一个 Eureka Server
,请访问【 搭建服务注册中心Eureka Server
】文章内容查看具体搭建流程。
我们需要搭建一个 Logging Admin
用于接收 Logging Client
上报的请求日志,请访问【 ApiBoot Logging整合SpringCloud Eureka负载均衡上报日志
】查看具体的搭建流程。
我们本章来模拟 提交订单
的业务逻辑,涉及到两个服务,分别是: 商品服务
、 订单服务
,接下来我们需要来创建这两个服务。
Maven
多模块的形式进行编写,请拉至文章末尾查看下载本章源码。
由于是采用 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
可以继承实现,我们只需要创建一个公共的 服务接口定义
,在实现该接口的服务进行业务实现,在调用该接口的地方直接注入即可。
下面我们创建一个名为 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); }
@FeignClient
: SpringCloud Openfeign
提供的接口客户端定义注解,通过 value
或者 name
来指定 GoodClient
访问的具体 ServiceID
,这里我们配置的 value
值为 good-service
项目 spring.application.name
配置参数( ServiceID
= spring.application.name
)。
这样当我们通过注入 GoodClient
接口调用 getGoodPrice
方法时,底层通过 Openfeign
的 Http
代理访问 good-service
的对应接口。
下面我们再来创建一个名为 good-service
的 SpringBoot
项目。
在 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; } }
我们需要将 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
我们需要将 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
最后我们在 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-service
的 SpringBoot
项目(建议参考源码,本章采用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; } }
将我们创建的 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
我们需要将 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
修改 order-service
入口类 OrderServiceApplication
,添加启用 Eureka Client
、 Logging 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)的透传是否正确。
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" } ]
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
生成的 spanId
, traceId
同样也是 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接口服务架构师