让应用程序之间互相传递RequestId
我想要做的事情
如果服务涉及多个应用,在运行时可能需要查看跨应用的日志,下面是实现方法:
1. 使用Spring Boot生成RequestId,并将其输出到日志文件中。
2. 向其他应用(例如Gin)发送请求。
3. 在Gin端接收RequestId。
框架
Spring Boot
Gin
The environment
-
- Java
-
- Spring Boot
-
- Go
- Gin
在Spring Boot中配置RequestId的设置
确保日志文件中显示RequestId。
使用MDC来为每个请求生成UUID,并将其嵌入在日志中。
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
public class SampleFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestIdKey = "X-REQUEST-ID";
String uuid = UUID.randomUUID().toString();
// どこからでもリクエストIDが取得できるように設定しておく
request.setAttribute(requestIdKey, uuid);
try {
// ログに出力できるように設定
MDC.put(requestIdKey, uuid);
filterChain.doFilter(request, response);
} finally {
MDC.remove(requestIdKey);
}
}
}
将Filter注册到Bean中,以便能够使用Filter。
将/api下的路径设置为过滤器所适用的。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SampleConfiguration {
@Bean
public FilterRegistrationBean<SampleFilter> hogeFilter() {
FilterRegistrationBean<SampleFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new SampleFilter());
bean.addUrlPatterns("/api/*");
bean.setOrder(Integer.MIN_VALUE);
return bean;
}
}
Logback的设置
在SampleFilter.java中定义的X-REQUEST-ID也要设置为.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<springProperty name="loggingFilePath" source="logging.file.path" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${loggingFilePath}/spring.log</file>
<encoder>
<pattern>[%-5level] [%X{X-REQUEST-ID}] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%logger{36}] - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
这次我们只会发布INFO级别以上的内容。
logging.file.path=logs/
logging.level.org.springframework=INFO
提交请求并查看日志
请在 http://localhost:8080/api/ 上创建一个 GET 的端点。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api")
public class SampleController {
private static final Logger logger = LoggerFactory.getLogger(SampleController.class);
@GetMapping
public String get() {
logger.info("info!");
return "get";
}
}
请求结果的日志文件 de
[INFO ] [2389c607-f5ed-4789-95a3-efd78be1e8d9] [2020-XX-XX 23:26:25.536] [c.e.log.logdemo.SampleController] - info!
看起来像是由2389c607-f5ed-4789-95a3-efd78be1e8d9生成的UUID,好像正常显示出来了。
在使用RestTemplate向其他服务发送请求时,附加RequestId。
将RestTemplate注册为Bean,以便使用。(尽管在这里定义它并不是必需的,但这样做很方便。)
@SpringBootApplication
public class LogDemoApplication {
public static void main(String[] args) {
SpringApplication.run(LogDemoApplication.class, args);
}
@Bean
public RestTemplate setRestTemplate(){
return new RestTemplate();
}
}
为他的应用程序准备一个投放点。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@RestController
@RequestMapping("api")
public class SampleController {
private static final Logger logger = LoggerFactory.getLogger(SampleController.class);
// 追記
RestTemplate restTemplate;
// 追記
public SampleController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping
public String get() {
logger.info("info!");
return "get";
}
// 追記
@GetMapping("to-other-app")
public String toOtherApp() {
String requestId = (String) RequestContextHolder
.getRequestAttributes()
.getAttribute("X-REQUEST-ID", RequestAttributes.SCOPE_REQUEST);
logger.info("他のアプリケーションにリクエスト投げます!");
HttpHeaders headers = new HttpHeaders();
headers.set("X-REQUEST-ID", requestId);
HttpEntity<String> entity = new HttpEntity<>("headers", headers);
ResponseEntity<OtherAppResponse> response = restTemplate.exchange("http://localhost/api", HttpMethod.GET, entity, OtherAppResponse.class);
return response.getBody().getValue();
}
}
这样,Spring Boot的设置就完成了!
创建Gin侧的应用程序
我将使用另一篇我自己写的文章进行借用。
只需要运行“docker-compose up -d”,很简单。
访问http://localhost:80/api,只会返回一个 {“key”: “value”} 的API。
彼此互相确认日志。
Spring Boot将在http://localhost:8080/启动。
Gi将在http://localhost:80/启动。
在Spring Boot中处理请求
访问以下地址:http://localhost:8080/api/to-other-app
[INFO ] [549ef61a-44c9-4fe4-9c0f-3d923976d32f] [2020-XX-XX 00:19:00.274] [c.e.log.logdemo.SampleController] - 他のアプリケーションにリクエスト投げます!
我能看到日志
请求ID是 549ef61a-44c9-4fe4-9c0f-3d923976d32f
请检查Gin侧的日志文件。
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /api --> main.main.func1 (4 handlers)
[GIN-debug] Listening and serving HTTP on :3000
[GIN] 2020/XX/XX - 15:16:44 | 200 | 1.005ms | 172.19.0.3 | GET "/api"
{"time":"2020-XX-XXT15:19:00.3482677Z","level":"-","prefix":"-","file":"main.go","line":"30","message":"RequestId=549ef61a-44c9-4fe4-9c0f-3d923976d32f"}
收到的请求ID是549ef61a-44c9-4fe4-9c0f-3d923976d32f。
看起来似乎是匹配成功了(日期已隐藏)。
总结
我做了一些简要的工作,像是在Spring Boot中使请求ID在任何地方都可以使用,或者用Gin创建应用程序等等花费了一些时间。虽然在Spring Boot端使用RequestContextHolder之类的方法,但总感觉不太确定,也没有想到其他的方法,所以如果您知道的话,我会非常高兴能告诉我。由于在Gin端只需设置Header即可将其返回给Spring Boot,所以我忽略了这一部分。为什么选择Gin呢?因为我们团队平常是用Spring Boot + NodeJS实现的,但最近我对Go产生了兴趣(Go很有趣)。
如果有难以理解的地方,或者更高效的方法和建议,请务必评论告诉我。