统一日志怎么做

在不少 Spring Boot 项目里,统一异常已经成了“标配”。 但真正把项目做大的团队,会把 统一日志体系 放在更重要的位置。

日志不是打印一下 log.info 那么简单。 日志是:

  • 排查线上 Bug 的唯一线索

  • 追踪链路的关键依据

  • 数据分析的重要来源

  • 监控告警的触发源

  • 规范化团队工程能力的基石

  • 项目越大,日志越是“生命线”。

这篇文章,我们就把 企业级日志体系 从理念到设计再到落地,一次讲透。 让你做项目不再凭感觉乱打 log。

1 日志为什么容易乱?(普通项目 vs 企业项目)

很多项目的日志体系,都是长这样的:

开发者写多了就变成:

  • 看日志看不懂

  • 线上问题复现不出来

  • 同一个接口的调用链断裂

  • 大量 debug 信息上了生产

  • 想查某个请求,要翻几十 MB 日志

为什么?

因为缺少 统一规范 + 全链路日志设计 。

企业级项目的做法通常有三层:

  1. 日志规范(规则)

  2. 日志中间件(统一格式化)

  3. 日志链路(TraceId)

这篇文章就按照这三部分逐层展开,帮你搭出可复用的企业级模板。

2 企业项目的日志到底要统一哪些?(范围很大)

一套合格的日志体系,需要至少覆盖:

  1. 日志格式 (统一字段)

  2. 日志等级 (INFO/WARN/ERROR 的边界)

  3. 日志位置 (在哪里打,在哪里禁止打)

  4. 请求日志 (完整记录一次请求)

  5. 响应日志 (统一输出 & 脱敏)

  6. 异常日志 (归一化输出)

  7. 链路追踪 TraceId

  8. 慢接口监控

  9. 方法级别日志 AOP

  10. 敏感信息脱敏处理

  11. 生产日志滚动

  12. 审计日志 Audit Log

对,你没看错,日志体系巨大。 但我们今天把它讲得足够简单,并且给你直接能复制到项目里的代码。

3 统一日志体系的设计蓝图

先来看大局观:

你会发现:

  • 每一层都会产生日志

  • 但每一层输出的内容必须统一管理

  • 最终让一个请求成为“可完整复盘的链路”

接下来我们逐步拆开讲。

4 从零到落地:企业级日志体系构建

1. 统一日志格式(Logging Format)—— (全项目同一种格式)

统一格式非常关键,因为没有标准格式,后续 ELK、OpenSearch、语义分析都没法做。

推荐格式(JSON 是最好解析的格式):

{
"timestamp": "2025-01-15 10:20:33.123",
"traceId": "b2d7e8f1...",
"level": "INFO",
"thread": "http-nio-8080-exec-2",
"class": "com.demo.UserController",
"message": "create user success",
"cost":12,
"params": {...},
"result": {...}
}

Spring Boot 使用 Logback 非常容易实现:

logback-spring.xml 

appender name="console"class="ch.qos.logback.core.ConsoleAppender">
encoderclass="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
providers>timestamp/>pattern>pattern>"traceId": "%X{traceId}",
"level": "%level",
"thread": "%thread",
"class": "%logger{36}",
"msg": "%message"
pattern>pattern>providers>encoder>appender>

关键点: traceId 通过 MDC 传递 —— 稍后会讲。

本文由“壹伴编辑器”提供技术支持

2. 接口请求日志(Request Logging)

企业项目一般要做到:

  • 记录 URL

  • 记录请求参数

  • 记录请求耗时

  • 自动关联 TraceId

  • 可脱敏

最常见的方式:写一个 日志过滤器(Filter) 。

@Slf4j
@Component
public class RequestLogFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis;
String traceId = UUID.randomUUID.toString.replace("-", "");
MDC.put("traceId", traceId);
log.info("REQUEST [{}] {} {}", req.getMethod, req.getRequestURI, buildParams(req));
chain.doFilter(req, res);long cost = System.currentTimeMillis - start;
log.info("RESPONSE [{}ms] {}", cost, traceId);
MDC.clear;}}

本文由“壹伴编辑器”提供技术支持

3. 响应日志统一封装(配合统一返回值 Result)

在统一返回值中加入日志钩子。

例如你的 GlobalResponseAdvice 

@Slf4j
@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdviceObject> {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType contentType,Class converterType,ServerHttpRequest req, ServerHttpResponse res){
log.info("RESPONSE BODY: {}", JsonUtils.toJson(body));
return body;
}
}

这样每个响应都会自动日志化。

本文由“壹伴编辑器”提供技术支持

4. 统一异常日志(配合统一异常处理器)

你之前已经做过统一异常,但核心补一条:

所有异常必须记录 traceId + errorCode + message + stack

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public Result> handleBizException(BizException e) {
log.error("BizException: code={}, msg={}, traceId={}",
e.getCode, e.getMsg, MDC.get("traceId"), e);
return Result.error(e.getCode, e.getMsg);
}@ExceptionHandler(Exception.class)
public Result> handleOther(Exception e) {
log.error("UnhandledException: traceId={}", MDC.get("traceId"), e);
return Result.error(ErrorCode.SERVER_ERROR);
}}

做到这里, 异常链路完整可追踪 。

本文由“壹伴编辑器”提供技术支持

5. TraceId 全链路日志(MDC)核心机制

所有日志都要带上 traceId,否则无法串联。

关键点:

  1. 请求进入 Filter → 生成 traceId → 写入 MDC

  2. 后续所有日志自动带 traceId

  3. 响应结束时清除 MDC

  4. 异步线程必须手动传递 MDC(难点)

异步线程 MDC 传递方式:

public class MdcTaskWrapper implements Runnable {
private final Runnable task;
private final Map
public MdcTaskWrapper(Runnable task) {
this.task = task;
this.context = MDC.getCopyOfContextMap;
}@Override
public void run {
if (context != ) MDC.setContextMap(context);
try {
task.run;} finally {
MDC.clear;}}}

生产项目必须加这一层。

本文由“壹伴编辑器”提供技术支持

6. 方法级日志 AOP(记录耗时、参数、结果)

大量企业项目都会用 AOP 做方法级日志。

例如:

@Around("@annotation(Loggable)")
public Object logMethod(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis;
log.info("Method Start: {} args={}", pjp.getSignature, Arrays.toString(pjp.getArgs));
Object result = pjp.proceed;long cost = System.currentTimeMillis - start;
log.info("Method End: {} cost={}ms result={}", pjp.getSignature, cost, JsonUtils.toJson(result));
return result;
}

结合 注解 @Loggable 使用。

你可以让 Service/Manager 关键方法自动记录:

  • 入参

  • 出参

  • 耗时

非常适合大型项目。

本文由“壹伴编辑器”提供技术支持

7. 慢接口分析(性能监控的重要来源)

关键思路:

  • 接口耗时 > 500ms → WARN

  • 耗时 > 2000ms → ERROR

代码示例:

if (cost > 2000) {
log.error("Slow API: {} cost={}ms", uri, cost);
} else if (cost >500) {
log.warn("Slow API: {} cost={}ms", uri, cost);
}

这是定位性能问题的核心指标。

本文由“壹伴编辑器”提供技术支持

8. 日志脱敏(尤其是手机号、身份证、地址)

不要把敏感数据打印到生产日志!

使用工具类自动脱敏:

public class MaskUtils {
public static String mobile(String s) {
return s.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");
}public static String idCard(String s) {
return s.replaceAll("(\\w{4})\\w*(\\w{4})", "$1****$2");
}}

然后在日志切面里调用:

String safeParams = MaskUtils.mask(JsonUtils.toJson(args));

本文由“壹伴编辑器”提供技术支持

9. 生产日志滚动策略(避免日志撑爆磁盘)

logback-spring.xml 

rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
fileNamePattern>logs/app.%d{yyyy-MM-dd}.log.gzfileNamePattern>
maxHistory>30maxHistory>
totalSizeCap>2GBtotalSizeCap>
rollingPolicy>

保证日志合理分片、压缩、清理。

本文由“壹伴编辑器”提供技术支持

10. 审计日志(Audit Log)

适用于:

  • 后台管理系统

  • 用户权限操作

  • 金融相关

  • 重要数据修改

设计方式较简单:

@Slf4j
public void recordAudit(String operator, String action, Object data) {
log.info("[AUDIT] operator={} action={} data={}",
operator, action, JsonUtils.toJson(data));
}

企业项目几乎都会有这一块。

5 全链路示例:一次请求如何被日志完整追踪?

假设用户调用:

POST /user/create

日志链路会像这样:

RequestLogFilter:
REQUEST POST /user/create params={...}, traceId=xxx
AOP:
Method Start:UserService.create args=[...]
Repository:
SQL:insert into user...
AOP:
Method End:cost=10ms result={id: 1 }
ResponseAdvice:
RESPONSE BODY:{code: 0 ,data:{id: 1 }}
最终输出:RESPONSE [15ms] traceId=xxx

开发者只需要一个 traceId 就能追完所有链路。

总结

当你把统一日志体系落地之后,你会明显感受到:

  • 排查问题速度成倍提升

  • 团队开发一致性变更强

  • 异常链路一目了然

  • 性能问题易复现

  • 业务变化更可控

  • 整个项目可观测性跃升一个层级

日志不是“系统附属品”。 日志是你与系统沟通的唯一语言。 而统一日志体系,是这套语言的语法、句法和风格指南。

posted @ 2025-12-10 20:30  智慧园区-老朱  阅读(2)  评论(0)    收藏  举报