文章目录
-
- 一、开篇:可观测性的最后一块拼图
- 二、日志规范与分级
-
- [2.1 日志分级原则](#2.1 日志分级原则)
- [2.2 日志内容规范](#2.2 日志内容规范)
- [2.3 结构化日志最佳实践](#2.3 结构化日志最佳实践)
- [2.4 敏感信息脱敏](#2.4 敏感信息脱敏)
- 三、ELK架构与部署
-
- [3.1 为什么选择ELK](#3.1 为什么选择ELK)
- [3.2 Docker Compose部署ELK](#3.2 Docker Compose部署ELK)
- [3.3 Logstash管道配置](#3.3 Logstash管道配置)
- [3.4 Filebeat配置](#3.4 Filebeat配置)
- 四、Logback集成与TraceID注入
-
- [4.1 Spring Boot 4.0的Logback配置](#4.1 Spring Boot 4.0的Logback配置)
- [4.2 TraceID注入机制](#4.2 TraceID注入机制)
- [4.3 异步线程中的TraceID传递](#4.3 异步线程中的TraceID传递)
- 五、Kibana日志检索实战
-
- [5.1 创建索引模式](#5.1 创建索引模式)
- [5.2 核心检索语法](#5.2 核心检索语法)
- [5.3 从SkyWalking跳转到Kibana](#5.3 从SkyWalking跳转到Kibana)
- [5.4 日志可视化仪表盘](#5.4 日志可视化仪表盘)
- 六、线上问题排查体系
-
- [6.1 标准排查流程](#6.1 标准排查流程)
- [6.2 真实排查案例](#6.2 真实排查案例)
- [6.3 日志生产优化](#6.3 日志生产优化)
- 七、踩坑指南
- 八、阶段总结与下节预告
- [🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航](#🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航)

适配版本 :Elasticsearch 8.15、Logstash 8.15、Kibana 8.15、Spring Boot 4.0.8、SkyWalking 10.4.0、JDK 21
课程定位:监控追踪阶段收官,从日志规范到ELK搭建,从链路ID联动到线上问题排查,构建完整的可观测性闭环
一、开篇:可观测性的最后一块拼图
第25-27课我们完成了监控体系的两大支柱:指标(Metrics) 和链路追踪(Tracing) 。Prometheus+Grafana告诉你"系统当前状态如何",SkyWalking告诉你"一个请求经过了哪些服务、每段耗时多少"。
但这两者都无法回答最后一类问题:"系统在某个时刻到底发生了什么?"
指标是聚合数据------http_server_requests_seconds的P95延迟是3秒,但你看不到这3秒里究竟哪条SQL执行了、哪个参数导致了慢查询、堆栈里报了什么异常。链路追踪记录了Span的耗时和状态码,但Span的详细异常堆栈、业务上下文信息,只能通过日志获取。
更重要的是,当线上出现问题时,运维人员的第一反应往往是查日志 。错误码是什么?异常堆栈是什么?请求参数是什么?这些问题的答案都在日志里。但如果日志散落在20个服务的20台机器上,排查一个跨服务的问题可能需要登录5台服务器、grep几十个文件------效率极低。
ELK(Elasticsearch + Logstash + Kibana) 正是为解决这个问题而生的。它将所有服务的日志集中收集、统一存储、全文检索,配合SkyWalking的TraceID,实现"一键从链路跳转到该请求在所有服务中的完整日志"的排查体验。
本课将从日志规范讲起,完成ELK的搭建和Logback的集成,实战基于TraceID的全链路日志检索,最后总结线上问题排查的标准流程。
二、日志规范与分级
2.1 日志分级原则
Java生态中广泛使用的SLF4J定义了五个日志级别,每个级别有明确的语义和适用场景:
| 级别 | 语义 | 适用场景 | 生产环境 |
|---|---|---|---|
| ERROR | 系统出现错误,需要人工介入 | 业务异常、系统异常、外部调用失败 | ✅ 必须输出 |
| WARN | 潜在问题,不影响当前请求 | 降级触发、重试成功、资源接近阈值 | ✅ 建议输出 |
| INFO | 关键业务流程节点 | 请求入口/出口、订单创建、支付回调 | ✅ 核心链路输出 |
| DEBUG | 调试信息 | 方法参数、中间变量、SQL语句 | ❌ 生产关闭 |
| TRACE | 最细粒度追踪 | 循环内部、框架底层 | ❌ 生产关闭 |
生产环境日志级别配置原则:
- 根日志级别设为INFO:避免DEBUG/TRACE导致日志量爆炸
- 核心业务包设为INFO或DEBUG :
com.example.microservice包下设为INFO,排查时临时调整为DEBUG - 框架包设为WARN :
org.springframework、org.apache等框架包设为WARN,减少噪音日志 - 动态调整 :通过Actuator的
/actuator/loggers端点动态修改日志级别,无需重启
2.2 日志内容规范
一条合格的日志应包含以下要素:
| 要素 | 说明 | 示例 |
|---|---|---|
| 时间戳 | 精确到毫秒 | 2026-09-13 10:30:15.123 |
| TraceID | 链路追踪ID,用于跨服务串联 | a1b2c3d4e5f6.1234567890.123456.01 |
| 线程名 | 便于分析并发问题 | http-nio-8081-exec-1 |
| 日志级别 | ERROR/WARN/INFO/DEBUG | INFO |
| 类名 | 精简后的类名 | c.e.m.u.c.UserController |
| 日志内容 | 结构化的业务信息 | 查询用户: id=1, cost=45ms |
Logback配置模板:
xml
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} - %msg%n</pattern>
2.3 结构化日志最佳实践
反模式一:字符串拼接 。字符串拼接无论日志级别是否生效,都会执行拼接操作,浪费CPU。应使用占位符写法:
java
// ❌ 错误:字符串拼接
log.debug("用户信息: " + JSON.toJSONString(user));
// ✅ 正确:占位符写法(日志级别不生效时,不会执行序列化)
log.debug("用户信息: {}", JSON.toJSONString(user));
反模式二:在循环中打日志。循环中的日志会指数级放大日志量,应改为批量输出或采样输出。
反模式三:异常只打message不打堆栈 。log.error("异常: " + e.getMessage())会丢失异常堆栈,排查时无从下手:
java
// ❌ 错误:丢失堆栈
log.error("调用失败: " + e.getMessage());
// ✅ 正确:传入异常对象,SLF4J会自动打印堆栈
log.error("调用用户服务失败: userId={}", userId, e);
2.4 敏感信息脱敏
生产环境的日志绝不能包含以下信息:
| 敏感类型 | 示例 | 脱敏方式 |
|---|---|---|
| 密码 | password=123456 |
password=*** |
| 手机号 | 13812345678 |
138****5678 |
| 身份证 | 110101199001011234 |
110101********1234 |
| 银行卡 | 6222021234567890123 |
6222***********0123 |
| Token | Bearer eyJhbGci... |
Bearer *** |
脱敏方案 :通过Logback的MessageConverter或自定义Layout实现全局脱敏,或在业务代码中通过工具类脱敏。
三、ELK架构与部署
3.1 为什么选择ELK
ELK是Elasticsearch + Logstash + Kibana的缩写,后来加入了Filebeat(轻量级日志采集器),架构演进为Filebeat + Logstash + Elasticsearch + Kibana:
| 组件 | 职责 | 部署位置 |
|---|---|---|
| Filebeat | 轻量级日志采集,监听日志文件变化 | 每台应用服务器 |
| Logstash | 日志解析、过滤、格式化 | 独立服务器或Kafka后 |
| Elasticsearch | 日志存储和全文检索 | 集群部署 |
| Kibana | 日志查询和可视化 | 独立服务器 |
为什么用Filebeat而非Logstash采集? Logstash基于JVM,资源消耗大(至少1G内存)。Filebeat基于Go,资源消耗极小(几十MB),适合在每台应用服务器上部署。
为什么需要Logstash? Filebeat只能做简单的多行合并和字段添加,复杂的日志解析(如从日志中提取TraceID、业务字段)需要Logstash的Grok过滤器完成。
3.2 Docker Compose部署ELK
创建docker-compose.yml:
yaml
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.15.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms2g -Xmx2g
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
logstash:
image: logstash:8.15.0
container_name: logstash
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
ports:
- "5044:5044"
depends_on:
- elasticsearch
kibana:
image: kibana:8.15.0
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
volumes:
es-data:
3.3 Logstash管道配置
创建logstash.conf:
ruby
input {
beats {
port => 5044
}
}
filter {
# 解析Spring Boot的默认日志格式
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:traceId}\] \[%{DATA:thread}\] %{LOGLEVEL:level} %{DATA:logger} - %{GREEDYDATA:msg}"
}
}
# 解析JSON格式的日志(如果日志内容本身是JSON)
json {
source => "msg"
target => "logDetail"
skip_on_invalid_json => true
}
# 提取异常类型
if [msg] =~ /Exception/ {
grok {
match => { "msg" => "%{JAVACLASS:exceptionType}: %{GREEDYDATA:exceptionMessage}" }
}
}
# 移除不需要的字段
mutate {
remove_field => ["@version", "agent", "ecs", "host", "input", "log", "tags"]
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "spring-logs-%{+YYYY.MM.dd}"
}
}
关键配置解读:
| 配置 | 作用 |
|---|---|
grok |
从日志文本中提取结构化字段(时间戳、TraceID、日志级别等) |
json |
如果日志内容本身是JSON,解析为结构化字段 |
index |
按天创建索引,便于生命周期管理和清理 |
3.4 Filebeat配置
在每台应用服务器上安装Filebeat,配置filebeat.yml:
yaml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/microservice/*.log
multiline:
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
fields:
service: service-user
env: prod
output.logstash:
hosts: ["logstash:5044"]
关键配置解读:
| 配置 | 作用 |
|---|---|
multiline |
多行合并------以日期开头的行是日志的开始,异常堆栈等不以日期开头的行合并到上一行 |
fields.service |
添加服务名标签,便于在Kibana中按服务筛选 |
踩坑提示 :如果日志中包含
\n的多行异常堆栈,Filebeat会将其拆分为多条独立日志。multiline配置是必须的,否则异常堆栈在Kibana中会显示为多条碎片。
四、Logback集成与TraceID注入
4.1 Spring Boot 4.0的Logback配置
创建logback-spring.xml,放在src/main/resources下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件输出(JSON格式,便于ELK解析) -->
<appender name="FILE_JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>tid</includeMdcKeyName>
<customFields>{"service":"${APP_NAME}"}</customFields>
</encoder>
</appender>
<!-- 异步输出,避免日志阻塞业务线程 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>true</neverBlock>
<appender-ref ref="FILE_JSON"/>
</appender>
<!-- 业务包日志级别 -->
<logger name="com.example.microservice" level="INFO"/>
<logger name="org.springframework" level="WARN"/>
<logger name="org.apache" level="WARN"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
</configuration>
关键配置解读:
| 配置 | 作用 |
|---|---|
%X{tid} |
从MDC中读取tid键(SkyWalking注入的TraceID) |
LogstashEncoder |
输出JSON格式日志,Logstash无需Grok解析 |
AsyncAppender |
异步输出日志,避免日志I/O阻塞业务线程 |
neverBlock=true |
队列满时丢弃日志而非阻塞业务线程 |
discardingThreshold=0 |
不丢弃任何日志 |
依赖引入:
xml
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.0</version>
</dependency>
4.2 TraceID注入机制
第27课中,SkyWalking Agent会自动将TraceID注入MDC的tid键。但如果没有使用SkyWalking,可以通过MDC + Filter手动实现:
java
@Component
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 从请求头中获取上游传递的TraceID,没有则生成新的
String traceId = httpRequest.getHeader("X-Trace-Id");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
try {
MDC.put("tid", traceId);
// 将TraceID放入响应头,便于前端排查
((HttpServletResponse) response).setHeader("X-Trace-Id", traceId);
chain.doFilter(request, response);
} finally {
MDC.remove("tid"); // 必须清理,避免线程池复用导致串号
}
}
}
Feign透传TraceID (第14课的RequestInterceptor):
java
@Component
public class TraceIdFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String traceId = MDC.get("tid");
if (traceId != null) {
template.header("X-Trace-Id", traceId);
}
}
}
踩坑提示 :MDC.remove("tid")必须在finally中执行。Tomcat的线程池会复用线程,如果上一个请求的tid没有被清理,下一个请求的日志中会出现错误的tid------这就是"日志串号"问题。
4.3 异步线程中的TraceID传递
MDC基于ThreadLocal,异步线程无法访问父线程的MDC。需要在任务提交时捕获MDC快照,在任务执行时恢复:
java
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 捕获父线程的MDC
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// 在子线程中恢复MDC
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
java
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setTaskDecorator(new MdcTaskDecorator()); // 关键:注入MDC装饰器
executor.initialize();
return executor;
}
}
五、Kibana日志检索实战
5.1 创建索引模式
首次进入Kibana(http://localhost:5601),需要创建索引模式:
- Stack Management → Index Patterns → Create index pattern
- 输入
spring-logs-*,选择@timestamp作为时间字段 - 点击"Create index pattern"
创建后,在Discover页面即可查询日志。
5.2 核心检索语法
按服务筛选:
service: "service-user"
按日志级别筛选:
level: "ERROR"
按TraceID检索完整链路:
traceId: "a1b2c3d4e5f6.1234567890.123456.01"
这是最核心的排查手段 。当SkyWalking中定位到某条慢链路时,复制其TraceID,在Kibana中检索,即可看到该请求在所有服务中的完整日志。
组合查询:
service: "service-order" AND level: "ERROR" AND message: "库存不足"
时间范围筛选:Kibana顶部的时间选择器支持相对时间(如"最近15分钟")和绝对时间(如"2026-09-13 10:00:00 到 10:30:00")。
全文检索:
"Connection refused"
5.3 从SkyWalking跳转到Kibana
SkyWalking UI支持自定义链路追踪的跳转链接。在SkyWalking的UI配置中,可以为TraceID配置Kibana的跳转URL。配置后,在SkyWalking的链路详情页面中,点击TraceID旁的链接即可直接跳转到Kibana的检索页面,自动填入TraceID查询条件。
实现原理 :SkyWalking UI的config.js中配置traceIdLink模板:
javascript
traceIdLink: 'http://kibana:5601/app/discover#/?_a=(query:(query_string:(query:\'traceId:"{traceId}"\')))'
5.4 日志可视化仪表盘
在Kibana中创建日志分析仪表盘,包含以下面板:
| 面板 | 查询 | 可视化类型 |
|---|---|---|
| 日志量趋势 | Count by @timestamp |
折线图 |
| 日志级别分布 | Count by level |
饼图 |
| 各服务ERROR数量 | level:ERROR by service |
柱状图 |
| Top 10异常类型 | level:ERROR by exceptionType |
数据表格 |
| 慢请求日志 | cost > 1000 |
数据表格 |
六、线上问题排查体系
6.1 标准排查流程
结合Prometheus、SkyWalking和ELK,线上问题的标准排查流程如下:
告警触发(Prometheus Alertmanager)
│ 收到"service-order P95延迟超过3秒"告警
▼
① 确认影响范围(Grafana)
│ 查看仪表盘,确认是单个服务还是全局问题
│ 判断是QPS突增、错误率上升还是延迟升高
▼
② 定位问题服务(SkyWalking拓扑图)
│ 查看拓扑图,确认是哪个服务首先出现延迟
│ 判断是服务自身问题还是下游依赖问题
▼
③ 定位问题接口(SkyWalking链路)
│ 查看Trace详情,找到耗时最高的Span
│ 记录TraceID
▼
④ 查看详细日志(Kibana)
│ 用TraceID检索该请求的所有日志
│ 查看异常堆栈、SQL语句、请求参数
▼
⑤ 定位根因并修复
│ 根据日志中的异常信息和SQL执行计划定位根因
│ 修复后验证
6.2 真实排查案例
场景 :某天下午,监控告警显示service-order的P95延迟从50ms飙升到3秒。
第一步:确认影响范围。 打开Grafana仪表盘,发现service-order的QPS没有明显变化(约200 QPS),但P95延迟从50ms飙升到3秒。同时,service-order的5xx错误率从0.1%上升到2%。
第二步:定位问题服务。 打开SkyWalking拓扑图,发现service-order → service-user的调用边显示红色(延迟高)。而service-order → service-product的调用边显示正常(绿色)。初步判断问题在service-user。
第三步:定位问题接口。 进入SkyWalking的Trace页面,筛选service-order的慢链路。打开一条耗时3.2秒的链路:
[Gateway] GET /api/order/1 (3.2s)
├── [service-order] OrderController.getOrder (3.1s)
│ ├── [service-user] UserClient.getUserById (3.0s) ← 瓶颈
│ │ └── [service-user] UserController.getUserById (2.95s)
│ │ └── [MySQL] SELECT * FROM user WHERE id=1 (2.9s) ← 慢SQL
第四步:查看详细日志。 复制该链路的TraceID,在Kibana中检索。日志显示:
[service-user] [http-nio-8081-exec-5] INFO c.e.m.u.c.UserController - 查询用户: id=1
[service-user] [http-nio-8081-exec-5] DEBUG c.e.m.u.mapper.UserMapper -
==> Preparing: SELECT * FROM user WHERE id=?
==> Parameters: 1(Long)
<== Total: 1
Time: 2900ms
第五步:定位根因。 SELECT * FROM user WHERE id=1耗时2.9秒,但user表数据量只有10万行,id是主键,理论上应该在毫秒级返回。进一步排查发现,该SQL被一个全表扫描的联表查询阻塞------数据库连接池中有大量慢查询占用连接,导致主键查询排队等待。
根因 :service-user的另一个接口/api/user/list执行了全表扫描的联表查询,占满了数据库连接池。
修复 :为/api/user/list的查询添加索引,并限制返回条数。
复盘 :整个排查过程不到10分钟。如果没有ELK和SkyWalking,这个排查可能需要数小时。核心效率提升来自:SkyWalking的拓扑图快速定位了问题服务,TraceID在Kibana中一键检索到了完整日志,日志中的SQL耗时直接指向了根因。
6.3 日志生产优化
优化一:异步日志。 Logback的AsyncAppender将日志写入操作异步化,业务线程只需将日志放入队列即可返回,避免日志I/O阻塞业务线程。
优化二:日志采样。 对于高频日志(如每个请求都打印的入口日志),可以配置采样率:
java
// 使用计数采样,每100个请求记录1条
if (counter.incrementAndGet() % 100 == 0) {
log.info("请求采样: path={}", path);
}
优化三:日志文件滚动策略。 按天+按大小滚动,避免单个日志文件过大:
xml
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- 单文件最大100MB -->
<maxHistory>30</maxHistory> <!-- 保留30天 -->
<totalSizeCap>5GB</totalSizeCap> <!-- 总大小不超过5GB -->
</rollingPolicy>
优化四:Elasticsearch索引生命周期管理(ILM)。 配置ES的ILM策略,自动清理过期索引:
json
{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_size": "50GB", "max_age": "1d" } } },
"warm": { "min_age": "7d", "actions": { "forcemerge": { "max_num_segments": 1 } } },
"delete": { "min_age": "30d", "actions": { "delete": {} } }
}
}
}
优化五:日志量控制。 生产环境的DEBUG日志会导致日志量暴增。通过/actuator/loggers端点动态调整:
bash
# 临时将 service-user 的日志级别调整为 DEBUG
curl -X POST http://localhost:8081/actuator/loggers/com.example.microservice \
-H "Content-Type: application/json" \
-d '{"configuredLevel": "DEBUG"}'
# 排查结束后恢复为 INFO
curl -X POST http://localhost:8081/actuator/loggers/com.example.microservice \
-H "Content-Type: application/json" \
-d '{"configuredLevel": "INFO"}'
七、踩坑指南
坑一:异步Appender丢失日志
现象:应用关闭时,最后几秒的日志丢失。
原因 :AsyncAppender的队列中还有未消费的日志,应用关闭时队列被直接丢弃。
解决 :配置logback-spring.xml的关闭钩子:
xml
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook">
<delay>5 seconds</delay>
</shutdownHook>
坑二:MDC线程池串号
现象:日志中的TraceID与请求不匹配,出现"串号"。
原因:Tomcat线程池复用线程时,上一个请求的MDC未清理。
解决 :在Filter的finally中执行MDC.clear()(如第4.2节所示)。
坑三:多行日志被拆分为多条
现象:Kibana中异常堆栈被拆成多条独立日志。
原因 :Filebeat未配置multiline,每行作为独立日志发送。
解决 :配置Filebeat的multiline.pattern匹配日志起始格式,将堆栈合并到上一条日志。
坑四:Kibana中字段无法识别
现象 :日志中的traceId字段在Kibana中显示为traceId.keyword,无法直接检索。
原因:Logstash未正确解析日志格式,或索引模式未刷新字段映射。
解决 :使用LogstashEncoder输出JSON格式日志,Logstash无需Grok解析;在Kibana中刷新索引模式的字段列表。
坑五:日志文件过大导致磁盘写满
现象:服务器磁盘被日志文件占满,应用无法写入。
原因:未配置日志滚动策略,或滚动策略配置错误。
解决 :配置SizeAndTimeBasedRollingPolicy,设置maxFileSize、maxHistory和totalSizeCap。
坑六:SkyWalking的TraceID与日志中的TraceID不一致
现象:SkyWalking链路详情中的TraceID与Kibana日志中的TraceID不同。
原因 :SkyWalking的TraceID存储在sw8头中,MDC中的tid可能是其他追踪系统注入的(如Micrometer Tracing)。
解决:确保只使用一种追踪系统。如果使用SkyWalking,关闭Micrometer Tracing的TraceID注入;反之亦然。
八、阶段总结与下节预告
至此,微服务监控、链路追踪、日志体系阶段(第25-28课) 全部完成。回顾这一阶段的核心内容:
| 课程 | 核心能力 | 关键产出 |
|---|---|---|
| 第25课 | Actuator端点体系 | 健康检查、探针、自定义端点、安全配置 |
| 第26课 | Prometheus+Grafana | 指标采集、可视化大盘、告警规则 |
| 第27课 | SkyWalking链路追踪 | 调用链路分析、拓扑图、性能瓶颈定位 |
| 第28课 | ELK日志体系 | 日志收集、TraceID检索、问题排查体系 |
四课串联的核心逻辑:Actuator暴露指标和健康状态(第25课)→ Prometheus采集指标并告警(第26课)→ SkyWalking追踪请求链路(第27课)→ ELK集中日志并基于TraceID联动排查(第28课)。指标、链路、日志三大支柱结合,构成完整的可观测性闭环。
第29课将进入微服务高阶特性 & 分布式核心能力阶段 ------微服务异步调用、线程池优化 & 事件驱动开发 。内容包括@Async异步调用、线程池自定义配置、线程池参数优化、异步异常处理、Spring事件驱动和业务解耦实战。可观测性解决了"如何发现问题",异步调用和事件驱动将解决"如何提升性能和解耦"------两者结合,让微服务在高并发场景下既能快速响应,又能保持稳定。
🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航
第一部分:微服务前置基础 & 新版环境搭建(第1-5课)
第二部分:注册中心核心(Nacos 最新版)(第6-9课)
第三部分:配置中心核心(Nacos配置中心)(第10-12课)
第四部分:服务通信核心(OpenFeign + LoadBalancer)(第13-16课)
第五部分:网关核心(SpringCloud Gateway 新版)(第17-20课)
第六部分:熔断、限流、降级(Sentinel 新版)(第21-24课)
第七部分:微服务监控、链路追踪、日志体系(第25-28课)
第八部分:微服务高阶特性 & 分布式核心能力(第29-31课)
第九部分:企业级完整项目实战 & 架构复盘(第32-35课)