Spring Boot 项目里打印 SQL 日志这件事,说大不大,说小不小。我见过不少同事在 IDEA 控制台里翻来翻去找不到一条 SQL 输出,最后发现是 MyBatis 的 log-impl 没配,也有人把日志框架从 Logback 切到 Log4j2 之后,SQL 日志莫名消失,排查半天才意识到是 logger name 的包路径写错了。这篇文章就把 Spring Boot 项目里打印 SQL 日志和结果的完整方案讲清楚,覆盖 MyBatis、MyBatis-Plus、JPA/Hibernate 三种主流持久层框架,同时讲透基于配置文件(application.yml)和基于 Logback(logback-spring.xml)两种配置方式的差异与选择,最后附上我实际踩过的坑和排查思路。
适合谁看:正在做 Spring Boot 项目、发现 SQL 日志打不出来或者不知道日志该怎么独立归档的开发者,尤其是刚接触 MyBatis 和 JPA 不久、对日志体系还没完全理清的同学。读完你不仅能照着配,还能明白为什么这样配。
1. 先搞清楚 Spring Boot 的日志体系,再谈 SQL 日志
这一步很多人会跳过,直接去搜"Spring Boot 打印 SQL 日志"然后抄一段配置,结果换了个框架就失效了。根本原因在于,不同持久层框架对日志的处理机制完全不一样。
1.1 SLF4J 门面与 Spring Boot 默认的 Logback
Spring Boot 的默认日志实现是 Logback,但项目代码里通常直接用的是 SLF4J 的 API。你可以把 SLF4J 理解成一个插座规范,Logback、Log4j2、java.util.logging 都是不同的插头,只要插头符合规范,插座就能用。Spring Boot 的 spring-boot-starter-logging 已经帮你把 Logback 和 SLF4J 绑定好了,所以你在 pom.xml 里看不到 Logback 依赖,代码里照样能打日志,因为 starter 帮你在后台装配了。
理解这个机制对配置 SQL 日志非常重要。因为你配置 SQL 日志时,本质上有两条路:
- 走应用程序的日志框架(Logback/Log4j2),用 logging.level 来控制 SQL 日志的级别。
- 不走日志框架,直接通过持久层框架自身的 stdout 输出到控制台。
这两条路的配置入口完全不同,而且很多教程把它们混在一起讲,导致新手抄配置时很容易张冠李戴。
举个例子,MyBatis 的 StdOutImpl 是直接 System.out 输出,根本不经过 Logback,所以你在 logging.level 里怎么调级别都没用。而 Slf4jImpl 是把日志交给了 SLF4J,这时才受 logging.level 管控。这就是为什么同样的"打印 SQL 日志"需求,配置方式却千差万别。
1.2 日志级别是 SQL 日志输出的总开关
日志级别从低到高是 trace、debug、info、warn、error。如果不理解级别,你可能会遇到一个非常常见的困惑:明明代码里什么都正常,为什么 SQL 日志就是不出来?
原因很简单。Spring Boot 默认的 root 日志级别是 info,而很多持久层框架的 SQL 日志默认是 debug 级别(比如 Hibernate 的 org.hibernate.SQL 就是 debug)。debug 低于 info,不会输出。所以你必须在 application.yml 里把对应 logger 的级别降到 debug 或更低,SQL 日志才会浮现。
这是 SQL 日志问题的核心开关。你后面所有配置,本质上都是在做一件事:把特定 logger 的级别调到能输出 SQL 的程度,并指定输出的目的地。
另外注意一点,项目里如果有比较多的 debug 日志,直接调 root 的级别到 debug 会造成日志暴涨,所以实践上永远只针对 mapper 包或者框架的 SQL logger 单独调级别,不要全局放开。
1.3 持久层框架不同,日志行为天差地别
MyBatis、MyBatis-Plus、JPA/Hibernate 三者的日志机制差异非常大,我用一张表总结一下,后面每个框架再细讲:
| 持久层框架 | SQL 日志走日志框架? | 关键 logger/配置项 | 默认级别 | 结果集日志 |
|---|---|---|---|---|
| MyBatis | 可选,通过 log-impl 指定 | mapper 接口包路径 | debug | 支持,输出行数 |
| MyBatis-Plus | 同 MyBatis | mapper 接口包路径 | debug | 支持 |
| JPA/Hibernate | 走日志框架 | org.hibernate.SQL | debug | 通过 trace 级输出参数 |
| JPA/Hibernate | 不走日志框架 | spring.jpa.show-sql | info(stdout) | 不支持 |
这张表记住一个核心结论:MyBatis 系要看 log-impl 和包路径;JPA/Hibernate 系要看 spring.jpa.show-sql 和 org.hibernate.SQL 的级别。理解了这一点,后面就是照着配置的事了。
2. MyBatis 系 SQL 日志配置实操
MyBatis 和 MyBatis-Plus 是 Java 后端用得最多的持久层框架,SQL 日志配置也几乎是同一个套路。我这里直接给出最实用的配置组合。
2.1 方式一:走 Logback,用 logging.level 控制 mapper 包
这种方法是我最推荐的,因为日志会统一进入 Logback,后续不管是想输出到文件、按天归档、还是接日志采集系统,全都顺理成章。
首先确保 application.yml 中有这样一段配置:
logging: level: com.example.project.mapper: debug这里 com.example.project.mapper 是你自己的 Mapper 接口所在的包路径,不是 XML 文件所在路径。很多刚接触的同学把 resource 下的 mapper XML 目录路径填进来,结果怎么配都不生效,就是这个原因。
同时,MyBatis 需要指定日志实现为 Slf4jImpl,这样它才会走日志框架而不是直接 stdout:
mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl如果你是 MyBatis-Plus,配置几乎一样,只是前缀改成 mybatis-plus:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl然后重启项目,执行一条 SQL,你就会在控制台看到类似这样的输出:
==> Preparing: SELECT id, name, age FROM user WHERE id = ? ==> Parameters: 1(Long) <== Columns: id, name, age <== Row: 1, 张三, 25 <== Total: 1能看到 Preparing(预编译 SQL)和 Parameters(绑定参数)就说明配置成功了。注意,这时即使你的 root 日志级别是 warn,mapper 包下的 SQL 日志也照样输出,因为 logging.level 下的包路径优先级高于 root。
2.2 方式二:用 StdOutImpl 快速验证
如果不关心日志归档,只是想在本地调试时快速看到 SQL,可以直接用 MyBatis 的 StdOutImpl:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这个实现会把 SQL 日志直接打印到标准输出,IDEA 控制台里显示效果完全一样。好处是零学习成本、配置一行搞定;坏处是日志不经过 Logback,你无法用 logging.level 控制、无法归档、也无法接入日志采集系统。
我在开发环境用过一段时间的 StdOutImpl,后来切到生产环境做日志采集时被迫全部改回 Slf4jImpl。坦白说,如果从一开始就用 Slf4jImpl,后面不用返工。
注意:StdOutImpl 与 logging.level 互不干扰。即使你设了 logging.level.com.example.project.mapper=debug,也没办法让 StdOutImpl 的输出受控,因为它根本不走 SLF4J。
2.3 方式三:把 SQL 日志单独输出到文件
这个需求比较常见:排查线上问题时不希望 SQL 日志跟其他业务日志混在一起,而是单独放一个 sql.log,方便 grep。这时必须配合 Logback 配置。
先看 application.yml 部分的写法:
logging: level: com.example.project.mapper: debug然后在 resources 下创建 logback-spring.xml,核心配置如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 单独的文件 appender --> <appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/sql.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/sql.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 只针对 mapper 包 --> <logger name="com.example.project.mapper" level="DEBUG" additivity="false"> <appender-ref ref="SQL_FILE"/> </logger> </configuration>这里有个关键细节:additivity="false"。如果你不写这一句,SQL 日志不仅会写入 sql.log,还会继续向上传播到 root logger,然后在控制台打一份、在其他业务日志文件里再打一份。很多同学配置后日志重复,就是栽在这个属性上。
这个方案在生产环境非常实用。我把 mapper 包下的日志单独落盘后,运维同学查问题只需要看 sql.log 一个文件,效率提升明显。daily 滚动保留 30 天,磁盘压力可控。
3. JPA/Hibernate 的 SQL 日志配置套路
用 Spring Data JPA 的同学经常会遇到另一种困惑:spring.jpa.show-sql=true 配置了,SQL 确实能看到,但日志里没有参数值,全是问号占位符,排查起来很难受。这其实是 Hibernate 的日志机制决定的。
3.1 show-sql 只是权宜之计,真正可控的是日志级别
先看最简单的配置方式:
spring: jpa: show-sql: true properties: hibernate.format_sql: trueshow-sql=true 会把 SQL 输出到控制台,format_sql=true 会把 SQL 格式化换行,看起来更美观。但问题有两个:
- 输出走的是 System.out,不是 Logback,不受日志级别控制,也无法归档。
- SQL 里的参数值不会显示,你只能看到类似 select u.* from user u where u.id=? 的占位符。
所以 show-sql 更适合本地开发快速验证,生产环境不推荐。
真正可控的方式是走日志框架:
logging: level: org.hibernate.SQL: debug这时 SQL 会通过 Logback 输出,并且受日志框架管控,可以归档、可以采集。但注意,参数值仍然不会显示在 SQL 那一行里,Hibernate 把参数绑定和结果集处理放在了更细的级别。
3.2 在日志里看到参数值:org.hibernate.type.descriptor.sql
如果你不光要看 SQL,还要看每个参数实际传进来的值,需要额外开启 trace 级别:
logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql: trace这样配置后,每次 SQL 执行,会在日志里输出一行绑定参数的信息,类似:
bind => [1, 张三]我实际排查一个分页查询的分页参数传错问题时,就是因为 trace 级别下可以看到每个参数的实际类型和值,很快就定位到是 Pageable 的页码参数传了 0 导致查出的数据不符合预期。这在 debug 级别下根本看不出来。
不过这里有个性能层面的考虑:trace 级别的日志输出对高并发接口有一定开销,生产环境不建议长期开启。我的做法是:本地开发开 trace,生产只开 debug,遇到疑难问题时临时把某个接口的 logger 级别动态调成 trace,排查完立刻降回来。
3.3 JPA 配置对照表
把 JPA 的几种配置方式和效果整理成一张表,方便你按需选择:
| 配置方式 | 输出位置 | 是否显示参数 | 是否可归档 | 适合场景 |
|---|---|---|---|---|
| spring.jpa.show-sql=true | 标准输出 | 否 | 否 | 本地开发快速看 SQL |
| logging.level.org.hibernate.SQL=debug | 日志框架 | 否 | 是 | 生产环境常规监控 |
| 再加 org.hibernate.type.descriptor.sql=trace | 日志框架 | 是 | 是 | 排查参数类问题 |
| 配合 format_sql=true | 日志框架 | 视上表 | 是 | 需要阅读复杂 SQL |
4. Logback 配置文件实战:从基础到进阶
讲完了持久层框架侧,接下来重点说 Logback 本身。很多项目在运行一段时间后,日志管理需求会从"能打印"升级到"能控制、能归档、能分流",这时 logback-spring.xml 就是绕不开的环节。
4.1 logback-spring.xml 与 logback.xml 的区别
Spring Boot 官方推荐使用 logback-spring.xml,而不是 logback.xml。原因在于 logback-spring.xml 支持 Spring Boot 的 profile 特性,比如:
<springProfile name="dev"> <!-- 开发环境专用配置 --> </springProfile> <springProfile name="prod"> <!-- 生产环境专用配置 --> </springProfile>这样同一份配置文件可以针对不同环境输出不同格式、不同文件目录。例如开发环境 SQL 日志直接输出到控制台,生产环境 SQL 日志输出到独立文件并做滚动归档。如果你用 logback.xml,就没有这个能力,需要维护多份配置文件。
另外还需要注意,配置文件的命名位置不能错。Spring Boot 默认会从 classpath 下加载 logback-spring.xml,所以要放在 resources 目录下。如果你用的是 yml 配置项 logging.config,可以指定自定义路径,但一般没必要改。
4.2 通过 logger 精确控制持久层日志
Logback 的 logger 节点是控制日志行为的核心。可以把它理解成一个筛选规则:name 属性指定包名或类名,level 属性指定日志级别,appender-ref 指定输出到哪里。
可以这样配置:
<logger name="com.example.project.mapper" level="DEBUG"/>这个配置意味着 com.example.project.mapper 包下的所有 debug 级别及以上日志都会被收集,然后向上传递给 root logger 进行输出(控制台、文件等)。
如果你不想让 MyBatis 的内部日志轰炸你的文件,还可以单独把 MyBatis 框架自身的日志级别调高:
<logger name="org.apache.ibatis" level="INFO"/>这在排错时很实用。MyBatis 在 debug 级别下会输出大量内部状态日志,信息量很大但也很吵,调成 INFO 后清爽很多,而我们真正关注的业务 mapper 包日志不受影响。
4.3 多环境配置推荐模板
我做项目时常用的模板是:dev 环境 SQL 日志走控制台,prod 环境 SQL 日志走独立文件,同时保留必要的文件滚动策略。贴合 Spring Boot profile 的写法推荐参考这段:
<configuration> <!-- 控制台 appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> </appender> <!-- SQL 专用文件 appender --> <appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/sql.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/sql.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> <totalSizeCap>2GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <logger name="com.example.project.mapper" level="DEBUG" additivity="false"> <springProfile name="prod"> <appender-ref ref="SQL_FILE"/> </springProfile> <springProfile name="dev"> <appender-ref ref="CONSOLE"/> </springProfile> </logger> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>这里有几个细节需要说明:
- totalSizeCap 控制所有归档日志的总大小上限,避免日志无限膨胀撑爆磁盘。2GB 是我按业务量拍的一个合理值,你可以根据实际情况调整。
- maxHistory 是保留天数,30 天对于线上排障来说够用。
- 如果 additivity 保持默认 true,SQL 日志会在 SQL_FILE 输出之外,再往 root 的 CONSOLE 打一份,这不一定是你想要的,注意按需求决定。
4.4 异步输出 SQL 日志的取舍
高并发系统里,同步写文件日志会占用接口响应时间,所以 Logback 提供了 AsyncAppender 来实现异步日志。原理是先把日志事件放进一个阻塞队列,后台线程负责真正写文件,主线程无需等待磁盘 IO 完成。
如果你的系统并发较高且希望 SQL 日志不影响主流程性能,可以这样配置:
<appender name="ASYNC_SQL" class="ch.qos.logback.classic.AsyncAppender"> <queueSize>2048</queueSize> <discardingThreshold>0</discardingThreshold> <appender-ref ref="SQL_FILE"/> </appender>注意 discardingThreshold 这个参数,官方默认值是队列容量的 20%,意思是当队列剩余容量低于这个比例时,会直接丢弃 INFO 及以下级别的日志事件来保护系统。但 SQL 日志本身是我们排查问题的依据,直接丢弃会让问题无处可查。所以建议把 discardingThreshold 设为 0,也就是队列满时阻塞等待而不是丢弃。
同时 queueSize 要根据 QPS 估算。一个简单的估算方法:假设每秒执行 500 条 SQL,每条日志平均 200 字节,那么每秒产生 100KB 日志,2048 的队列容量足够缓冲几秒的积压,其实已经够用了。如果 QPS 高一个量级,可以调到 8192,但要注意内存占用会相应增加。
我实际项目中用 AsyncAppender 配合独立 SQL 文件,在日均百万级 SQL 执行的系统上跑过,性能影响可以忽略,日志一条不少。不过这里也提醒一句:文件 appender 的磁盘 IO 能力是系统整体瓶颈,异步只是把压力后移,磁盘本身还是要监控的。
5. 慢 SQL 日志:从打印到监控的一步之遥
SQL 日志打出来之后,很多同学的下一步诉求是:能不能自动把执行时间超过阈值的 SQL 标记出来?这就是慢 SQL 日志。借助 Logback 或者 MyBatis 的拦截器机制,这个需求并不复杂。
5.1 基于 Logback 的耗时输出
Logback 的 pattern 里有一个 %r 或 %relative 参数,可以输出从应用启动到当前日志事件的时间差,单位毫秒。不过这个时间差跟单条 SQL 执行耗时没有直接关系,所以用它监控慢 SQL 并不靠谱。
更实用的方案是结合 MyBatis 拦截器,拦截 Executor 层的 query 和 update 方法,计算执行耗时。核心代码可以参考这段:
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class SlowSqlInterceptor implements Interceptor { private static final Logger log = LoggerFactory.getLogger(SlowSqlInterceptor.class); private long slowSqlMillis = 3000; @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); try { return invocation.proceed(); } finally { long cost = System.currentTimeMillis() - start; if (cost > slowSqlMillis) { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; BoundSql boundSql = ms.getBoundSql(); log.warn("慢 SQL 执行耗时: {} ms, SQL: {}", cost, formatSql(boundSql.getSql())); } } } private String formatSql(String sql) { return sql.replaceAll("\\s+", " "); } }然后在 MyBatis 配置里注册这个拦截器。Spring Boot 场景下,如果用的是 MyBatis-Plus,直接在配置类里声明一个 bean 即可;用的是原生 MyBatis,可以通过 org.apache.ibatis.session.Configuration 的 addInterceptor 注册。
这里有几个执行上的细节需要注意:
- Optional 判断要放在 try 之外,否则慢 SQL 判断本身也会被算进耗时。
- 反射获取 BoundSql 时还需要处理参数,实际项目中可以借助 ms.getConfiguration().newMetaObject 等方式获取完整的可执行 SQL,这里给的是简化版。
- slowSqlMillis 阈值可以通过配置类注入,不要在代码里写死。生产环境一般建议先设 1 秒,观察一个周期后再调整。
5.2 慢 SQL 日志与独立文件结合
拦截器打出 warn 级别日志后,再利用 Logback 的 logger 配置把它引导到独立文件,这样慢 SQL 和普通 SQL 分开,定位问题的效率更高。
<logger name="com.example.project.interceptor.SlowSqlInterceptor" level="WARN" additivity="false"> <appender-ref ref="SLOW_SQL_FILE"/> </logger>这本质上是 Logback 的精确路由能力。把拦截器类当成普通 logger 来定向输出,跟前面把 mapper 包输出到独立文件是同一个套路,只是粒度细到了类级别。
我实际做过一次比较极端的应用:把慢 SQL 日志接到一个单独的告警文件,配合脚本每分钟检测文件是否有新增内容,一旦发现就触发告警。虽然不如专业 APM 工具强大,但在小团队、预算有限的情况下,确实能用最低成本解决慢 SQL 的监控问题。
6. 常见问题排查与避坑指南
配置方式都讲完了,最后总结我这些年工作中遇到的高频问题。这些问题在官方文档里很难直接搜到答案,属于典型的"踩过才知道"系列。
6.1 问题速查表
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| 配置 logging.level 后 SQL 日志不出现 | MyBatis 还在用 StdOutImpl 或其他非 Slf4j 实现 | 将 log-impl 改为 Slf4jImpl |
| 改了 mapper 包路径配置没反应 | 包路径写错,或写成了 XML 文件路径 | 确认包路径是 Mapper 接口包,不是 resource 下的 XML 路径 |
| SQL 日志在控制台打印两份 | Logback 里没有设置 additivity="false" | 为对应 logger 设置 additivity="false" |
| SQL 日志出现在业务日志文件,但控制台没有 | appender 配置指向了文件,没有指向 CONSOLE | 检查 logger 的 appender-ref 配置,按需增加 CONSOLE |
| 日志里只有 Preparing,没有 Parameters | 级别开到了 debug 但缺少参数绑定输出 | 检查 MyBatis log-impl 是否生效,或考虑换框架级别的日志输出 |
| JPA 的 SQL 有占位符没有参数值 | 只开了 org.hibernate.SQL debug | 额外开启 org.hibernate.type.descriptor.sql trace |
| 配置了 logback-spring.xml 但完全不生效 | 文件名或路径不对 | 确认文件在 classpath 下且名称为 logback-spring.xml |
| profile 相关配置不生效 | 配置文件是 logback.xml 而不是 logback-spring.xml | 重命名为 logback-spring.xml |
6.2 配置不生效的三个常见原因
第一个原因是依赖冲突。如果你的项目里手动引入了 Log4j2 的依赖,同时又不小心排除了 spring-boot-starter-logging,那么 Logback 的配置很可能完全不生效。检查方法很简单:在 IDE 的依赖树里搜 ch.qos.logback,如果没有这个依赖,说明 Logback 已经不在运行时环境里了。
第二个原因是包路径大小写问题。MyBatis 的 mapper 接口包路径必须和实际代码完全一致,不能凭印象写一个看起来差不多的路径。我见过有人把 com.example.mapper 写成了 com.example.Mapper,排查了很久。
第三个原因是配置被 profile 覆盖。Spring Boot 支持多份配置文件,比如 application-dev.yml 和 application-prod.yml。如果你在 application.yml 里配了 logging.level.mapper=debug,但 application-dev.yml 里又把 root 级别设为 error,实际运行时的效果取决于 profile 激活情况。要养成习惯:所有日志级别配置集中在同一份主配置文件中,或者明确知道 profile 之间的覆盖规则。
6.3 日志打印重复问题的分析与解决
日志重复是最高频的坑,而且成因不止一种。除了前面说的 additivity 问题,还有一种隐蔽的场景:项目里不光有 logback-spring.xml,还保留了 logback.xml,两份配置文件同时存在时 Spring Boot 会优先加载 logback.xml,导致你的 logback-spring.xml 完全失效。
排查日志重复或日志配置不生效时,第一步不是看代码,而是看启动日志里有没有这一行:
Logging system: Logback如果启动日志里显示的日志实现不是 Logback,说明你的依赖体系出了问题。如果是 Logback,再看加载的配置文件路径,Spring Boot 会在启动时打印类似Logging configuration: classpath:logback-spring.xml的信息。这两行信息能定位 80% 的问题。
还有一个容易忽略的点:某些中间件会自带日志配置。比如 Dubbo、Spring Cloud Gateway 等框架可能在依赖里带了自己的 logback 配置文件,通过 Maven 的 dependency 管理把项目里的配置文件覆盖掉。遇到这种情况,可以通过排除依赖或者指定 logging.config 来强制指定配置路径。
6.4 参数结果日志的隐私与安全提醒
SQL 日志里会打印参数值和查询结果,包括用户手机号、身份证、姓名等敏感信息。开发环境打出来没问题,但生产环境日志文件如果被未授权人员拿到,就是严重的数据泄露事件。
建议至少做到两点:
- 生产环境只输出 SQL 语句,不输出参数值和结果集。MyBatis 系可以使用日志级别控制,不开启 trace 或调整 log-impl;JPA/Hibernate 系则不开 trace。
- 如果业务确实需要完整 SQL 参数日志(比如金融支付类系统的对账需求),必须对日志文件做权限管控,日志采集链路要做脱敏处理。
这里有一个安全细节:MyBatis 的 StdOutImpl 输出结果集时,日志里会显示完整的结果行。相比 Slf4jImpl 只输出 Preparing 和 Parameters,StdOutImpl 的敏感信息暴露面更大。从这个角度讲,我也更推荐生产环境使用 Slf4jImpl 而不是 StdOutImpl。
6.5 动态调日志级别的实用技巧
最后分享一个小技巧:配置文件的日志级别是静态的,但排查线上问题时往往需要临时调高某个包的日志级别,又不想重启应用。Spring Boot 提供了 actuator 的 loggers 端点,可以动态修改。
依赖里加上 spring-boot-starter-actuator,然后通过 POST 请求即可:
curl -X POST http://localhost:8080/actuator/loggers/com.example.project.mapper \ -H "Content-Type: application/json" \ -d '{"configuredLevel":"DEBUG"}'这个操作在生产环境非常实用。比如某个 SQL 接口突然变慢,你临时把 mapper 包级别调到 debug,查看日志后马上调回 info,全程不用重启,对在线业务零影响。
需要注意的是,这个端点在生产环境暴露时有安全风险,必须配合 Spring Security 做权限控制,或者至少限制内网访问。
写在最后
Spring Boot 打印 SQL 日志这件事,配置本身不难,难点在于搞清楚不同持久层框架的日志机制差异,以及怎么让日志输出体系跟业务监控需求匹配。我个人的经验是,不管用什么框架,生产环境永远优先走 Logback 体系,而不是依赖框架自身的 stdout 输出。统一的日志链路,后续做文件归档、日志采集、慢 SQL 监控,才能游刃有余。
最后再分享一个自己的习惯:每次新项目启动,先花两分钟确认日志配置文件被正确加载、mapper 包路径正确、SQL 日志能正常输出,再开始写业务代码。这个习惯帮我避免了无数次"项目写完了才发现日志体系有问题"的返工。你的项目如果现在还在为 SQL 日志发愁,照着上面的配置走一遍,应该五分钟之内就能看到自己想要的输出。