代码语言

知识点思维导图

29 个知识节点

Java(32) - 全链路问题排查实战

读完后,你应能完成以下任务:

  • 绘制“Java(32) - 全链路问题排查实战 / 为什么这是最实用的收尾课”的关键对象与数据流,解释“写代码只占工作的一半,另一半是"出问题了怎么办"。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Java(32) - 全链路问题排查实战 / 先建立"五站"心智模型”设计正常与异常输入,验证“先用"二分法"快速缩小范围——这和前端排查"是接口返回错了还是渲染逻辑错了"是一个思路。”,输出首个偏差位置与回归测试结果。
  • 实现“Java(32) - 全链路问题排查实战 / 核心武器:traceId 链路追踪”的最小代码或配置,检验“demo 用的是 OpenTelemetry(一套链路追踪标准)来生成和传递 traceId。”,输出命令、结果与 Diff,并说明不适用边界。

前 31 课教你怎么"写对",这一课教你怎么"查错"——线上出问题时,怎么从前端一行红色报错,一路追到数据库里那条脏数据。这是你转全栈后最值钱的硬技能。

一、为什么这是最实用的收尾课

写代码只占工作的一半,另一半是"出问题了怎么办"。前端时代你靠 Chrome DevTools 的 Network 面板就能定位大部分问题:看哪个请求红了、看 Response、看 Status Code。

但全栈不一样。一个请求从用户点击按钮开始,要穿过网关 → 多个微服务 → MySQL/Redis,任何一站都可能出错。DevTools 只能看到第一站(浏览器到网关),剩下的全是黑盒。

【前端类比】

DevTools Network 面板 全链路排查
看单个请求的 Status / Response 看请求穿过了几个服务、在哪一站挂的
看 Request Headers / Payload 看网关日志、各服务日志
看 Timing 瀑布图(哪段慢) 看 traceId 串起来的链路耗时
范围:浏览器 ↔ 服务器(1 跳) 范围:浏览器 ↔ 网关 ↔ N 个服务 ↔ DB(N 跳)

可以把全链路排查理解成 "DevTools Network 面板的服务端延伸版"——你需要一套工具,把后端那些看不见的"跳"也变得可见。这套工具的核心就是 日志 + traceId


二、先建立"五站"心智模型

回顾第 04 课讲的 HTTP 请求生命周期,demo 的一个请求会经过这五站:

 ┌──────────┐   ┌─────────┐   ┌────────────┐   ┌──────────┐   ┌────────┐
 │ 前端 Web  │──▶│  网关    │──▶│ Controller │──▶│ Service  │──▶│ MySQL  │
 │ (浏览器)  │   │(gateway)│   │  (入口)     │   │ (业务)    │   │ (数据)  │
 └──────────┘   └─────────┘   └────────────┘   └──────────┘   └────────┘
   第①站          第②站          第③站            第④站         第⑤站

排查的第一原则:先定位"在哪一站挂的",再深入那一站查"为什么挂"

千万不要一上来就扎进某个 Service 的代码里读半天,结果发现请求根本没到那个服务。先用"二分法"快速缩小范围——这和前端排查"是接口返回错了还是渲染逻辑错了"是一个思路。

每一站对应的排查入口:

站点 排查入口 前端类比
① 前端 浏览器 DevTools Network / Console 你已经很熟了
② 网关 网关访问日志(access log) nginx 日志
③ Controller 应用日志里的"请求日志" 接口入参打印
④ Service 应用日志里的业务日志、异常栈 console.log / 报错堆栈
⑤ MySQL 慢查询日志、SQL 执行日志、直接查库 看数据库里的真实数据

三、核心武器:traceId 链路追踪

3.1 traceId 是什么

一个请求进来,demo 会给它分配一个全局唯一的 ID,叫 traceId(也叫 trace_id)。这个 ID 像一根线,把请求经过的所有服务、打印的所有日志全串起来。

【前端类比】这就像你在 Network 面板里给某个请求加了个唯一标记,然后无论它在后端转发给多少个服务,每个服务的日志都会带上这个标记。你只要 grep 这个 traceId,就能把散落在 5 个服务里的几十行日志,按时间顺序拼成一条完整链路。

demo 用的是 OpenTelemetry(一套链路追踪标准)来生成和传递 traceId。来看真实工具类 TracerIdUtils.java

// 文件:demo-common/demo-common-core/.../utils/TracerIdUtils.java
public final class TracerIdUtils {

    /** TRACE_ID 在 MDC 里的 key,日志框架会从 MDC 取这个值打到每行日志上 */
    public static final String TRACE_ID_MDC_KEY = "trace_id";

    /**
     * 获取当前请求的 traceId
     * @return 当前链路的 traceId;取不到时返回空串(绝不抛异常,避免影响主流程)
     */
    public static String traceId() {
        try {
            // 分支1:开启了 OpenTelemetry 且 trace 有效,直接从 Span 上下文取——这是最准的来源
            if (openTelemetryTraceEnabled()) {
                return Span.current().getSpanContext().getTraceId();
            }
            // 分支2:没有 OpenTelemetry,退而求其次从日志 MDC 里取
            return Optional.ofNullable(MDC.get(TRACE_ID_MDC_KEY))
                    .orElseGet(() -> MDC.get("traceId"));
        } catch (Exception e) {
            // 取 traceId 失败绝不能影响业务,所以兜底返回空串
            return "";
        }
    }
}

这里有个关键概念:MDC(Mapped Diagnostic Context)。

【前端类比】MDC 类似 React 的 Context 或者一个"请求级别的全局变量袋子"。请求一进来,框架就把 traceId 塞进 MDC,整个请求处理期间,任何地方打日志,日志框架都能自动从这个袋子里把 traceId 取出来贴到日志行首。你不需要手动在每行 log.info 里传 traceId。

3.2 traceId 怎么出现在日志里

看 demo-basic 示例的日志配置 logback.xml,注意那个 %X{...}

<!-- 文件:demo-basic/demo-basic-biz/src/main/resources/logback.xml -->

<!-- 控制台/文件日志格式:%X{traceId} 就是从 MDC 里取 traceId 贴到每行 -->
<property name="PATTERN"
    value="%d{yyyy-MM-dd HH:mm:ss.SSS} [TraceId: %X{traceId} , SpanId: %X{spanId}] [%-5p] [%thread] %logger{36} | %msg%n"/>

<!-- 线上走 ELK,输出 JSON,trace_id 作为独立字段,方便 Kibana 检索 -->
<appender name="ELK" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
        <providers>
            <pattern>
                <pattern>
                    {
                    "time":"%d{yyyy-MM-dd HH:mm:ss.SSS}",
                    "app_name": "${app.name}",
                    "level": "%level",
                    "trace_id": "%mdc{trace_id}",   <!-- 关键:每条日志都带 trace_id -->
                    "message": "%message",
                    "stack_trace": "%exception"
                    }
                </pattern>
            </pattern>
        </providers>
    </encoder>
</appender>

划重点两个语法:

  • %X{traceId} —— logback 从 MDC 里取名为 traceId 的值(%X 就是"取 MDC")。
  • %mdc{trace_id} —— JSON 编码器里取 MDC 的写法,效果一样。

所以本地开发时,你的控制台日志长这样:

2026-06-11 10:23:01.123 [TraceId: a1b2c3d4e5f6 , SpanId: 7788] [INFO] [http-nio-8080-exec-3] c.c.c.b.OrganizationController | 查询公司 id=10086
2026-06-11 10:23:01.156 [TraceId: a1b2c3d4e5f6 , SpanId: 7799] [WARN] [http-nio-8080-exec-3] c.c.c.b.OrganizationService    | 公司 id=10086 不存在

看到没?两行日志虽然来自不同的类,但 TraceId 一样(a1b2c3d4e5f6)。这就是那根"线"。


四、看日志:grep 与 Kibana

4.1 本地 / 登服务器:grep 三板斧

登服务器后,日志通常落在一个统一的根目录下(demo 的 logback 里用 logger.root 属性指定,默认 /data/applogs,再按服务名分子目录)。最常用的就是 grep

提示:上面那段 logback 配的是 ConsoleAppender(日志先打到标准输出),容器/部署平台再把标准输出重定向落盘到 logger.root 目录。所以你 grep 的文件路径,本质就是这个目录下的服务日志。

【前端类比】grep 之于服务端日志,约等于 DevTools Console 里的过滤框(那个 Filter 输入框)。你在 Console 输入关键词过滤日志,在服务器上就是用 grep。

# 1. 按 traceId 捞出一整条链路的所有日志(最常用,排查第一步)
grep "a1b2c3d4e5f6" /data/applogs/demo-basic/*.log

# 2. 捞所有 ERROR 级别日志,看最近有没有异常
grep "ERROR" /data/applogs/demo-basic/app.log | tail -50

# 3. 捞某个接口路径的请求,-A 5 表示连带显示匹配行后面 5 行(看异常栈很有用)
grep -A 5 "/organization/getById" /data/applogs/demo-basic/app.log

# 4. 实时跟踪日志,边操作边看(约等于 DevTools Network 开着不关)
tail -f /data/applogs/demo-basic/app.log | grep "10086"

常用 grep 参数速记表:

参数 作用 类比
-i 忽略大小写 DevTools 过滤的大小写不敏感
-A 5 / -B 5 显示匹配行后/前 5 行 看上下文
-C 5 前后各 5 行
-c 只统计匹配条数 "有多少个红色请求"
-r 递归搜目录 全局搜索
tail -f 实时跟踪文件新增 实时监控

4.2 线上:Kibana(ELK)

线上几十台机器,不可能挨个登上去 grep。demo 线上日志统一输出 JSON(看上面那个 ELK appender),汇集到 ELK(Elasticsearch + Logstash + Kibana),通过 Kibana 这个 Web 界面检索。

【前端类比】Kibana ≈ 一个"全集群版的 DevTools Network 面板 + Console 过滤"。它把所有机器、所有服务的日志聚到一处,你在浏览器里用查询语句过滤,再也不用 ssh 登机器。

Kibana 里最常用的查询(KQL 语法,类似搜索框):

# 按 traceId 查整条链路(跨所有服务!这是 Kibana 最强的地方)
trace_id: "a1b2c3d4e5f6"

# 查某个服务最近的 ERROR
app_name: "demo-basic" and level: "ERROR"

# 组合:某服务、某时间段、含某关键词
app_name: "demo-order-cost" and message: "库存不足"

注意第一条——trace_id 是独立字段(还记得 logback 里那个 "trace_id": "%mdc{trace_id}" 吗?正因为它是结构化字段,Kibana 才能精确按它检索)。一个 traceId 就能把请求在 5 个服务里的所有日志全捞出来,这是 grep 单机日志做不到的。

排查线上问题的黄金流程:

前端报错 → 拿到 traceId → Kibana 搜 trace_id → 看链路在哪一站断 → 定位服务 → 看该服务异常栈

怎么拿到 traceId?让前端把它打到 Console,或者后端在错误响应的 header / body 里带上。demo 的网关和响应通常会回带 traceId,前端同学 F12 一看就有。


五、常见问题的排查思路

下面按你最常遇到的四类问题,给出"看到什么现象 → 大概率在哪一站 → 怎么查"的套路。

5.1 404 Not Found

【现象】DevTools Network 里请求标红,Status 404

【最可能的站】第②站网关 或 第③站 Controller——请求根本没找到对应的处理方法

【排查思路】

404 多半是"路由没匹配上",而不是业务错误。重点查:
  1. 前端请求的 URL 路径对不对?有没有拼错、少了前缀(如 /api)
  2. 网关有没有配这个服务的转发规则?(网关日志看请求有没有进来、转去了哪)
  3. 后端 Controller 的 @RequestMapping 路径和前端对得上吗?
  4. HTTP Method 对吗?前端用 GET,后端只写了 @PostMapping
     —— 注意:路径找到了但方法不对,标准 Spring 返回的是 405(Method Not Allowed),不是 404;
        而在 demo 里它会被下面的全局异常处理器接住,转成 HTTP 200 + code=1(详见下方代码)

【前端类比】就像 Vue Router 配了 /user/:id 但你跳转到 /users/123(多了个 s),匹配不上直接 404。后端路由同理。

demo 里 Method 不匹配会被全局异常处理器接住——注意它不是返回裸的 405/404,而是统一转成 HTTP 200 + code=1(请求参数错误),看匿名化示例代码:

// 文件:demo-common/demo-common-web/.../advice/CommonWebExceptionHandler.java
// 请求方法不支持(比如该用 POST 你用了 GET)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<CommonCodeResponse> handler(HttpRequestMethodNotSupportedException e) {
    // 这行 warn 日志就是你排查此类问题的线索
    log.warn("请求 HttpMethod 错误: {}", e.getMessage());
    // 注意:HTTP 状态码是 200,错误信息放在响应体的 code/message 里(demo 统一风格)
    return builderResponseEntity(HttpStatus.OK)
            .body(CommonCodeResponse.response(CommonErrorCodeEum.BAD_REQUEST_PARAM.getErrno(), "请求HttpMethod错误"));
}

demo 的约定:HTTP 状态码基本恒为 200,真正的错误码在响应体的 code 字段code=0 才是成功)。所以排查 demo 接口别只盯着 DevTools 里的 Status,要看 Response body 里的 codemessage。真正出现裸 404 的场景,通常是请求压根没进到应用(网关没转发、路径前缀错),这时要去查第②站网关。

所以 grep 日志时搜 请求 HttpMethod 错误缺少请求参数,能快速确认是不是参数/方法对不上。

5.2 服务端内部错误("500 类")

【现象】两种形态:

  • 标准 Spring 应用:HTTP Status 500
  • demo 风格:HTTP Status 仍是 200,但响应体 code=2message="未知异常"——因为 demo 把未捕获异常统一兜底转成了 200(见下方代码)。看到"未知异常"四个字,就等价于别处的 500。

【最可能的站】第④站 Service——业务代码抛了未捕获的异常(空指针、SQL 报错、调用下游服务失败等)。

【排查思路】

500 = 服务端代码炸了。核心是找到那条"异常栈(stack trace)":
  1. 拿到 traceId,去日志里搜
  2. 找带 "未知异常" 或 ERROR 级别、带堆栈的那几行
  3. 看异常栈最顶上的 "Caused by",那才是根因
  4. 顺着栈里的类名、行号,定位到具体哪行代码

demo 的全局异常处理器对"未知异常"会打 ERROR 日志并告警,看匿名化示例代码:

// 文件:demo-common/demo-common-web/.../advice/CommonWebExceptionHandler.java
// 兜底处理所有没被专门 catch 的异常 = 别处的 500(demo 里转成 200 + code=2)
@ExceptionHandler(Exception.class)
public ResponseEntity<CommonCodeResponse> handler(HttpServletRequest request, Exception e) {
    // 关键:ERROR 级别 + 完整异常栈,这就是你排查"未知异常"要找的那行
    log.error("未知异常: ", e);
    // 项目可注入自定义处理器接管(没注入时走下面默认逻辑)
    if (unknownExceptionHandler != null) {
        return unknownExceptionHandler.handlerException(request, e);
    }
    // 触发告警,把"接口 + 异常类型 + message"推到报警群
    alarmSender.sendAlarm("接口异常", buildExceptionAlarmMsg(request, e));
    // 返回 HTTP 200,错误码 code=2、message="未知异常"放在响应体
    return builderResponseEntity(HttpStatus.OK)
            .body(CommonCodeResponse.response(CommonErrorCodeEum.UNKNOWN_EXCEPTION));
}

【实战技巧】搜 未知异常 关键词 + 你的 traceId,立刻定位。然后看下面那一大段堆栈:

ERROR ... 未知异常:
java.lang.NullPointerException: Cannot invoke "Organization.getName()" because "organization" is null
    at c.c.c.b.service.OrganizationService.format(OrganizationService.java:88)   <-- 看这行!第88行
    at c.c.c.b.controller.OrganizationController.getById(OrganizationController.java:42)
    ...

【前端类比】这和你在浏览器里看 JS 报错堆栈一模一样——Cannot read properties of null 就是 JS 版的 NPE,你也是顺着堆栈点进出错的那一行。Java 的栈从上往下读,最上面那行 at ...:88 就是案发现场。

区分一下:demo 里业务异常BusinessException,第 07 课讲过)是预期内的错误(如"余额不足"),打的是 WARN 日志、返回友好提示,不算 500;只有没预料到的异常才走上面的 Exception.class 兜底,打 ERROR + 告警。排查时优先看 ERROR。

5.3 接口超时 / 很慢

【现象】DevTools 里请求转圈很久,最后超时;或 Timing 显示某请求耗时几秒甚至几十秒。

【最可能的站】第⑤站 MySQL(慢 SQL)最常见,其次是第④站调用下游服务慢。

【排查思路】

慢的本质是"某一段耗时长",要先定位是哪一段慢:
  1. 看链路日志里相邻两行的时间戳差值(traceId 串起来后一目了然)
     —— 哪两行之间隔了好几秒,瓶颈就在那段
  2. 慢 SQL:看 MySQL 慢查询日志,或日志里打印的 SQL 执行耗时
     常见原因:没走索引、全表扫描、一次查太多数据、N+1 查询
  3. 调用下游慢:看是不是某个 Feign / HTTP 调用卡住(下游服务挂了或慢)
  4. 锁等待:数据库行锁/表锁互相等

【前端类比】这就是 DevTools Network Timing 瀑布图的服务端版。前端你看 Waiting(TTFB) 那段长不长来判断是不是后端慢;现在你深入后端,用 traceId 把"瀑布图"画到 SQL 这一级,看到底哪一段在等。

【看时间差的实战】

10:23:01.100 [TraceId: xxx] OrganizationController | 开始查询
10:23:01.105 [TraceId: xxx] OrganizationService    | 调用 mapper 查询公司列表
10:23:08.880 [TraceId: xxx] OrganizationService    | 查询完成,返回 5000 条   <-- 这里!隔了7.7秒
10:23:08.885 [TraceId: xxx] OrganizationController | 返回结果

一眼看出第 2、3 行之间花了 7.7 秒——SQL 慢,而且一次返回 5000 条,多半是没分页或没走索引。

5.4 数据不对(最隐蔽)

【现象】接口 Status 200,不报错,但返回的数据是错的——少了字段、值不对、该有的记录没了。

【最可能的站】可能在任何一站,最难查,因为没有报错给你指路。

【排查思路】

数据不对 = 逻辑错,没有异常栈兜底,全靠"对比"和"分层验证"。用二分法:
  1. 先确认"数据库里到底是什么"——直接查库(眼见为实,排除一切代码因素)
     如果库里数据本身就是错的 → 问题在"写入"链路,不是"读取"链路
     如果库里数据是对的 → 问题在"读取/组装/序列化"环节
  2. 再确认"Service 算出来是什么"——加日志打印关键中间变量
  3. 再确认"Controller 返回前是什么"——看请求响应日志(demo 有 ReqRespLogFilter 自动打印出入参)
  4. 最后对比"前端收到的是什么"——DevTools Response
  逐层定位"数据从哪一层开始变错的"

demo 有个自动打印请求出入参的过滤器 ReqRespLogFilter,排查"数据不对"时它打印的响应体就是宝藏——你能看到后端到底返回了什么,从而判断是后端给错了还是前端渲染错了:

// 文件:demo-common/.../web/filter/ReqRespLogFilter.java
@Slf4j
@Order(-90)   // Order 很小 = 很靠前执行,能包住整个请求,完整记录出入参
public class ReqRespLogFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) {
        long reqTime = System.currentTimeMillis();   // 记录请求开始时间,用于算耗时
        // ... 包装 request/response 以便能重复读取 body ...
        logReq(request, logProperties);               // 打印入参
        filterChain.doFilter(request, response);       // 放行,执行真正的业务
        logResp(response, logProperties, reqTime);     // 打印出参 + 耗时
    }
}

【前端类比】"数据不对"就像你 Vue 页面渲染出来的列表少了一列。你的排查顺序也是从源头往回查:先看接口 Response(数据本身对不对)→ 再看 computed/transform 逻辑 → 再看模板绑定。后端只是把这条链路延长到了 Service 和数据库。

直接查库是排查"数据不对"的核武器。但生产库查询要走规范流程(只读权限、避免锁表),不要在生产库上跑未经确认的写操作或大范围扫描。


六、把套路串成一张排查决策图

                        线上报问题 / 收到告警
                               │
                               ▼
                     ┌───────────────────┐
                     │ 拿到 traceId       │  ← 让前端给,或从告警/响应里拿
                     └─────────┬─────────┘
                               ▼
                  Kibana 搜 trace_id: "xxx"
                               │
            ┌──────────────────┼──────────────────┐
            ▼                  ▼                   ▼
      日志里有 ERROR      日志里相邻行         200 但数据错
      + 异常栈            时间差很大            / 没异常
            │                  │                   │
            ▼                  ▼                   ▼
        看堆栈 Caused by    定位慢的那一段       直接查库 + 分层加日志
        定位到具体行        (SQL? 下游?)          逐层对比找出变错点
            │                  │                   │
            ▼                  ▼                   ▼
        改空指针/边界       优化SQL/加索引        修业务逻辑
                            /扩容下游

记住排查的两条铁律:

  1. 先定位站点,再深入细节——用二分法快速缩小范围,别一头扎进代码。
  2. traceId 是你的生命线——任何排查的第一步都是拿到它、用它串起全链路。

七、本课小结

  • 全链路排查 ≈ DevTools Network 面板的服务端延伸版:前端只能看到"浏览器↔网关"1 跳,全栈要看清"网关→多服务→DB"的 N 跳。
  • 五站心智模型:前端①→网关②→Controller③→Service④→MySQL⑤。排查第一原则是"先定位在哪一站挂,再深入查为什么"。
  • traceId 是核心武器:demo 用 OpenTelemetry 生成全局唯一 ID,通过 MDC(≈请求级全局变量袋子)自动贴到每行日志(%X{traceId}),一个 traceId 串起跨服务的所有日志。
  • 看日志两种方式:本地/单机用 grep(≈ Console 过滤框),常用 -A/-B/-Ctail -f;线上用 Kibana(≈ 全集群版 Network 面板),按 trace_id 字段一键捞全链路。
  • 四类常见问题套路
    • 404:路由没匹配上(URL/网关规则/Method),查请求有没有进来、路径对不对。
    • 500:服务端抛未捕获异常,搜"未知异常"+ERROR,看异常栈最顶的 Caused by 定位到行。
    • 超时/慢:看链路相邻日志的时间差找瓶颈段,重点查慢 SQL(没索引/没分页)和下游调用。
    • 数据不对:最隐蔽,用二分法分层验证——先直接查库(眼见为实),再逐层往上对比,借 ReqRespLogFilter 打印的出入参定位。
  • 引用的 demo 匿名化示例代码TracerIdUtils(traceId 获取)、logback.xml(日志格式与 ELK 输出)、CommonWebExceptionHandler(404/500 异常处理与日志)、ReqRespLogFilter(自动打印出入参)。

下一课(第 33 课)我们进入性能优化实战:当排查发现"慢 SQL"后,怎么真正优化它——索引怎么加、N+1 怎么解、缓存怎么用,把"查出问题"升级成"解决问题"。

八、总结

  • 为什么这是最实用的收尾课:写代码只占工作的一半,另一半是"出问题了怎么办"。
  • 先建立"五站"心智模型:先用"二分法"快速缩小范围——这和前端排查"是接口返回错了还是渲染逻辑错了"是一个思路。
  • 核心武器:traceId 链路追踪:demo 用的是 OpenTelemetry(一套链路追踪标准)来生成和传递 traceId。
  • 看日志:grep 与 Kibana:登服务器后,日志通常落在一个统一的根目录下(demo 的 logback 里用 logger.root 属性指定,默认 /data/applogs,再按服务名分子目录)。
  • 常见问题的排查思路:demo 里 Method 不匹配会被全局异常处理器接住——注意它不是返回裸的 405/404,而是统一转成 HTTP 200 + code=1(请求参数错误),看匿名化示例代码:
  • 把套路串成一张排查决策图:先定位站点,再深入细节——用二分法快速缩小范围,别一头扎进代码。 -> traceId 是你的生命线——任何排查的第一步都是拿到它、用它串起全链路。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“全链路问题排查实战”中,需要同时满足“为什么这是最实用的收尾课”与“先建立"五站"心智模型”。给定正文约束“DevTools 只能看到第一站(浏览器到网关),剩下的全是黑盒。”,哪些判断保持了原有处理机制?多选
2“全链路问题排查实战”出现偏差:“在“全链路问题排查实战 / traceId 是什么”中,即使不满足“你不需要手动在每行 log.info 里传 traceId”,结果与副作用仍会保持不变。”已成为实际行为。围绕“traceId 是什么”与“traceId 怎么出现在日志里”,哪些判断能定位被改变的职责或边界?多选
3评审“全链路问题排查实战”方案时,验收条件包含“所以你 grep 的文件路径,本质就是这个目录下的服务日志。”。关于“本地 / 登服务器:grep 三板斧”与“线上:Kibana(ELK)”的哪些决策符合正文机制?多选