Java案例怎么记录接口访问日志?

wen java案例 46

高效记录Java接口访问日志:从入门到最佳实践

📚 目录导读

  1. 为什么接口日志如此重要?
  2. 日志记录的几种主流方案对比
  3. 实战:基于AOP的日志记录案例
  4. 如何避免日志记录的性能陷阱
  5. 常见问题问答
  6. 选型建议与进阶方向

为什么接口日志如此重要?

在任何一个生产级别的Java项目中,接口访问日志都是排错、审计、性能分析的第一手资料,缺少日志的接口就像没有黑匣子的飞机——出了问题你根本不知道发生了什么。

Java案例怎么记录接口访问日志?

核心价值包括:

  • 故障快速定位:当用户投诉“接口返回500”,日志能告诉你参数、耗时、堆栈
  • 安全审计:记录谁、什么时间、调用了哪些敏感接口
  • 性能监控:统计每个接口的平均响应时间、QPS瓶颈
  • 业务分析:分析用户行为模式(如高频接口、失败率高的操作)

一个真实案例: 某金融系统上线后偶发交易超时,通过分析接口日志发现某第三方支付网关在14:00-15:00期间响应延迟从200ms飙升到5s,最终定位到对方服务端批量任务导致,没有日志,这种问题排查无异于大海捞针。


日志记录的几种主流方案对比

方案类型 实现方式 优势 劣势 推荐场景
Filter过滤器 javax.servlet.Filter 简单直接,无需侵入业务代码 无法获取方法级参数 单体应用基础日志
AOP切面 Spring AOP + @Around 灵活度高,可精确控制 需要理解切面表达式 主流选择(推荐)
拦截器 HandlerInterceptor 可拦截Controller前后 无法获取Controller返回值 MVC架构搭配使用
自定义注解 @Loggable + AOP 可配置性强,选择性记录 需要额外注解标记 大型项目精细控制

选型建议: 绝大多数场景下,AOP切面 + 自定义注解是当前最成熟、可维护性最高的方案,它允许你通过一个注解 @ApiLog 轻松开关日志记录。


实战:基于AOP的日志记录案例

下面演示一个完整的Spring Boot项目实现(代码可直接粘贴运行):

1 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
    String value() default "";      // 接口描述
    boolean recordParams() default true;   // 是否记录参数
}
3 切面实现
@Aspect
@Component
@Slf4j
public class ApiLogAspect {
    @Around("@annotation(apiLog)")
    public Object around(ProceedingJoinPoint point, ApiLog apiLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = point.getSignature().toShortString();
        Object[] args = point.getArgs();
        // 记录请求信息
        if (apiLog.recordParams()) {
            log.info("[API] 方法: {} | 参数: {}", methodName, JSON.toJSONString(args));
        }
        Object result = null;
        try {
            result = point.proceed();
            long cost = System.currentTimeMillis() - startTime;
            log.info("[API] 方法: {} | 耗时: {}ms | 结果: {}", methodName, cost, 
                     result != null ? JSON.toJSONString(result) : "void");
            return result;
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - startTime;
            log.error("[API] 方法: {} | 耗时: {}ms | 异常: {}", methodName, cost, e.getMessage(), e);
            throw e;  // 继续抛出,交给全局异常处理
        }
    }
}
4 使用示例
@RestController
public class UserController {
    @ApiLog("查询用户列表")
    @GetMapping("/users")
    public Result<List<User>> list(@RequestParam String name) {
        // 业务逻辑...
    }
}

输出效果:

2025-06-01 10:15:23.456 [http-nio-8080-exec-1] INFO  c.e.demo.aspect.ApiLogAspect - [API] 方法: UserController.list() | 参数: {"name":"张三"}
2025-06-01 10:15:23.789 [http-nio-8080-exec-1] INFO  c.e.demo.aspect.ApiLogAspect - [API] 方法: UserController.list() | 耗时: 333ms | 结果: {"code":200,"data":[...]}

如何避免日志记录的性能陷阱

很多人以为日志就是“print一下”,但在高并发场景下,错误的日志设计可能拖垮整个系统:

① 避免序列化大对象

// ❌ 错误:每次记录日志都序列化整个请求体
log.info("请求参数: {}", JSON.toJSONString(request));  
// ✅ 正确:只记录关键字段或截断
log.info("请求参数: userId={}, bizType={}", request.getUserId(), request.getBizType());

② 使用异步日志 在logback.xml中配置AsyncAppender,让日志写入操作不阻塞业务线程:

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE"/>
    <queueSize>1024</queueSize>
    <discardingThreshold>0</discardingThreshold>
</appender>

③ 分级采样策略 重要接口(支付/登录)全量记录;高QPS但低风险接口(如健康检查)抽样记录(例如每100条记录1条)。


常见问题问答

Q1:日志中需要记录密码、身份证等敏感信息吗? A:绝对不要!建议在AOP切面中增加脱敏过滤器,例如将密码字段替换为,手机号保留前3后4位,可使用Jackson的@JsonFilter或自定义脱敏工具类。

Q2:如果接口参数是MultipartFile文件流,该怎么记录? A:文件流序列化会报错,建议判断参数类型,遇到MultipartFile记录文件名和大小,而非内容:

if (arg instanceof MultipartFile) {
    MultipartFile file = (MultipartFile) arg;
    log.info("文件: {} ({}KB)", file.getOriginalFilename(), file.getSize()/1024);
}

Q3:日志文件越来越大,怎么办? A:配置logback的按天/按大小滚动策略:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>/logs/api-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <maxHistory>30</maxHistory>
        <totalSizeCap>10GB</totalSizeCap>
    </rollingPolicy>
</appender>

Q4:分布式系统如何关联日志? A:在请求入口生成全局traceId,通过MDC注入到日志上下文:

// 在Filter中设置
MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
// 在logback pattern中引用
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n</pattern>

选型建议与进阶方向

基础选择建议:

  • 中小项目:直接使用上述AOP + 自定义注解方案,代码量少且清晰
  • 大型项目:可考虑集成ELK(Elasticsearch + Logstash + Kibana)或SkyWalking这类APM工具,实现日志的集中存储、检索和可视化

切勿踩坑:

  • 不要在循环中打日志(尤其高QPS接口)
  • 不要使用log.info(String.format(...)),应使用占位符
  • 生产环境关闭debug日志,保留info及以上

进阶方向:

  • 将日志与Redis结合,实现实时接口监控报警
  • 利用AOP + SpEL表达式实现动态过滤(如仅记录耗时超过500ms的接口)
  • 集成Spring Cloud Sleuth实现全链路追踪

最后送上一句实战心得: “日志不是bug修复后的追责工具,而是上线前的质量守护者”,好的日志设计,能提前帮你发现80%的生产问题。


文章中所涉及的代码片段均可直接运行,项目示例域名已统一处理,如需完整项目源码,可参考主流开源框架如pigruoyi中的日志实现。

抱歉,评论功能暂时关闭!