☰
JavaWeb后端日志技术全解:框架选择、配置与线上排查
2026/10/9 3:08:25 网站建设 项目流程

我平时带后端新人,发现一个很有意思的现象:很多人写代码时把System.out.println当成万能调试工具,结果项目一上线,日志刷得跟瀑布一样,排查问题全靠眼力。等到真的出故障了,翻半天日志找不到关键信息,才意识到日志技术不是"能打印就行"那么简单。这篇JavaWeb后端学习记录,就围绕日志技术讲透三件事:日志框架到底怎么选、配置文件怎么写才能既详细又不臃肿、线上日志出问题怎么排查。无论你是刚开始学JavaWeb的学生,还是已经写了一段时间CRUD的初级开发,这篇内容都值得收藏。

日志技术听着不起眼,实际上它在整个后端体系里的地位,和我之前讲过的异常处理、事务管理一样,都属于"平时看不见,出事定生死"的模块。我见过太多项目,功能跑得挺欢,一到生产环境就抓瞎,就是因为日志这块没打好基础。这篇就用我自己实际踩坑的经历,把日志技术从零到落地讲清楚。

1. JavaWeb后端日志到底在解决什么问题

先说结论:日志技术不是简单地把信息打印到控制台,它是你在生产环境里唯一能依靠的"眼睛"。我们开发的JavaWeb应用,绝大多数都是部署在服务器上运行,你不可能像在自己电脑上调试一样打断点、看变量。线上环境出了任何问题,你能拿到的第一手资料就是日志文件。没有日志,后端开发就变成了盲人摸象,出了问题只能靠猜。

我用一个生活类比来解释日志的作用:你家的智能门锁突然打不开了,你不会把整个锁拆了看里面结构,你肯定先查它的运行记录——是密码输入错误、是电机没响应、还是电量不足。日志就是软件系统里的"运行记录",它告诉你程序每一步在干什么、在哪个环节出了问题。尤其是JavaWeb这种多线程、高并发的应用,请求链路复杂,参数传递频繁,没有日志几乎无法定位问题。

从技术栈的角度看,JavaWeb后端涉及日志的地方比你想的要多得多:

场景日志发挥作用的地方
接口调用记录请求参数、响应结果、耗时情况
异常处理输出异常堆栈、错误上下文、业务状态
数据库操作打印SQL语句、参数值、执行结果
定时任务记录任务开始、结束、执行结果
第三方对接记录外部接口返回、超时信息、重试次数
权限操作记录用户操作行为、登录状态、越权尝试

这张表里列的每一类,在我实际开发的JavaWeb项目里都遇到过对应的线上故障。比如定时任务突然不跑了,如果你没有在任务开始和结束的地方打日志,你根本不知道是任务压根没触发、还是触发后死循环了、还是中途抛异常了。再比如第三方接口偶尔超时,你需要在调用前记录请求内容,在返回后记录响应状态和耗时,才能判断到底是对方的问题还是自己的问题。

还有一个很多人容易忽视的点:日志影响性能。有人嫌配置日志框架麻烦,就用System.out.println顶事,结果在高并发环境下,大量的IO操作拖慢了接口响应速度。我实测过,一个普通接口每秒调用100次,如果用println打印三行日志,接口平均耗时能增加20%以上。这个后面讲框架选择的时候再细说。

2. 主流日志框架对比:SELogg|Log4j2|Logback到底怎么选

Java后端日志框架的演进史其实挺清晰的。最早大家用Log4j 1.x,后来Sun推出了java.util.logging,但用的人少。再后来Logback出现,被Spring Boot默认集成。现在Log4j2凭借异步性能又杀回来了。很多新手看到这几个名字直接懵,不知道选哪个,我先用一张表把它们的核心差异列出来:

框架名称性能表现配置复杂度与SLF4J整合适合场景
java.util.logging差简单一般JDK自带,很少单独用
Log4j 1.x较差简单可以老旧项目维护
Logback优秀简单原生支持Spring Boot默认选择
Log4j2极优秀中等支持高并发、大流量场景

选Logback还是Log4j2,这个纠结我自己也经历过。我个人的经验是:如果是新开Spring Boot项目,直接跟着框架默认走,用Logback就行,因为Spring Boot已经把Logback集成得很透,配置文件写好基本不用操心。如果你的项目对吞吐量有极端要求,比如每秒几十万次日志写入,那可以考虑Log4j2的异步日志功能,它用Disruptor环形队列实现,性能确实比Logback高不少。

不过,无论选哪个框架,我都强烈建议统一用SLF4J门面模式来写代码。门面模式这个概念听起来玄乎,其实核心就是:你的业务代码里只面向SLF4J的API写日志,具体底层用Logback还是Log4j2,由配置文件决定。这样做的好处是项目里不会出现混用多种日志框架导致日志重复输出、格式混乱的问题。我接手过一个老项目,有的类直接用Log4j1的API,有的类用commons-logging,有的类用SLF4J,结果一个日志信息打印三遍,排查起来极其痛苦。

具体到代码里的使用方式,SLF4J的标准写法是这样:

import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OrderService { private static final Logger logger = LoggerFactory.getLogger(OrderService.class); public void createOrder(Long userId, Long productId) { logger.info("开始创建订单,用户ID:{},商品ID:{}", userId, productId); // 业务逻辑 logger.info("订单创建成功,用户ID:{}, 商品ID:{}", userId, productId); } }

注意这段代码里的{}占位符,这是SLF4J最常用的特性。它和字符串拼接的区别在于,只有当日志级别被启用时才会执行参数格式化的操作,避免了不必要的资源浪费。但如果用logger.info("订单创建成功,用户ID:" + userId)这种写法,就算日志级别是WARN不会输出,字符串拼接也已经执行了。

我建议的新手路线是:不管你的项目最终用Logback还是Log4j2,代码层面一律用SLF4J。这样后期想切换日志框架,只需要改配置文件,不需要动一行业务代码。

3. Logback配置详解:从基本语法到生产级配置文件

既然Spring Boot默认用Logback,我重点讲它的配置怎么写。很多人一看到logback-spring.xml就头疼,其实核心概念就三块:Logger(记录器)、Appender(输出目的地)、Layout(输出格式)。你只需要理解这三个概念之间的关系,配置文件的逻辑就通了。

我用做饭来类比:Logger是你决定"什么菜需要记录、记录到什么程度"的地方,相当于厨房的菜单;Appender决定"做好的菜端到哪里去",可能是端到餐桌(控制台)、放到柜子里(文件)、或者打包寄走(发送到日志收集系统);Layout决定 "每道菜怎么摆盘",也就是日志输出的格式——包含时间、线程名、类名、日志内容等。

3.1 控制台输出配置

开发环境我们最需要的默认配置是控制台打印日志,配置内容是定义一个ConsoleAppender,把INFO级别的日志打印出来。

<configuration> <!-- 引入Spring Boot默认的logback基础配置 --> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> </appender> <!-- 项目自定义logger --> <logger name="com.example" level="DEBUG"/> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>

这段配置里的${CONSOLE_LOG_PATTERN}是Spring Boot已经定义好的默认格式模板,实际输出长这样:

2024-01-15 14:30:21.123 INFO 12345 --- [nio-8080-exec-1] com.example.OrderService : 订单创建成功

如果不喜欢这个格式,可以自己写pattern。我最常用的一套自选pattern是下面这个,信息足够全但又不啰嗦:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

简单解释一下:%d是时间,%thread是当前线程名,%-5level是日志级别左对齐显示,%logger{50}是打印类名压缩到50个字符以内,%msg是日志内容,%n是换行。这个格式在排查线上问题时特别好用,尤其是多线程场景,线程名能帮你找到同一个请求的日志线索。

3.2 文件输出配置

控制台日志有一个致命问题:服务器一重启,日志就没了。生产环境必须把日志写到文件里,而且要做滚动备份——就是日志文件大了或者时间到了,自动切割成新文件,旧文件按策略留存。Logback最常用的滚动策略是SizeAndTimeBasedRollingPolicy,同时按文件大小和时间滚动。

我项目里用的文件输出配置是这样的:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>./logs/app.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>./logs/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>15</maxHistory> <totalSizeCap>2GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> </appender>

这里有几个参数需要认真说明一下。maxFileSize=100MB表示单个日志文件满100MB就触发滚动,这是基于文件大小的滚动;fileNamePattern里包含%d{yyyy-MM-dd},表示按天滚动,所以正确的滚动触发条件是"文件达到100MB"或"跨天"这两个任意一个满足,都会生成新文件。maxHistory=15表示最多保留15天的日志文件,超期的自动删除。totalSizeCap=2GB限制的是所有归档日志的总大小,超过2GB后Logback会删除最旧的归档来满足这个限制。这几个参数在线上环境是必须设置的,不然磁盘会被日志塞满,我看过太多因为日志把磁盘撑爆导致服务宕机的案例。

还有一个细节要提醒:文件名里写%i是必须的。因为同一个日期内可能会滚动多次(文件达到maxFileSize时触发),每次滚动生成的文件名如果完全一样,就会互相覆盖。%i是一个自增序号,保证同一时间段内的多个滚动文件不会重名。

3.3 生产级配置:多环境分离

在实际项目中,开发环境、测试环境、生产环境的日志策略应该是不同的。开发环境想看到大量DEBUG日志,方便调试;生产环境则必须控制日志量,避免无关日志淹没关键信息。Logback支持通过springProfile标签区分不同环境,配置一次搞定。

<configuration> <!-- 开发环境:控制台输出DEBUG级别 --> <springProfile name="dev"> <root level="DEBUG"> <appender-ref ref="CONSOLE"/> </root> </springProfile> <!-- 生产环境:文件输出INFO级别,保留15天 --> <springProfile name="prod"> <root level="INFO"> <appender-ref ref="FILE"/> <appender-ref ref="CONSOLE"/> </root> </springProfile> </configuration>

这样在application.yml里设置spring.profiles.active=dev时,就会走开发环境配置;部署到生产服务器设置成prod,就会走生产配置。不需要在部署时改任何日志代码,非常干净。

4. 日志级别怎么定:从DEBUG到ERROR的合理规划

日志级别这事看着简单,实际用起来很多人把握不好火候。Java里的日志级别从低到高依次是:TRACE、DEBUG、INFO、WARN、ERROR。低级别包含高级别的所有信息,也就是设置了INFO级别,WARN和ERROR也会一起输出。

实际开发中逻辑是这样的:TRACE用于记录比DEBUG更细的流程,比如循环里每次迭代的关键变量值,这种级别的日志量非常大,一般只在应急排查时临时启用;DEBUG用于开发阶段输出详细调试信息,比如参数值、中间计算结果、分支执行情况;INFO记录业务运行的关键节点,比如请求开始、请求结束、订单创建成功、用户注册成功等;WARN记录可能出现问题但不影响当前运行的情况,比如缓存未命中后回源查库、接口调用重试前警告;ERROR记录异常和错误,比如捕获到业务异常、数据库连接失败、第三方接口返回错误码。

我见很多新人喜欢把所有日志都写成logger.info(),这是一个误区。INFO日志应该是在业务层面有意义的事件记录,像方法入口这种情况,在DEBUG级别打印就够了。如果所有信息都堆在INFO,日志文件会快速膨胀,真正有价值的信息反而被淹没。我自己的习惯是:写业务代码时,方法入口和出口打印DEBUG,关键业务状态变更打印INFO,捕获到异常打印ERROR并带上完整上下文,需要关注但不必阻断的场景用WARN。

特别要强调一下ERROR日志的写法。很多新人只在catch块里写一句logger.error("出错了"),这是最没用的日志。正确写法是必须包含异常类型、异常信息、以及相关的业务上下文。比如:

try { // 调用第三方支付接口 paymentClient.pay(orderId, amount); } catch (PayException e) { logger.error("支付接口调用失败,订单ID:{},金额:{},错误码:{}", orderId, amount, e.getErrorCode(), e); // 业务处理 }

注意最后把e这个异常对象直接传进去了,Logback会自动把堆栈信息完整打印出来。如果你只打印e.getMessage(),堆栈就丢失了,排查问题时少了很多线索。这是我在code review时经常给新人纠正的点。

5. 我的日志实战:一个接口异常的完整排查过程

理论知识讲再多,不如一次实战来得直观。我用自己最近排查的一个真实问题,完整演示日志技术怎么在日常开发中发挥作用。

现象是这样的:上线后的系统偶尔收到用户反馈,说创建订单的接口会报"系统繁忙",但频率不高,大概每天早上高峰时段发生几次,且用户重试一次往往就成功了。这种偶发性问题最让人头疼,监控告警也没触发,因为接口错误率没达到阈值。

我先看的日志配置。因为之前已经规范了日志,接口入口和出口都有INFO日志,异常catch块也打印了完整堆栈。我登录服务器查看当天的日志文件,用grep搜索订单创建接口相关的链路:

grep "createOrder" /app/logs/app-2024-01-15.log | head -50

日志输出了很多条,我注意到在早上9点32分左右有一条异常日志:

2024-01-15 09:32:15.832 ERROR 27621 --- [http-nio-8080-exec-12] c.e.s.OrderService - 创建订单异常,订单号:20240115093212345,原因:数据库连接池获取连接超时,错误信息:waiting for a connection at pool timeout,重试次数:0

看到"数据库连接池获取连接超时"这几个字,方向明确了:问题大概率出在连接池配置上,而不是业务逻辑本身。我打开对应的配置检查,发现HikariCP的maximum-pool-size设置的是15,connection-timeout设置的是30000毫秒。

再结合日志中的其他信息——所有异常都集中出现在早上9点30分到9点45分之间,这个时间段正好是业务高峰,并发请求量大,连接池的连接被打满,新的请求等不到空闲连接,直接超时。而用户手动重试时,因为高峰已经过去,连接池释放了部分连接,自然就成功了。

为了验证这个推断,我继续查日志,统计了同时段的慢SQL日志。Spring Boot的spring.jpa.properties.hibernate.jdbc.batch_size等设置对日志输出也有影响,配合SQL执行时间日志,我确认好几条SQL执行超过10秒,占用了连接很长时间。根因清楚了:部分SQL执行过慢,导致连接被长时间占用,高峰期并发一上来,连接池就空了。

修复措施有两步:第一,优化那几条慢SQL的索引,让执行时间从10秒降到200毫秒以内;第二,把连接池的maximum-pool-size从15调整到30,connection-timeout从30秒降到10秒,让请求快速失败而不是傻等。改完上线观察了两周,问题没有复现。

复盘这个过程,我在日志使用上的几个关键动作起了决定性作用:接口入口和出口有INFO日志帮我确认调用链路;异常catch块带了订单号和完整堆栈帮我定位到具体环节;连接池超时信息被我记录下来,关联到时间段和并发场景,才能快速锁定根因。如果当初只是System.out.println("error"),这个问题排查时长至少翻倍。

6. 异步日志与日志分组:高并发场景的进阶玩法

上面讲的方案,对大部分项目已经够用。如果你的系统流量特别大,日志写入本身可能成为性能瓶颈。我举个例子:一个接口每秒被调用500次,每个请求打印3行日志,每秒就有1500条日志需要写入磁盘。这时候如果同步写日志,每次打印都要等待IO完成,接口响应时间会被拖慢。

解决这个问题,有两个主要手段:异步日志和日志分组。异步日志的核心思想是:应用线程打印日志时,先把日志消息丢到一个内存队列里,立刻返回继续执行。后台有一条专门的工作线程,从这个队列里取消息,批量写入文件。这样IO开销被分摊到后台,不阻塞业务线程。Logback实现这个只需要两步,第一步在配置里加一个AsyncAppender,第二步让logger引用它:

<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"> <!-- 引用配置文件 --> <appender-ref ref="FILE"/> <!-- 队列最大容量,超出后日志直接丢弃(默认2000) --> <queueSize>5000</queueSize> <!-- 丢弃阈值:队列剩余容量低于这个值就丢弃TRACE/DEBUG/INFO日志 --> <discardingThreshold>0</discardingThreshold> <!-- 是否只保留日志信息中的一个(避免丢弃不完整) --> <neverBlock>true</neverBlock> </appender>

配置里的neverBlock这个参数要特别注意。如果设为true,当队列已满时,应用线程不会阻塞等待,而是直接把该日志消息丢弃。这样能最大程度保证业务主流程不受影响,坏处是极端情况下会丢日志。如果设为false,队列满时应用线程会阻塞,相当于又变回同步,但保证日志不丢。我个人的经验是,关键业务日志可以引入可靠同步策略,普通业务日志用异步就好。线上到底用哪种,要结合业务的重要程度取舍。

日志分组也是一个很实用但知道的人不多的功能。它本质上是把同一个业务链路的日志,归拢到同一个独立的日志文件里。比如订单模块和支付模块的日志分开存储,排查支付问题时直接看支付专用的日志文件,不用在全部日志里大海捞针一遍。配置方式是在Logback的配置里声明多个logger,每个logger绑定不同的appender和文件路径:

<logger name="com.example.payment" level="INFO" additivity="false"> <appender-ref ref="PAYMENT_FILE"/> </logger> <appender name="PAYMENT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>./logs/payment.log</file> ... </appender>

配置里有个additivity="false"很关键。如果不加,日志除了会写到payment专用的文件里,还会由于root logger的传递性,把所有com.example.payment包下的日志同时写到全局的app.log里。这会导致日志重复。设置为false,就表示日志只写到指定的appender,不再向上传递。

7. 日志追踪ID:让日志串联成一条线

讲到高并发场景,就不得不提一个让日志价值翻倍的操作:在日志中注入追踪ID(traceId)。后端系统一次请求往往要经过很多层方法,从Controller到Service到Mapper,可能还会调用其他服务。如果出现异常,你想知道"这次请求里除了报错之外还经历了什么",就需要把同一次请求的所有日志串起来看。

Spring Boot里实现追踪ID的第一种方式是使用MDC,全称Mapped Diagnostic Context,它是SLF4J提供的一个ThreadLocal上下文环境,你往里面放的值,可以自动附加到当前线程后续打印的所有日志里。我在Filter里为每个请求生成一个唯一ID,然后放进MDC:

@Component public class TraceIdFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { String traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16); MDC.put("traceId", traceId); try { chain.doFilter(request, response); } finally { MDC.remove("traceId"); } } }

然后在logback的pattern里加上%X{traceId}:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%X{traceId}] - %msg%n</pattern>

这样输出效果是:

2024-01-15 09:32:15.832 ERROR 27621 --- [http-nio-8080-exec-12] c.e.s.OrderService [a1b2c3d4e5f6a7b8] - 创建订单异常...

排查时只需要根据你从用户那里得到的traceId,或者从入口日志中找到的traceId,在日志文件里grep一下,整个请求的完整处理过程就全出来了。这是我从"盲目翻日志"到"精准看链路"转折最大的一个操作,强烈建议所有JavaWeb项目都做这个改造。

要实现全链路追踪,还可以引入分布式链路追踪系统,比如Sleuth和Zipkin的组合。它们做的事情本质上是在每个请求进入时生成spanId和traceId,跨服务调用时自动传递这些ID,把所有服务中同一个请求的日志都串起来。如果你做的是微服务架构,这一步几乎是必须的。

8. 常见日志问题与排查技巧实录

日志技术看似简单,实际使用中坑非常多。我把自己踩过的、带新人时见到的典型问题整理成一个速查表,跟着这个表排查,能少走很多弯路。

现象可能原因排查方法
日志没有输出日志级别设置过高,日志被过滤检查root/logger的level配置
日志重复打印多个logger引用同一logback配置检查additivity设置和重复的appender-ref
控制台没有报错异步日志丢弃了消息查看AsyncAppender队列大小和discardingThreshold
日志文件不滚动fileNamePattern里缺少%i占位符文件名相同导致覆盖
磁盘被日志占满totalSizeCap未设置或设置过大设置总大小上限并定期清理
堆栈信息不完整打印异常时只输出getMessage把异常对象作为最后一个参数传入
日志时间不对服务器时区与启动时区设置不一致统一配置JVM时区参数
请求日志串不到一起缺traceId或MDC使用方式错误引入过滤器注入traceId,用%X输出
日志里有乱码编码格式不一致日志文件中charset设置为UTF-8
上线后调试日志不见了生产环境级别限制检查激活的profile对应配置

这里面有两个问题值得单独展开说。

第一个是日志重复打印。这个问题在老项目里非常普遍,原因通常是引入了多个日志框架的桥接包,比如项目中引入了log4j-to-slf4j和logback,同时又被commons-logging的旧依赖干扰。排查方法是看启动日志,如果发现"Multiple SLF4J providers"这类提示,再用mvn dependency:tree检查依赖树,找出重复的日志框架依赖,统一用一个。

第二个是异步日志下的日志丢失。我最早用AsyncAppender时,因为配置了neverBlock=true,在高流量下出现过日志丢失。后来我学到一个习惯——日志容量的监控。给日志文件设置固定的maxFileSize和maxHistory之后,再配合服务器上的定时告警,比如磁盘使用率超过75%就告警,日志文件增长速率超过正常基线就告警。发现问题时先检查是日志量真的增加,还是异步队列排队导致积压刷盘。

还有一个小技巧:临时调整线上日志级别。线上环境日志级别一般设成INFO,但遇到疑难杂症需要看DEBUG级别信息,总不能重新发版。我常用的办法是在Spring Boot的application.yml里通过logging.level.com.example=DEBUG这个配置项动态调级别,然后重启应用。如果不能重启,可以用Loki、SkyWalking这类工具配置动态日志级别下发,不过那属于另一套体系了,基础阶段先把配置文件玩熟就够了。

9. 踩坑复盘:一个月后我再来看日志配置

趁这次写日志技术专题,我把手上项目的日志配置整体复查了一遍,还真发现一个之前埋的雷。我原来的配置里,maxHistory参数改过一次从7天调成30天,但没有同步调整totalSizeCap,结果日志文件累积了快3GB。虽然不是问题的高峰期不至于宕机,但如果任由涨下去,磁盘肯定被撑爆。

后来我总结了一个日志参数联动检查表,每次调整任何日志参数,都会把这个表过一遍:

  • maxFileSize决定了单个文件切割大小,影响的是滚动频率。
  • maxHistory决定保留天数,影响是旧文件何时被清理。
  • totalSizeCap决定所有归档文件的总容量边界。
  • fileNamePattern里的%d和%i决定切割维度和同名文件是否能共存。
  • 异步队列的线程数和队列大小需要配套。

这几个参数不是独立的,改一个不评估其他几个,早晚出事。

另外我还强烈建议,日志配置代码化。日志相关配置尽量写成配置文件放进项目仓库里,而不是直接在服务器上去改logback.xml。原因是配置文件也是一个需要review、有版本记录的东西。我见过一个项目,线上logback.xml不知道被谁手动改过,导致排查时看代码仓库里的配置和实际运行的配置完全对不上,浪费了整整一天时间。

如果是维护老项目,没有配置化管理的条件,那至少要做到:服务器上任何对日志配置的修改,都留下一份变更记录,方便后续对账。

10. 留个作业:给你的项目加一个请求日志切面

学完这么多内容,最怕的就是"收藏了等于会了"。这篇的结尾,我不写总结了,给你安排一个实操任务,做完才算真正掌握。

任务是这样的:写一个Spring AOP切面,对所有Controller请求统一打印请求日志,包含请求路径、请求方法、入参、耗时。同时给每个请求生成traceId,写入MDC。要求:

  • 使用泛型通配符拦截所有Controller方法(execution(public * com.example.controller.*.*(..)))
  • 打印耗时用System.currentTimeMillis()前后的差值,或者直接用StopWatch
  • 入参打印时,对外部传入的大对象做个toString()防超长处理
  • 切面里打印的日志必须带上MDC的traceId
  • 日志级别定为INFO,但这个切面的logger单独走一个异步appender

做完之后,你把这几个问题的答案想清楚,并在评论区留言,我挨个看:

  1. 打印请求日志时,为什么不能直接打印HttpServletRequest对象本身?
  2. 切面里获取方法参数值时,如果参数里含HttpSession,怎么处理才不会报错?
  3. 生产环境下,把入参信息打印到日志里,需要注意哪些安全合规问题?

我个人最喜欢日志技术的一点是,它是少有的从第一天写代码到最后架构设计始终都用得到的基础能力。JavaWeb后端的日志功夫到位之后,再看什么连接池、缓存、分布式链路,都会顺手很多。毕竟,排查问题的效率,直接影响你作为后端开发的工作质量。这篇的内容到这结束,大家评论区见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询