DLZ Caller 3 分钟启用指南
本指南只做一件事:让 HttpClient、RedisClient、RPC、MyBatis 等公共组件日志直接显示业务调用处。
完成后,原本这样的日志:
INFO HttpClientUtil - HTTP POST /payments
会变成:
INFO [(OrderService.java:86)] HttpClientUtil - HTTP POST /payments
0. 前提
- Java 8+;
- 应用使用 SLF4J;
- 已有 Logback 或 Log4j2;
1. 引入依赖
<dependency>
<groupId>top.dlzio</groupId>
<artifactId>dlz-kit</artifactId>
<version>6.7.4</version>
</dependency>
2. 在日志格式中显示 caller
没有这一步,caller 已经写入 MDC,但不会显示在日志文本中。
Logback
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{dlz-caller}] %logger - %msg%n</pattern>
</encoder>
Log4j2
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{dlz-caller}] %c - %m%n"/>
dlz-caller 是固定 MDC key(定义在 DlzCaller.MDC_KEY_DLZ_CALLER),不可配置。没有 caller 的普通日志会显示空方括号,不会影响原有功能。
3. 配置公共组件包
在应用启动位置执行一次。这里配置的是“日志发生在这些工具类中,但 caller 应继续向业务层查找”的包。
import com.dlz.caller.DlzCaller;
public final class LogBootstrap {
public static void init() {
DlzCaller.getProperties().addIgnoreCallerPackage("com.example.http"
,"com.example.redis"
,"com.example.rpc"
,"com.example.cache");
}
}
将包配置完整后,任意层数的这些代理和工具类都会被跳过,不需要反复调整堆栈层级。
org.springframework、java、jdk、sun、CGLIB 代理类和 Lambda 代理类已默认排除,不需要手动配置。匹配方式为前缀匹配(startsWith),不需要以 . 结尾。一般只需添加业务自己的公共组件包或类名。注意:com.example.rpc 会同时匹配 com.example.rpcgateway,配置时留意包名前缀的边界。
4. HttpClient 快速植入
在公共 HTTP 工具的最外层入口包一层 caller scope:
import com.dlz.caller.DlzCaller;
import com.dlz.caller.DlzCaller;
public class HttpClientUtil {
public HttpResult execute(HttpRequest request) {
try (MdcContext ignored = DlzCaller.caller(0)) {
log.info("HTTP {} {}", request.getMethod(), request.getUrl());
return httpClient.execute(request);
}
}
}
业务侧不需要任何改动:
public class OrderService {
public void createOrder() {
httpClientUtil.execute(HttpRequest.post("/payments"));
}
}
效果:
INFO [(OrderService.java:86)] HttpClientUtil - HTTP POST /payments
5. RedisClient 快速植入
Redis、缓存、MQ、文件存储等公共组件的写法完全相同:
import com.dlz.caller.DlzCaller;
import com.dlz.caller.DlzCaller;
public class RedisClient {
public String get(String key) {
try (MdcContext ignored = DlzCaller.caller(0)) {
log.debug("redis get key={}", key);
return redisTemplate.opsForValue().get(key);
}
}
}
业务日志效果:
DEBUG [(ProductService.java:51)] RedisClient - redis get key=product:1001
6. 多层代理场景
假设调用链如下:
OrderService
-> PaymentFacade
-> RetryProxy
-> RpcClient
-> HttpClientUtil
只要启动时配置:
DlzCaller.getProperties().addIgnoreCallerPackage("com.example.retry","com.example.rpc","com.example.http");
日志仍会定位到:
(OrderService.java:86)
内部 HttpClient 又调用 RPC、RPC 又调用 Redis 时,内层 scope 不会覆盖已经确定的外层 caller。
7. MyBatis 3 分钟接入
MyBatis 集成位于独立 Artifact。除 dlz-caller 外,再引入:
<dependency>
<groupId>top.dlzio</groupId>
<artifactId>dlz-caller-mybatis</artifactId>
<version>6.7.4</version>
</dependency>
7.1 开启 SQL logger
Logback:
<logger name="dlz-sql" level="DEBUG"/>
7.2 原生 MyBatis XML
将插件放在分页、租户等 SQL 重写插件之后:
<plugins>
<plugin interceptor="com.dlz.caller.mybatis.DlzMybatisSqlLogInterceptor">
<property name="enabled" value="true"/>
<property name="showCaller" value="true"/>
<property name="showMapper" value="true"/>
<property name="injectCallerMdc" value="true"/>
<property name="ignoreCallerPackages"
value="com.example.persistence,com.example.infrastructure"/>
</plugin>
</plugins>
执行:
orderMapper.findById(7L);
输出:
(OrderService.java:86) OrderMapper.findById 12ms => select id, name from orders where id = 7
其中 12ms 是应用侧 SQL 总耗时,包含 JDBC、网络 IO、数据库处理、结果读取和 MyBatis 映射,
不等于数据库服务端纯执行时间。
7.3 Spring Boot / MyBatis-Plus
将属性和拦截器注册为 Spring Bean。@ConfigurationProperties 会将
application.yml(或 application.properties)中的配置自动绑定到 DlzSqlLogProperties,
再由 Spring 注入拦截器。
import com.dlz.caller.mybatis.DlzMybatisSqlLogInterceptor;
import com.dlz.caller.mybatis.DlzSqlLogProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DlzMybatisSqlLogConfiguration {
@Bean
@ConfigurationProperties(prefix = "dlz.caller.mybatis.sql-log")
public DlzSqlLogProperties dlzSqlLogProperties() {
return new DlzSqlLogProperties();
}
@Bean
public DlzMybatisSqlLogInterceptor dlzMybatisSqlLogInterceptor(
DlzSqlLogProperties properties) {
return new DlzMybatisSqlLogInterceptor(properties);
}
}
application.yml 示例:
dlz:
caller:
mybatis:
sql-log:
enabled: true
show-caller: true
show-mapper: true
inject-caller-mdc: true
ignore-caller-packages:
- com.example.persistence
- com.example.infrastructure
8. 启用后检查清单
- 日志 pattern 中有
%X{dlz-caller}。 - 公共工具类入口已使用
DlzCaller.caller()。 - 公共组件所在包已加入
ignoreCallerPackages。 - MyBatis 项目已启用
sqllogger 的DEBUG。 - MyBatis 插件位于其他 SQL 重写插件之后。
- 线程池和异步任务使用项目已有的 MDC 传播方案。
9. 常见问题
日志中 caller 为空
检查日志是否发生在 caller scope 内;检查 %X{dlz-caller} 是否配置;检查 injectCallerMdc 是否为 true。
caller 仍然显示 HttpClientUtil 或 RedisClient
将该工具类所在包加入忽略列表:
DlzCaller.getProperties().addIgnoreCallerPackage("com.example.http");
caller 显示成代理类
将代理所在包加入忽略列表。不要只依赖固定层级 n,包过滤对代理层数变化更稳定。
异步日志没有 caller
caller 基于 ThreadLocal MDC。线程池、CompletableFuture、Reactor 等异步切换需要自行传播 MDC。
SQL 没有输出
确认 logger 名称是 dlz-sql,级别为 DEBUG;确认 MyBatis 插件已经注册;确认插件在 SQL 重写插件之后。
SQL 中包含敏感参数
可执行 SQL 用于诊断。生产环境请控制 sql logger 开关、日志权限和保留期限;当前版本不自动脱敏。