要在 ELK (Elasticsearch, Logstash, Kibana) 堆栈中实现跨服务日志追踪,通常我们会采用 Trace-Id 来跟踪每个请求在微服务之间的流动。这里是如何在 ELK 中实现跨服务日志追踪的具体方案,并提供一个 Trace-Id 的实现方案和代码示例。
1. 跨服务日志追踪的整体思路
跨服务日志追踪的核心是生成一个唯一的 Trace-Id,并确保它在服务之间传递。以下是跨服务日志追踪的流程:
- 客户端发送请求,服务 A 生成一个唯一的 Trace-Id。
- Trace-Id 被附加到请求头中,服务 A 将请求转发到服务 B。
- 服务 B 从请求头中提取 Trace-Id,记录日志时将 Trace-Id 附加到日志中,然后继续处理请求。
- 同样地,服务 B 可以将请求转发给服务 C,并在请求头中附带相同的 Trace-Id。
- 所有微服务的日志中都会包含 Trace-Id,这使得跨服务日志可以通过 Trace-Id 进行关联。
- 最后,所有的日志数据都会被发送到 Logstash,并由 Elasticsearch 存储,使用 Kibana 可视化分析。
2. Trace-Id 实现方案
要实现 Trace-Id,我们需要在应用程序中完成以下几个步骤:
- 在每个服务的入口处生成 Trace-Id。
- 将 Trace-Id 附加到请求头中传递给其他服务。
- 在日志记录时确保将 Trace-Id 作为日志的字段之一。
3. 技术栈选择
我们使用以下技术栈来实现:
- Spring Boot:作为微服务框架,支持通过拦截器生成和传递 Trace-Id。
- Logback:作为日志框架,将 Trace-Id 添加到日志中。
- Logstash:用于收集日志并将其发送到 Elasticsearch。
4. 具体实现方案
4.1 在微服务中生成和传递 Trace-Id
我们可以通过 Spring Boot 的拦截器实现 Trace-Id 的生成和传递。
4.1.1 创建一个 Spring Boot 拦截器
java
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; @Component public class TraceIdInterceptor implements HandlerInterceptor { private static final String TRACE_ID_HEADER = "X-Trace-Id"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求头中的 Trace-Id,如果没有则生成一个新的 String traceId = request.getHeader(TRACE_ID_HEADER); if (traceId == null) { traceId = UUID.randomUUID().toString(); // 如果没有 Trace-Id,则生成一个新的 } // 将 Trace-Id 设置为响应头,以便其他服务使用 response.setHeader(TRACE_ID_HEADER, traceId); // 将 Trace-Id 添加到 MDC(Mapped Diagnostic Context),Logback 会自动从 MDC 中获取 org.slf4j.MDC.put("Trace-Id", traceId); return true; } }
4.1.2 配置 Spring Boot 拦截器
java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TraceIdInterceptor traceIdInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(traceIdInterceptor); } }
4.2 将 Trace-Id 添加到日志中
使用 Logback 和 SLF4J 实现将 Trace-Id 输出到日志中。首先,确保在 logback-spring.xml
中配置了 MDC 变量。
4.2.1 配置 Logback 记录 Trace-Id
在 logback-spring.xml
中添加以下配置:
xml
<configuration> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{Trace-Id}%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="console" /> </root> </configuration>
这里的 %X{Trace-Id}
是通过 MDC 获取并输出 Trace-Id。
4.3 跨服务传递 Trace-Id
假设服务 A 调用服务 B,在服务 A 的 HTTP 请求中附带 Trace-Id,并且服务 B 会从请求头中提取并使用它。
4.3.1 服务 A 调用服务 B
java
import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; public class ServiceAClient { private RestTemplate restTemplate; public ServiceAClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public void callServiceB(String traceId) { HttpHeaders headers = new HttpHeaders(); headers.set("X-Trace-Id", traceId); // 将 Trace-Id 加入请求头 HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange("http://service-b/endpoint", HttpMethod.GET, entity, String.class); } }
4.3.2 服务 B 获取 Trace-Id 并记录日志
java
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RestController public class ServiceBController { private static final Logger logger = LoggerFactory.getLogger(ServiceBController.class); @GetMapping("/endpoint") public String handleRequest(@RequestHeader("X-Trace-Id") String traceId) { // 记录日志时包含 Trace-Id logger.info("Received request with Trace-Id: {}", traceId); return "Request handled by Service B"; } }
4.4 配置 Logstash 收集日志并发送到 Elasticsearch
配置 Logstash 来收集应用程序日志并将其发送到 Elasticsearch。以下是一个基本的 Logstash 配置示例:
plaintext
input { file { path => "/path/to/your/application/logs/*.log" start_position => "beginning" } } filter { grok { match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:thread}\] %{LOGLEVEL:level} %{JAVACLASS:logger} - %{GREEDYDATA:message} %{DATA:trace_id}" } } } output { elasticsearch { hosts => ["http://localhost:9200"] index => "service-logs-%{+YYYY.MM.dd}" } }
4.5 在 Kibana 中查询和可视化
在 Kibana 中,你可以通过 Trace-Id 进行查询,查看跨服务的日志,并生成图表和仪表板来分析系统的行为。
总结
- 通过 Spring Boot 拦截器生成和传递 Trace-Id。
- 使用 Logback 在日志中记录 Trace-Id,以便在不同服务的日志中进行关联。
- 配置 Logstash 收集日志,并将其发送到 Elasticsearch,然后在 Kibana 中进行可视化分析。
这样,你就能在 ELK 堆栈中实现跨服务的日志追踪,通过 Trace-Id 关联各个微服务的日志,帮助开发者进行故障排查和性能优化。