想让 AI 改 Bug 快准狠?先给日志加个业务代码坐标

想让 AI 改 Bug 快准狠?先给日志加个业务代码坐标

把日志贴给 AI,再补一句:"帮我看看哪里有问题。"

如果 AI 能读项目代码,你大概还会期待它继续往下做:找到调用位置,分析原因,给出修改,补上验证。

但有时候,它连第一步都需要绕一圈。

SQL 找到了,Mapper 找到了,调用这个 Mapper 的 Service 也找到了好几个。这次究竟走的是哪一个?

人查日志时缺少的线索,交给 AI 之后,依然缺。

所以,我给公共组件日志补了一项信息:本次调用对应的业务代码位置。

例如:

text 复制代码
[a3Kd9xQ] (CouponService.java:118)
OrderMapper.findById 12ms => select id, status from orders where id = 7

AI 可以先从 CouponService.java 第 118 行附近读起,再沿相关逻辑继续分析。

想让它改 Bug 更快、更准,可以先把"该从哪里看起"说明白。

下文用简化的代码和日志说明排查过程,未报告 AI 工具实测结果或 Token 节省比例。

1. 查找引用,找出的是所有可能;你要查的是这一次

先看一个具体场景:一次下单请求里,相同的订单查询出现了两遍。

text 复制代码
[a3Kd9xQ] OrderMapper.findById => select id, status from orders where id = 7
[a3Kd9xQ] OrderMapper.findById => select id, status from orders where id = 7

你的问题是:"第二次查询是不是多余?能不能复用第一次的结果?"

AI 能从日志里确认发生了两次查询。但要提出可靠修改,它还需要知道两次查询分别来自哪里。

搜索调用关系后,可能会发现:

调用者 查询订单的用途
OrderCheckService 校验订单状态
CouponService 计算优惠金额
PaymentService 检查支付条件

现在有了候选列表,还需要结合入口、条件分支和其他日志,确认本次请求实际执行了哪些位置。

如果日志直接带着 caller,信息会变成:

text 复制代码
[a3Kd9xQ] (OrderCheckService.java:52) OrderMapper.findById => select id, status from orders where id = 7
[a3Kd9xQ] (CouponService.java:118)    OrderMapper.findById => select id, status from orders where id = 7

排查起点就明确了:先看订单校验和优惠券处理这两个位置。

查找引用告诉 AI 哪些代码可能调用;caller 给出了这一次调用对应的代码位置。

原来需要搜索、阅读和推断才能补齐的一部分信息,现在直接出现在日志里。

这也解释了为什么一个行号有用:它能让后续代码检索围绕已发生的调用展开。

2. "快准狠",具体快在哪里、准在哪里?

我希望 AI 辅助排查时做到三件事:

目标 对应的排查动作
快 尽快找到值得先读的业务代码,减少候选位置之间的搜索
准 把本次运行的日志与对应调用点联系起来,有证据地分析
狠 把修改收敛到明确的问题上,用可验证的小范围改动解决它

caller 直接帮助前两步。能否给出正确修改,还取决于业务上下文和验证结果。

例如,看到两次相同 SQL,可以确认重复执行了查询,却不能立刻认定第二次应该删除。

AI 还需要检查:

  • 两次读取之间,订单状态是否可能变化?
  • 后一次业务是否需要读取最新状态?
  • 两个调用的事务边界是什么?
  • 复用查询结果后,是否改变了原有行为?

有了代码坐标,这些问题有了更明确的检查入口。它们仍然需要回答。

尤其是"没有抛异常,但行为不符合预期"的问题:重复查询、重复调用外部接口、某个分支读取了错误的缓存键。这时没有异常堆栈可以直接带路,日志里的业务位置就更有帮助。

如果异常栈已经明确指到相关业务代码,或者 AI 已经掌握了调用上下文,增加 caller 的收益会小一些。

3. traceId 找请求,caller 找业务调用点

日志里需要的两类信息,可以放在一起理解:

字段 提供的信息 AI 可以据此做什么
traceId 相关日志属于哪次请求或链路 筛选同一次请求的上下文
caller 本次调用按规则定位到哪个业务位置 定向查找并阅读源码
Mapper 或组件方法 执行了哪个数据访问或公共操作 确认操作入口
SQL、参数、耗时 这次操作执行了什么、表现如何 提出待验证的原因假设

这些信息各有作用。MDC 可以同时承载 traceId 和 caller,日志模板负责把它们输出。

项目已经有 traceId 体系时,可以继续沿用,补上 caller 即可。前提是请求上下文已经正确传播,否则光有一个字段名无法串起日志。

我希望交给 AI 的上下文,既有运行时发生的事,也有返回源码的入口。

4. 为什么普通日志里的类名、行号还不够?

假设所有 HTTP 请求都经过一个公共工具类。

日志所在的位置可能是:

text 复制代码
HttpClientUtil.java:57

它能帮你找到输出日志的代码。但是,几十个业务方法都可能调用这个工具类。

这一次你更关心的,可能是:

text 复制代码
OrderService.java:86

两者分别指向日志输出位置和业务调用位置。

Logback 已有 %class、%line,也支持通过 %caller 输出多层调用栈。要进一步选出业务位置,需要明确哪些框架、代理和公共组件栈帧应该跳过。

dlz-caller 用到的主要思路,就是按规则过滤调用栈。简化为伪代码:

java 复制代码
for (StackTraceElement frame : stack) {
    if (isInfrastructure(frame)) {
        continue;
    }
    return toLocation(frame);
}

从栈顶向外扫描,跳过配置中的基础设施帧,返回第一个符合定位规则的位置。

这比固定取"第 4 层"更适合封装层数会变化的项目。多一层重试、多一个代理时,需要依靠明确的过滤规则选择位置。

不过,过滤规则决定了结果落在哪一层。

例如业务 Service 调用 Facade,Facade 再调用公共 HTTP 组件。只过滤公共组件时,得到的可能是 Facade 中的调用点;把 Facade 也过滤掉,才会继续向外找。

因此要先确定期望的业务边界,不能承诺所有项目都会自动定位到最外层 Service。

对于前面的重复查询场景,还要检查嵌套作用域策略:如果两次查询都沿用同一个外层 caller,日志就可能只显示共同入口。每次采集是否能保留期望的调用位置,需要用实际输出确认。

5. MyBatis 先接上,看看多出来的坐标有没有用

以 6.7.5 版本为例,引入 MyBatis 集成 starter:

xml 复制代码
<dependency>
    <groupId>top.dlzio</groupId>
    <artifactId>dlz-caller-mybatis-spring-boot-starter</artifactId>
    <version>6.7.5</version>
</dependency>

开启对应 Logger:

yaml 复制代码
logging:
  level:
    dlz-sql: DEBUG

执行:

java 复制代码
orderMapper.findById(7L);

日志消息体的示例效果:

text 复制代码
(OrderService.java:86) OrderMapper.findById 12ms => select id, status from orders where id = 7

日期、级别、traceId 等前缀仍由项目的日志模板控制;输出 traceId 时,使用项目对应的 MDC key。

这条消息把业务坐标、Mapper 方法、调用耗时和带参数的 SQL 放在一起。消息体已经包含 caller,统一模板时避免重复输出即可。

这里有两个会影响诊断的细节:

耗时要看计时范围。 示例中的 12ms 是应用侧观测值,不能直接等同于数据库服务端的纯执行时间,也不表示它一定是慢 SQL。

带参 SQL 是诊断文本。 参数填充需要识别引号和注释,避免把字符串里的 ? 当占位符。遇到特殊 JDBC 类型、自定义 TypeHandler 或方言时,仍要结合实际参数绑定行为分析。

如果还接入了分页、租户、数据权限等 SQL 重写插件,用一条真实查询确认日志包含预期条件。采集时机、拦截方法和插件组合都会影响结果。

6. HTTP、Redis 这些公共入口,也可以补上位置

手动接入时,把 caller 作用域放到公共组件入口。下面省略 import 和依赖注入:

java 复制代码
// 位于 com.example.infra.stock 包
public class StockClient {

    public void deduct(Long skuId, int count) {
        try (MdcContext ignored = DlzCaller.caller()) {
            log.info("库存扣减 sku={} count={}", skuId, count);
            // 原有的库存服务调用逻辑
        }
    }
}

启动时配置需要跳过的公共组件包:

java 复制代码
DlzCaller.getProperties()
    .addIgnoreCallerPackage("com.example.infra");

日志 pattern 增加 MDC 字段:

text 复制代码
%level [%X{traceId}] [%X{dlz-caller}] %logger{0} - %msg%n

业务侧仍然正常调用:

java 复制代码
stockClient.deduct(10086L, 2);

假设业务调用位于 OrderService.java 第 42 行,输出可以是:

text 复制代码
INFO [a3Kd9xQ] [(OrderService.java:42)] StockClient - 库存扣减 sku=10086 count=2

MdcContext 管理作用域内的 MDC 状态,退出时恢复原值或清理,避免后续日志误带上之前的 caller。

这样,新业务调用已经接入的公共入口时,就不需要再额外传递一个来源参数。需要统一维护的是公共组件入口和定位规则。

异步场景则要额外处理上下文传播。工作线程里的调用栈无法还原提交任务前的那段同步栈;想保留提交位置,需要在线程切换前捕获,并在任务执行时正确设置、恢复或清理上下文。

7. 给 AI 的材料,也可以一起整理好

caller 提供位置,问题描述提供排查目标。两者都清楚,代码检索才更容易围绕问题展开。

可以用下面这样的模板交给能访问源码的 AI:

yaml 复制代码
问题: 同一下单请求重复查询订单,判断是否可以安全复用查询结果
源码版本: "<与运行实例对应的提交号或版本>"
traceId: a3Kd9xQ

日志: |
  <粘贴相关脱敏日志,保留 caller、Mapper、耗时和 SQL>

分析要求:
  - 先读取 caller 对应的业务方法,再补充必要的调用上下文
  - 区分日志已经证实的事实和仍需验证的推测
  - 检查事务边界,以及后一次读取最新状态的必要性
  - 如需修改,给出最小改动和验证方法

源码版本尤其重要。日志指向的是运行中那份代码的行号,本地已经改过文件时,同一行可能不再对应同一个调用。

如果项目里存在重名文件,还可以考虑给日志补充完整类名、方法名或源码路径。这是面向 AI 消费日志时值得增强的元数据,不代表本文示例已经自动提供了这些字段。

8. 更省 Token,值得期待,也值得测量

业务坐标能让代码检索更有针对性。是否因此减少 Token,以及减少多少,取决于项目规模、原始日志、模型已经掌握的上下文和工具检索方式。

一个 Mapper 只有一处调用时,普通搜索可能已经足够快。公共方法被大量复用、封装较深、运行路径又不明显时,caller 更可能发挥作用。

要验证收益,可以固定问题、源码和模型设置,在独立上下文中分别提供普通日志和带 caller 的日志,多次对照观察:

指标 用来判断什么
搜索次数、读取文件数 是否减少了为确认调用点所做的检索
输入与输出 Token 用量 总体上下文消耗是否减少
定位和修复结果 是否找对问题,修改是否通过必要验证
完成时间 整体排查过程是否缩短

不能只看读文件次数变少。只读一个文件就改错,代价更高。

组件自身同样有运行成本:抓栈、过滤、SQL 格式化和日志输出都需要资源。应按需要启用并在实际负载下评估;SQL 参数也要按项目要求脱敏,尤其在提交给 AI 之前。

让 AI 改 Bug "快准狠",第一步可以很小:先让它知道,本次运行究竟应该从哪段业务代码看起。

相关代码与文档:

机制参考:Logback 日志位置输出、MDC 上下文、MyBatis 插件链实现。

你让 AI 排查问题时,更常遇到哪一种情况:它还在找相关代码,还是已经找到了代码,却对业务意图理解有偏差?

这条 SQL 到底是谁调的?我给 MyBatis 日志加了个业务坐标

SQL 找到了,参数有了,traceId 也有了。

然后,你还是打开了 IDE,开始查调用关系。

比如,排查一个下单接口时,你发现同一个请求里,相同的查询出现了两次。日志简化后像这样:

text 复制代码
[a3Kd9xQ] OrderMapper.findById => select id, status from orders where id = 7
[a3Kd9xQ] OrderMapper.findById => select id, status from orders where id = 7

是不是重复查询?哪个步骤查过了,后面为什么又查了一次?

你打开 findById,查找引用。订单校验在用,优惠券计算在用,支付处理也在用。中间可能还有一层通用 Service。

查引用能找到"哪些地方可能调用它",但你要确认的是"这一次究竟走了哪些调用点"。

这些信息有时能从上下文推出来,有时还得补日志、重跑请求,或者本地打断点。

如果这两条日志各自带上业务位置,输出就可以是这样:

text 复制代码
[a3Kd9xQ] (OrderCheckService.java:52) OrderMapper.findById => select id, status from orders where id = 7
[a3Kd9xQ] (CouponService.java:118) OrderMapper.findById => select id, status from orders where id = 7

现在,至少有一件事明确了:一次来自订单校验,一次来自优惠券处理。你可以直接打开这两个位置,判断第二次查询是否必要、结果能否复用。

不用先从所有引用里猜出这次走过的路径,排查可以从具体调用点开始。

上面是用于说明问题的示例。这个能力叫 caller,我把它用在了公共组件日志里。本文讲它怎么接入、怎么找对位置,以及在哪些场景里值得用。

1. traceId 帮你找到这次请求,caller 帮你找到这次调用

没有请求标识时,并发请求的日志交错在一起。时间戳和线程名能提供线索,但线程会复用,请求也可能跨线程,光靠它们很难可靠地还原上下文。

把 traceId 放进 MDC,再通过日志 pattern 输出,是常见的处理方式:

text 复制代码
INFO  [a3Kd9xQ] OrderService   - 开始创建订单
DEBUG [a3Kd9xQ] HttpClientUtil - POST /stock/deduct
DEBUG [a3Kd9xQ] RedisClient   - GET product:10086

前提是上下文已经正确传播。同一个 traceId,能帮你把相关日志筛出来。

但 HttpClientUtil 和 RedisClient 都是公共组件。看到它们的名字,仍然不知道本次调用来自哪一行业务代码。

这几个字段提供的信息不同:

日志信息 能回答的问题
traceId 这条日志属于哪次请求或链路?
Logger 名称 这条日志由哪个 Logger 输出?
业务 caller 按定位规则,这次调用来自哪个业务代码位置?

MDC 可以同时承载 traceId 和 caller。前者关联请求,后者补充代码位置。

如果项目已经接入 APM,继续使用现有链路能力即可。单条日志里的业务坐标,可以作为排查时的一个直接入口。

它的收益也有前提:公共组件有多个调用者,而日志本身没有留下足够的来源信息。只有一处调用,或者异常堆栈已经明确指向业务代码时,增加 caller 的帮助就小得多。

它减少的是反查代码的步骤。为什么查了两次、为什么执行慢,仍然需要继续分析。

2. Logback 已经有行号了,为什么还要补 caller?

熟悉 Logback 的同学可能会问:

配上 %class、%line 或 %caller,不就知道谁调用了吗?

这里容易混淆两个位置:

  • 日志输出位置 :哪一行代码调用了 log.info()。
  • 业务调用位置:哪一行业务代码调用了这个公共组件。

假设日志写在 HttpClientUtil 里,输出日志所在的文件和行号,通常会得到:

text 复制代码
HttpClientUtil.java:57

这个位置当然有用。但公共组件被几十个业务方法复用时,你更想看到的可能是:

text 复制代码
OrderService.java:86

Logback 的 %caller 也支持输出多层调用栈。不过,中间隔着多少层封装、代理和框架代码,并不固定。把深度调大,可以看到更多帧;要从里面选出一个稳定的业务位置,还需要过滤规则。

要补上的,是"哪些栈帧算基础设施、在哪一层停下来"这套选择逻辑。

3. 把接入放在公共组件入口,业务调用处不用逐个补日志

最直接的做法,是每个调用方自己写一条:

java 复制代码
log.info("准备扣库存,来源:createOrder");
stockClient.deduct(10086L, 2);

或者,把来源一路传下去:

java 复制代码
stockClient.deduct("OrderService.createOrder", 10086L, 2);

能用,但维护成本落在了每个调用者身上。

尤其麻烦的是漏加:新增一个调用点,没有来源标识,业务照常运行,测试也可能全部通过。直到那条路径出了问题,你才发现关键位置没有留下来。

在同步调用还没结束时,来源其实就在当前调用栈里。

因此,dlz-caller 把接入位置放到了公共组件入口。以库存客户端为例,下面省略 import 和依赖注入,只看关键代码:

java 复制代码
// 位于 com.example.infra.stock 包
public class StockClient {

    public void deduct(Long skuId, int count) {
        try (MdcContext ignored = DlzCaller.caller()) {
            log.info("库存扣减 sku={} count={}", skuId, count);
            // 原有的库存服务调用逻辑
        }
    }
}

业务侧继续正常调用:

java 复制代码
public void createOrder() {
    stockClient.deduct(10086L, 2);
}

启动时配置需要跳过的公共组件包:

java 复制代码
DlzCaller.getProperties()
    .addIgnoreCallerPackage("com.example.infra");

日志 pattern 中增加 caller 对应的 MDC 字段:

text 复制代码
%level [%X{traceId}] [%X{dlz-caller}] %logger{0} - %msg%n

假设上面的业务调用位于 OrderService.java 第 42 行,输出效果就是:

text 复制代码
INFO [a3Kd9xQ] [(OrderService.java:42)] StockClient - 库存扣减 sku=10086 count=2

这里沿用项目已有的 traceId 字段;如果项目使用其他 MDC key,替换成对应名称即可。

接入完成后,新的业务方法只要调用这个已接入的入口,就能得到同样的来源信息,不需要再传一个 caller 参数。

改动集中在公共组件入口,收益覆盖调用它的业务代码。

准确地说,这是"业务调用处无需逐个埋点"。公共组件仍需统一接入;新建了另一套公共组件,也要接上这套机制。

4. 怎么找对位置?关键在过滤规则

获取调用栈并不复杂,容易出错的是"取哪一帧"。

例如,直接取固定的第 N 层:

java 复制代码
StackTraceElement caller =
    Thread.currentThread().getStackTrace()[4];

在简单示例里可能刚好正确。加一层重试封装,或者多经过一个代理,位置就可能变了。

dlz-caller 的主要思路是按配置过滤栈帧。简化成伪代码,就是:

java 复制代码
for (StackTraceElement frame : stack) {
    if (isInfrastructure(frame)) {
        continue;
    }
    return toLocation(frame);
}

从栈顶向外扫描,跳过框架、代理和已配置的公共组件帧,取第一个符合规则的位置。

这里有个重要细节:过滤规则决定了你最终看见哪一层。

假设一次调用经过这些方法:

调用顺序 方法 角色
1 OrderService.createOrder 业务入口
2 StockFacade.deduct 业务封装
3 RetryClient.execute 公共重试组件
4 HttpClientUtil.post 公共 HTTP 组件

如果只跳过公共组件,定位结果可能是 StockFacade。如果 StockFacade 也被配置为需要跳过,才会继续找到 OrderService。

所以,它定位的是过滤后最近的调用位置。把基础设施边界配置清楚,结果才有意义。

不要为了"只看业务代码",直接把整个 com.example 都加入忽略列表------业务代码也在里面。

对于开头的重复查询场景,还要关注捕获时机:每次 Mapper 调用,都需要得到适合这次调用的位置。如果整段业务始终沿用同一个外层 caller,两条 SQL 可能只显示同一个入口,区分内部调用点的能力就会减弱。

因此,过滤规则与嵌套作用域策略要一起看,用真实调用验证最终输出。

另一个细节是 MDC 的生命周期。

公共组件调用结束后,本次 caller 应该退出作用域。否则线程复用时,后续日志可能带上前一次调用的位置。

MdcContext 用 try-with-resources 管理这件事:退出作用域时恢复进入前的状态,原来没有值时则清理。这样,即使中途抛出异常,也能执行作用域的收尾逻辑。

5. MyBatis 场景:把坐标、方法、耗时和 SQL 放到一起

SQL 日志特别适合补 caller:Mapper 告诉你执行了哪个数据访问方法,业务坐标告诉你是谁发起了这次调用。

使用 MyBatis 集成 starter,可以把这部分接入交给插件。下面沿用 6.7.5 版本示例:

xml 复制代码
<dependency>
    <groupId>top.dlzio</groupId>
    <artifactId>dlz-caller-mybatis-spring-boot-starter</artifactId>
    <version>6.7.5</version>
</dependency>

开启对应 Logger:

yaml 复制代码
logging:
  level:
    dlz-sql: DEBUG

执行:

java 复制代码
orderMapper.findById(7L);

日志消息体的效果如下:

text 复制代码
(OrderService.java:86) OrderMapper.findById 12ms => select id, status from orders where id = 7

一次排查常用的几个信息,放在了同一行:

  • 业务坐标:打开哪份源码,从哪一行开始看。
  • Mapper 方法:执行了哪个数据访问方法。
  • 调用耗时:这个应用侧调用花了多长时间。
  • 带参数的 SQL:查询条件究竟是什么。

日期、级别、traceId 等前缀仍由项目的日志模板控制。示例中的消息体已包含 caller,统一模板时注意避免重复输出。

这里的 12ms 只是格式示例,不表示它一定属于慢 SQL;耗时口径也取决于插件的计时范围,不能直接当成数据库服务端的纯执行时间。

填充参数,也不能简单地替换问号

原生 MyBatis 日志通常分别输出预编译 SQL 和参数。排查时把两者放在一起,更方便阅读和复现。

但不能见到 ? 就替换:

sql 复制代码
select id
from orders
where remark = 'why?'
  and id = ? -- 这里的问号也不是参数 ?

这条 SQL 中,需要填充的只有 id = ?。

因此,参数填充要识别引号和注释状态,区分占位符与普通文本。dlz-caller 的 SQL 格式化也做了这类处理。

同时要保留一个边界:日志中的带参 SQL 是用于诊断的文本表示。遇到特殊 JDBC 类型、自定义 TypeHandler、二进制值或数据库方言时,不能保证它与驱动实际绑定参数后的行为完全等价。

对于常见查询,它能减少手工拼参数的步骤;对于复杂类型,仍要结合原始参数和驱动行为判断。

6. 真正接进项目时,别忽略这几个边界

第一,抓栈有成本,不能用"零开销"概括。

调用栈获取、过滤、SQL 格式化和日志 I/O 都有成本。实现上应先判断诊断日志是否需要输出,再做抓栈和格式化。

"关闭对应日志级别时跳过这些工作",与"开启后没有额外成本",是两回事。高频接口要结合实际调用量评估。

第二,跨线程时,要先传播上下文。

Logback 的 MDC 按线程保存上下文。线程池、CompletableFuture 等场景,需要在提交任务时捕获所需上下文,在执行任务时设置,并在结束后恢复或清理。

而且,工作线程里重新抓栈,无法补回提交任务之前的那段调用栈。想保留任务提交位置,就要在切换线程前捕获它。

第三,要确认记录的是 SQL 重写之后的结果。

项目里如果还有分页、租户、数据权限等插件,应检查 SQL 采集的时机。

不能简单地用"某个插件排在最后"概括所有情况。MyBatis 的代理包装顺序、具体拦截方法,以及插件在调用前还是调用后读取 SQL,都会影响结果。

接入时用一条带分页或租户条件的查询,确认日志里出现了实际期望的条件,比只看插件配置顺序更可靠。

第四,参数越完整,越要管理好日志。

按本文版本的使用前提,SQL 参数不会自动脱敏。涉及手机号、证件号、令牌等敏感信息时,需要结合项目要求控制输出内容、访问权限和保留期限。

caller 提供代码入口;业务日志、异常堆栈、执行计划和链路信息,继续提供其他排查证据。

7. 值得先接入哪里?

可以先找一个最常让你"查完日志还要继续找引用"的地方:

  • 被多个 Service 复用的 MyBatis 查询。
  • 统一封装的 HTTP、RPC 客户端。
  • Redis、文件存储等公共访问组件。

验证时,可以让两个不同业务方法调用同一个 Mapper,再检查日志能否区分这两个位置;也可以在已有封装外增加一层重试,看坐标是否仍然指向期望的业务边界。

这样的结果,才说明它确实减少了你项目里的排查步骤。确认一个高频场景有效后,再推广到其他公共入口。

相关基础能力来自 dlz-kit:MdcContext 管理 MDC 作用域,caller 用它承载调用位置。项目已有 traceId 体系时,可以继续沿用,单独补上 caller。

代码和文档:

也可以对照阅读 Logback 的位置输出说明、MDC 上下文说明和 MyBatis 插件链实现。

你排查 SQL、HTTP 或 Redis 日志时,最常卡在哪一步:找不到这次请求的完整上下文,还是找到了日志,却不知道该打开哪一行业务代码?

相关推荐
SL_staff1 小时前
JVS-Rules vs Drools:金融风控团队为何转向业务可编辑的决策平台
java·spring cloud·github
Zhou1411361 小时前
SpringMVC_03_进阶功能
java
合橱瑰1 小时前
踩坑实录:子进程“假 Ready”导致窗口永远无法唤起?
后端·全栈
咖啡八杯1 小时前
常量与枚举设计规范:HttpStatus 自定义 601 警告码
java·架构·代码规范
imDwAaY1 小时前
如何快速定位线上OOM
后端
一帅1 小时前
大象无形:OTel Java Agent 的隐身哲学
后端
ThinkerQAQ_1 小时前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·picasso
dd聊技术1 小时前
给项目接上动态线程池
后端
hsfxuebao1 小时前
常用开源项目github
后端·github