1. 升级踩坑Spring Boot 3.3.4 一换Logback 回滚策略先崩了先说结论这并不是你写的那段 logback-spring.xml 语法有问题而是 Spring Boot 3.3.4 默认引入的 Logback 版本出现了一次不大不小的“破坏性升级”。原本在 1.2.x 里用得顺手的TimeBasedRollingPolicy SizeAndTimeBasedFNATP组合在新版里直接不被识别日志不滚动、启动报错、配置静默失效三种情况我都遇到过。如果你正在做 Spring Boot 升级且日志文件本来按天加大小回滚这篇大概率能帮你少折腾半天。我这次是从 Spring Boot 2.7 一路升到 3.3.4项目里日志配置原本是这样类的appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/app.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxHistory30/maxHistory timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder /appender这段配置在 Spring Boot 2.x 时代是标准写法几乎每个 Java 服务都能见到。启动后每天一个文件单个文件超过 100MB 会自动拆成.1、.2这种序号保留最近 30 天。很常规也没什么难度。结果升级到 3.3.4 之后启动日志开始出现类似这样的报错java.lang.IllegalStateException: Unrecognized conversion character i看到这个错我第一反应是%i写错了。但检查了一遍没问题。然后我又怀疑是不是 pom 里显式依赖了老版本 Logback导致版本冲突。查了一下依赖树Spring Boot 3.3.4 默认管理的是 Logback 1.5.x而我项目里确实还残留着 1.2.11 的显式依赖。把显式依赖删掉之后报错变了java.lang.IllegalStateException: failed to initialize RollingFileAppender再往下翻具体异常才看到关键信息SizeAndTimeBasedFNATP在 Logback 1.3 里被标记为废弃并且某些场景下直接不兼容。这个时候我才明白问题的根源不在 Spring Boot而是 Spring Boot 升级后把 Logback 也带到了新版本旧配置的“经典组合拳”在 Logback 1.3/1.5 里走不通了。1.1 升级后常见的三类现象先别急着改配置建议你升级前先对照一下有没有下面这些症状确认是不是同一个病根第一种启动报错Unrecognized conversion character或者Failed to instantiate应用直接起不来。这种最好定位因为异常信息里的类名基本都指向 logback 的 rollingPolicy 相关类。第二种应用能启动但日志文件永远只有一个不滚动、不归档。这种最坑因为不是报错而是配置被默认行为覆盖了你盯着 log 目录半天看不出来问题。第三种启动后正常但文件到了设定大小后不是拆分归档而是直接把旧内容覆盖掉或者生成的归档文件名是log.2024-09-01.0.log这种奇怪带0的格式一看就是%i解析失败导致的。我这次遇到的是第一种但群里也有人反馈是第二种和第三种。无论哪种最后指向的都是同一个原因Logback 1.2 的旧配置写法在 1.3 版本已经不能继续无脑复制了。1.2 先确认依赖版本别被自己的 pom 坑了动手改配置之前务必先确认你项目里最终生效的 Logback 到底是哪个版本。Spring Boot 3.3.4 的spring-boot-dependenciesBOM 会统一管理 Logback 版本默认大概是 1.5.x。但很多老项目会在 pom 里显式声明过logback-classic或logback-core的 1.2.x 版本导致升级 Spring Boot 后仍然用旧版 Logback这时候反而不会报错。如果你没有显式依赖而是直接被 Spring Boot 带着升到新版本那么旧配置大概率会炸。建议先执行mvn dependency:tree -Dincludesch.qos.logback:logback-classic看一下最终解析出来的版本。如果显示 1.5.x请继续往下看如果还停留在 1.2.x说明你自己的显式依赖把 Spring Boot 的版本覆盖了这时候要么去掉显式依赖要么顺手升级到 1.5.x再按新写法调整。注意如果你升级 Spring Boot 后日志居然一切正常未必是配置没问题很可能是旧 Logback 还在“硬撑”。但这种版本错位的状态很危险后续扫描漏洞或者其他依赖升级随时可能把 Logback 拉到新版那时再炸就不好排查了。2. 为什么旧回滚策略不兼容Logback 1.3/1.5 动了哪些底层逻辑要彻底搞明白先看 Logback 本身的演进。Logback 1.2.x 是个非常老的稳定分支很多 Spring Boot 2.x 项目都在用。而 1.3.x / 1.5.x 是后来为适配 Jakarta EE 9 而发布的版本Spring Boot 3.x 正是基于 Jakarta EE 的生态。按官方说法1.3 之后的部分内部 API 被清理一些 deprecated废弃的类被移除或降级支持。其中影响最大的就是ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP。这个类在 1.2.x 里承担着“按时间回滚 按大小触发”的角色。它本身不是 RollingPolicy而是嵌套在TimeBasedRollingPolicy里的TriggeringPolicy。老教程都会告诉你要按天切分同时单文件又不想超过固定大小就用TimeBasedRollingPolicy塞一个SizeAndTimeBasedFNATP。这套设计在 Logback 1.2.x 里能跑但属于“组合式”设计。Logback 1.3 之后官方干脆推出了更直接的SizeAndTimeBasedRollingPolicy。单独一个类既能按时间又能按大小不再需要额外塞TimeBasedRollingPolicy。同时内部实现也换了处理%i的解析器。如果配置文件里还留着TimeBasedRollingPolicy SizeAndTimeBasedFNATP在解析%i时就会拿到不同的上下文最终导致启动异常或配置不生效。打个不严谨但容易理解的比方旧方式像是“时间处理器”和“大小处理器”两个模块对接中间用一根老式数据线新版本把这根线的协议改了两个模块还能共存但互相之间发不了数据结果就是处理器直接罢工。2.1 破坏性变化的本质不是语法变了而是类路径和解析器变了很多初级开发者会以为XML 标签写错了才会报错。但这次的问题不是标签缺失而是同一个标签在不同版本里的解析规则不同。SizeAndTimeBasedFNATP这个类在 Logback 1.2.x 里会主动解析maxFileSize然后控制何时触发滚动。而在 Logback 1.3 里这个类虽然还在 jar 包里但内部标记为 deprecated并且不再自动从TimeBasedRollingPolicy的配置层级里读取maxFileSize。可能的结果就是启动时类仍然能加载但配置项完全不生效启动时解析%i直接抛出Unrecognized conversion character万一加载成功但默认按时间策略走根本不管大小拆分。我的理解是新版本希望开发者直接使用SizeAndTimeBasedRollingPolicy这样时间模式和大小模式可以同时完成不再通过“回调”机制间接协作。这也是后续 Logback 演进的方向——减少继承层级让配置更扁平。2.2 为什么 Spring Boot 3.3.4 升级会触发这个问题Spring Boot 的版本升级通常会直接修改spring-boot-dependencies中管理的一批组件版本Logback 就是其中之一。Spring Boot 2.7 时代管理的是 Logback 1.2.xSpring Boot 3.x 开始使用 Logback 1.4.x/1.5.x。从 3.2 升到 3.3.4其实 Logback 主版本没有变但如果项目是从 2.x 一步到位升 3.3.4那么就等于 Logback 从 1.2 直接跳到 1.5跨越了两个大版本旧配置不兼容几乎必然发生。所以你在网上搜到的大多数“Spring Boot 3 Logback 配置”示例都会直接写SizeAndTimeBasedRollingPolicy很少再看到TimeBasedRollingPolicy FNATP的组合。如果你手上的资料比较老照着 2.x 时代的博客改 Spring Boot 3 项目的配置踩这个坑的概率极高。3. 实操解决把旧回滚策略迁移到新配置写法确认版本和本质原因之后解决方案很清晰不玩组合套路直接换成官方推荐的SizeAndTimeBasedRollingPolicy。这种策略本身就同时支持时间回滚和大小回滚而且%d、%i都直接解析不用再额外嵌套。下面是迁移前和迁移后的对照建议直接照着改。3.1 迁移后的新配置示例Spring Boot 3.3.4 Logback 1.5.x删除旧的TimeBasedRollingPolicy SizeAndTimeBasedFNATP嵌套改成下面这样appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/app.log/file rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy fileNamePatternlogs/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxFileSize100MB/maxFileSize maxHistory30/maxHistory totalSizeCap10GB/totalSizeCap /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder /appender文件名字模式里面依然保留%d{yyyy-MM-dd}.%i.log这是关键。%d负责日期%i负责当天内的文件序号两者可以同时存在。如果只写%d则不会按大小拆分如果只写%i则不会按日期归档。SizeAndTimeBasedRollingPolicy的设计本身就默认了两者并存不需要额外的触发策略。3.2 参数说明与推荐值迁移不是把类名换了就行几个参数必须理解到位不然配置出来行为不对。maxFileSize当前活跃文件达到多大触发滚动。常见写法100MB、1GB。别写成100缺省单位会有歧义。maxHistory保留归档文件的天数。这里指保留 30 天超出部分删除。totalSizeCap所有日志文件包括活跃文件和归档文件总大小达到上限后删除最旧归档文件。10GB 是我这边的生产标准你可以按磁盘情况调整。注意totalSizeCap在旧版 Logback 里有时候不会被严格执行尤其是当fileNamePattern里没有%i或者归档频率较低时。新版本中这个参数被处理得比较严格所以加了之后要留意日志目录是否会提前清理。再看一个容易被忽略的细节file的配置要与fileNamePattern的路径保持一个逻辑层级。如果file写logs/app.logfileNamePattern写logs/app.%d{yyyy-MM-dd}.%i.log那是正常的活跃文件叫app.log归档文件带日期加序号。但如果你把file也写到带日期会出现同时打开多个文件的问题甚至造成日志写入混乱。3.3 想保留旧配置能不能硬扛有人会问我不想改配置能不能在 pom 里把 Logback 版本锁回 1.2.x继续用旧方案短期看可以但非常不建议。Spring Boot 3.x 本身面向 Jakarta EE如果强制使用 Logback 1.2.x会出现类库冲突。最典型的问题就是javax.servlet和jakarta.servlet的混用。Logback 1.2.x 依赖旧的 Servlet APISpring Boot 3.3 运行在 Tomcat 10.1 上底层的jakarta.*包与 Logback 需要的javax.*包不匹配。你也许能把应用跑起来但到某些上报错时根本分不清是不是这个版本错位导致的。另一个隐患是漏洞扫描。Logback 1.2.x 已经停止安全更新后续 CVE 修复只在新版本里。你为了日志配置硬锁老版本等于给生产环境埋雷。所以我的立场是配置一次性改到位别试图反向兼容。Logback 1.5.x 的 API 并不复杂迁移成本远低于想象。3.4 多环境配置的注意点如果你的 logback-spring.xml 有 springProfile 分环境配置或者多应用共享一套日志配置迁移时建议保持一致。不要把 dev 环境改成新写法、prod 环境还用旧写法那样后面排查问题时要分两套思路看非常痛苦。我实际工程里是直接把日志配置抽取到公共模块所有服务共用同一个 logback-spring.xml。这样升级时只要改一次所有服务统一生效。如果你也准备这么做建议在公共配置中不要写死file的绝对路径而是用${LOG_PATH:-logs}这种占位符方式不同服务通过启动参数覆盖。这样日志目录、归档周期、大小阈值都可以由每个服务自己的环境变量控制但整体策略保持一致。4. 升级后常见问题与排查技巧把配置改成新写法之后很多人还会遇到一些衍生的“小毛病”。这一节我按实际排查顺序整理一下。4.1 现象日志文件根本不生成或者不写入改了配置但logs/app.log始终没出现或者一直停留在 0 字节。通常不是 logback 配置的问题而是权限或路径问题。先看启动日志里有没有Failed to create parent directories之类的警告。如果日志路径是相对路径logs/app.log在 Spring Boot 应用里通常相对于启动目录也就是 jar 包所在目录。如果通过 systemd 或 Docker 启动工作目录可能不是你以为的目录。最简单的办法是加一个绝对路径或者显式设置LOG_PATH环境变量确保应用有权限创建目录。另一个常见坑logback-spring.xml文件名写错。Spring Boot 默认加载logback-spring.xml不是logback.xml。如果文件放到了src/main/resources下但名字不匹配配置不会生效。你可以用logging.configclasspath:logback-spring.xml强制指定排查时特别管用。4.2 现象%i一直为0每天只生成一个带.0的文件这个问题在新旧版本都可能出现但在迁移后更容易暴露。原因通常是配置里fileNamePattern写成了logs/app.%d{yyyy-MM-dd}.%i.log但没有正确指定maxFileSize导致大小触发永远不会命中%i没有机会增长。第一个归档文件%i默认是 0所以看到.0.log是正常的关键是后续有没有.1.log、.2.log。如果文件到了 100MB 也没有生成.1.log请确认maxFileSize是否写进了SizeAndTimeBasedRollingPolicy内部而不是写在appender层级。maxFileSize必须作为rollingPolicy的子标签放在外面不生效。4.3 现象旧归档文件很快被删除日志保存天数缩水配置了maxHistory30和totalSizeCap10GB结果发现七天前日志就没了。这种情况往往不是配置错误而是totalSizeCap太小或者单日日志量远大于预期。maxHistory与totalSizeCap同时存在的时候遵循“先到先删”原则。如果每天的日志总量超过 1.5GB七天就会撞到 10GB 上限系统会删除最老归档文件。想要保留更多天数就得把totalSizeCap调大或者减少单文件大小与归档频率。我在生产环境是这么定的单文件 200MBmaxHistory30totalSizeCap50GB。这个组合能扛住中型服务的压力但又不会让磁盘被无限写满。真实项目还要结合监控告警不要只依赖totalSizeCap。4.4 现象升级后日志格式变了时间戳或线程信息串格式有时升级 Spring Boot 3.3.4 后即使没有改 logback 配置日志输出格式也和旧版略有差异。这通常不是 logback 配置的问题而是默认pattern里的输出行为随版本调整。如果你的日志已经被标准化监控收集比如通过 Filebeat 或 Logstash任何格式变化都可能导致采集失效。我建议升级后先跑一个最小案例人工检查一条 INFO 和一条 ERROR 日志确认%d、%thread、%logger、%msg等字段的排列和之前一致再滚动发布。不要盲信“配置没改就不会变”。在很多 Java 基础组件升级中这类隐式变化很常见。4.5 现场排查工具启动时开启 logback 调试如果配置改了还是不起作用可以临时打开 Logback 的调试输出在logback-spring.xml开头加一行configuration debugtrue这样启动时控制台会输出每个 appender 的初始化状态、配置文件路径、解析出来的实际参数。对解决“配置不生效”特别有效。排查完记得去掉不然第三方依赖也会跟着输出大量调试日志。还可以用StatusListener打印内部状态比如statusListener classch.qos.logback.core.status.OnConsoleStatusListener /这会把 Logback 启动阶段收集到的所有警告和错误直接打到控制台比看异常堆栈更直观。5. 升级前后的进一步思考5.1 为什么不能只看“应用能起来”就完事我见过不少同事升级 Spring Boot 后应用正常启动就认为一切顺利。但日志滚动这种后台任务如果不观察几天根本看不出来问题。尤其是旧配置用了SizeAndTimeBasedFNATP的项目在 Logback 1.5 里可能应用启动不报错但实际的滚动策略已经退化为“按天滚动不按大小拆分”。这种退化不会立即影响业务但在高并发日志量大时单个文件会被撑到几个 GB最终导致磁盘写满或日志采集工具处理失败。所以升级 Spring Boot 大版本后日志系统是必查项。我一般会做一个快速验证启动应用手动生成一定量日志把单个文件越过maxFileSize阈值观察logs目录是否生成app.log、app.2024-09-01.0.log、app.2024-09-01.1.log修改系统日期测试环境或缩短maxHistory验证旧文件是否被清理。只有这三个动作都通过我才敢说日志配置没问题。5.2 一个值得养成的习惯把日志配置纳入版本变更清单升级 Spring Boot 这类框架容易只关注业务代码、接口变化、数据库驱动兼容性日志配置常常被忽略。但它的影响范围是全局的一旦出问题所有服务的日志都会受影响排查起来很被动。我现在的团队有个不成文的规定任何 Spring Boot 主版本升级都必须把logback-spring.xml和application.yml的logging部分放进 review 清单。升级前先对照官方迁移文档检查配置项升级后至少保留 24 小时的现场观察期。把日志问题消灭在灰度阶段而不是等全量发布后半夜起来看磁盘占用报警。关于 Logback 迁移最后再分享一个小技巧不要把fileNamePattern里的%i放在%d前面。合理的顺序是%d在前、%i在后。旧配置里有些文章会写成app.%i.%d{yyyy-MM-dd}.log在新版 Logback 里这种顺序会导致归档分组逻辑和意图不符可能出现每次启动都重命名文件的情况。我踩过这个坑折腾了一下午才发现是顺序问题。日志配置本身就是越简单越可靠。能用官方推荐的聚合策略就别再把两个策略嵌套在一起。这次升级虽然折腾但也让我把项目里积攒多年的“玄学日志配置”彻底清理了一遍算是因祸得福。