Spring-AOP接口日志怎么统一-入参返回值耗时与脱敏

📅 2026/8/27 10:03:20
Spring-AOP接口日志怎么统一-入参返回值耗时与脱敏
Spring AOP 接口日志怎么统一入参、返回值、耗时与脱敏接口日志看似只是打印请求参数、返回值和耗时进入生产环境后却会同时遇到敏感字段、超大对象、异常路径、TraceId 和日志成本等问题。日志越“完整”泄露和噪声风险反而可能越高。关键风险如果每个 Controller 自己记录字段和格式无法统一如果一个 AOP 无差别序列化所有对象文件、密码、Token 与大列表又可能直接进入日志。企业级接口日志需要的是稳定事件模型而不是更多 println。MetaLite 用 BaseAspectLogger 及相关处理链统一采集入口、结果、耗时和异常并给脱敏与失败补偿留下扩展位置。下文先定义一条可运营的接口日志应包含什么再用源码说明这些字段在什么时机产生。一、先统一“哪些调用需要被观察”MetaLite 的AspectTypeEnum把调用分为八类类型场景是否为入口API_RECEIVE接收 HTTP 请求是JOB_RUN执行定时任务是MQ_CONSUME消费消息是DAO_CALL访问数据库或搜索引擎否API_CALL调用内部 HTTP 服务否MQ_PRODUCE发送消息否REDIS_CALL调用 Redis否CAFFEINE_CALL调用本地缓存否入口负责建立 TraceId调用层沿用当前上下文。这样一条请求可以在 API、RPC、缓存和 DAO 日志中使用同一 TraceId 关联而不是靠开发者在每个方法里手工拼接。这也是统一日志与普通HandlerInterceptor的区别拦截器主要看到 HTTP 入口无法天然覆盖定时任务、缓存、DAO 和服务调用。二、为什么不是每类切面各写一套 LoggerMetaLite 为 API、RPC、Redis、Caffeine 和任务等场景保留各自的 Logger例如publicclassApiReceiveLoggerextendsBaseAspectLogger{OverridepublicAspectTypeEnumaspectType(){returnAspectTypeEnum.API_RECEIVE;}}子类只声明自己属于哪种切面真正的日志模板都由BaseAspectLogger完成。它在调用前记录开始时间 | 应用名 | 远端IP | 本机IP | 切面类型 | URI或方法 | 入参 |调用完成后追加返回结果 | 耗时毫秒因此一条日志可以形成稳定结构2026-08-07 10:20:30.123|admin|10.0.0.8|10.0.0.12|API_RECEIVE|/api/order/list|{param:{...}}|{return:{...}}|36固定分隔字段比随意自然语言更适合检索和聚合也让不同调用类型可以共用分析规则。但这里的输出仍是普通文本日志并没有自动变成 Elasticsearch 索引、Micrometer 指标或 OpenTelemetry Span采集、解析、留存和告警仍需部署侧完成。三、接口名称为什么不能一律写成类名加方法名BaseAspectLogger.fetchName对入口和内部调用做了不同处理API_RECEIVE使用当前请求 URI其他类型使用类名.方法名。用户报告/api/order/list出错时直接按 URI 搜索比先猜 Controller 方法更快排查 Redis 或 RPC 调用时类名与方法名又比 HTTP 路径更准确。远端 IP 也只在API_RECEIVE中读取。内部方法调用没有伪造一个“远端 IP”避免字段看似完整、语义却是错误的。四、如何防止入参和返回值把日志撑爆MetaLite 用PrintControl.printLength为每种切面控制输出privateintprintLength;当前语义是0 → 不输出该切面日志 -1 → 完整输出 0 → JSON 序列化后按字符长度截断这使 API 与 DAO 可以采用不同策略。例如 API 在排障期打印有限长度DAO 默认不打印大结果缓存调用只保留足够识别 key 和结果的片段。需要注意当前实现是在对象完整序列化之后再截取字符串。它能减少最终日志体积却不能避免大对象序列化本身的 CPU 和内存成本截断后的 JSON 也可能不是合法完整 JSON。因此超大列表、文件内容和二进制对象更应该在序列化前按字段或类型忽略而不是只依赖长度。五、忽略字段、忽略类型与脱敏应该分开PrintControl提供两类忽略规则privateStringignoreFieldName;privateStringignoreFieldClass;AspectLogProperties还维护全局DesensitizeRuleprivateListDesensitizeRuleglobalDesensitizeControls;三者分别解决不同问题密码、私钥、Token按字段直接不输出文件、请求对象等大类型按类型忽略手机号、身份证等仍有排障价值的数据保留部分字符并脱敏。日志安全的原则不是“所有内容都打星号”而是先做数据最小化再决定哪些字段需要有限展示。规则按字段名称或类型匹配字段改名、同名异义和嵌套对象变化都可能造成漏网。因此配置存在不代表治理完成仍应通过请求、返回值、集合和异常场景的测试验证。六、日志序列化失败为什么不能拖垮业务入参或返回值可能包含 FastJson2 无法正常处理的对象。BaseAspectLogger在fetchParam和fetchResult中捕获序列化异常try{returnFastJson.obj2Json(...);}catch(Exceptione){log.error(fetchResult error,e);return{\return\:\serialize error\};}准确的源码行为是记录序列化失败并用错误摘要占位避免为了写日志改变主业务的成功或失败结果。这体现了基础设施的降级原则可观测性很重要但日志组件不应成为业务接口的新故障源。七、鉴权或校验提前失败时日志会不会消失切面处理器链通常采用 fail-fast参数校验、认证或限流失败后不再执行后续业务处理器。如果日志处理器排在链尾最直接的实现会导致“成功请求有日志拒绝请求没日志”。MetaLite 的AspectHandlerChain在前置处理失败时会从链尾寻找BaseAspectLogger并补执行日志前置逻辑然后返回失败响应。这样认证失败、限流和参数错误仍能获得基本调用上下文。需要区分的是前置日志补偿不代表所有处理器都会继续执行也不代表错误响应一定走成功日志的完整后置流程。异常路径最终还会进入errorHandle业务异常只记录消息其他异常附带堆栈。八、DAO 的 SQL 日志为什么需要单独开关PrintControl还有一个仅针对DAO_CALL的配置privatebooleanprintSql;SQL 或 Elasticsearch DSL 对慢查询排查很有价值但它们也可能包含大量参数、敏感条件和长文本。把 SQL 输出与普通方法参数分开控制可以让生产环境默认克制在受控诊断期按需打开。开关本身不解决慢 SQL、执行计划和数据库指标问题。DAO 日志能告诉我们“调用了什么”数据库监控才能解释“为什么慢”。九、统一日志方案仍有哪些边界结合当前源码至少要公开以下限制日志长度是序列化后按字符截断截断结果不保证是完整 JSON大对象仍会先序列化长度限制不是性能隔离字段忽略和脱敏依赖配置命中需要持续测试BaseAspectLogger输出的是结构化文本不是自动建好的日志平台耗时是当前切面包围的方法耗时异步返回Future时不等于任务最终完成耗时TraceId 是轻量日志关联不包含 Span、父子关系和调用拓扑开启完整入参、结果或 SQL 会增加泄露与存储风险。这些边界不削弱统一日志的价值反而决定了它应该处在什么位置负责建立一致、可配置、可降级的观测入口不伪装成完整 APM。十、从散落的 log.info 到可治理的调用日志Spring Boot 接口日志真正难的不是打印一行 JSON而是统一回答哪些入口和调用需要观察每条日志有哪些稳定字段大对象输出到什么程度哪些字段必须忽略或脱敏失败、短路和序列化异常如何降级日志耗时代表同步方法还是异步任务最终结果。MetaLite 用AspectTypeEnum、AspectHandlerChain、BaseAspectLogger、PrintControl和AspectLogProperties把这些决策集中到工程基座中。业务代码因此少写日志模板团队也获得了一套可以统一审计和演进的规则。框架简介MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。源码基线JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3具体组件版本以项目backend-bom为准。作者简介15 年 Spring 体系企业级开发经验专注于 Java 微服务架构、工程治理与生产实践。持续更新MetaLite 系列内容将持续更新围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者及时获取后续内容。在线演示演示地址: https://admin.metalite.top/演示账号: guess演示密码: admin2026