从P4小白到P7专家都是怎么打日志的?一文揭秘大厂日志规范

从P4小白到P7专家都是怎么打日志的?一文揭秘大厂日志规范

大家好,今天我们来聊聊一个让无数程序员又爱又恨的话题——日志打印。

你是否也遇到过这些场景:

  • 线上出问题了,翻遍日志却找不到关键信息
  • 日志文件几个G,根本没法看
  • 用户投诉了,但日志里没有用户ID,无法定位问题
  • 开发环境日志详细,生产环境却一片空白
  • 想查问题,却被海量无用日志淹没了

别慌!今天我就把这套从P4小白到P7专家的日志打印规范全掏出来,手把手教你写出高质量的日志!

为什么日志这么重要?

在开始正题之前,先聊聊为什么日志如此重要:

  1. 问题排查:90%的线上问题都靠日志来定位
  2. 系统监控:通过日志可以监控系统运行状态
  3. 安全审计:记录用户操作,防范安全风险
  4. 性能分析:通过日志分析系统性能瓶颈
  5. 合规要求:很多行业对日志有明确要求

P4小白阶段:能跑就行

刚入行的程序员通常是这样的:

// P4小白写法 - 各种问题
@RestController
public class OrderController {
    
    @PostMapping("/api/orders")
    public Result createOrder(@RequestBody OrderRequest request) {
        System.out.println("开始创建订单");
        
        try {
            Order order = orderService.create(request);
            System.out.println("订单创建成功: " + order.getId());
            return Result.success(order);
        } catch (Exception e) {
            System.out.println("订单创建失败: " + e.getMessage());
            return Result.error("创建失败");
        }
    }
}

问题分析

  • 使用System.out.println,无法控制级别
  • 没有结构化信息,难以解析
  • 缺少关键上下文信息
  • 生产环境可能看不到日志

P5进阶阶段:开始用日志框架

有经验的程序员会使用专业的日志框架:

// P5进阶写法 - 使用SLF4J
@RestController
@Slf4j
public class OrderController {
    
    @PostMapping("/api/orders")
    public Result createOrder(@RequestBody OrderRequest request) {
        log.info("开始创建订单,用户ID: {}, 商品ID: {}", 
                request.getUserId(), request.getProductId());
        
        try {
            Order order = orderService.create(request);
            log.info("订单创建成功,订单ID: {}", order.getId());
            return Result.success(order);
        } catch (Exception e) {
            log.error("订单创建失败,用户ID: {}, 错误信息: {}", 
                     request.getUserId(), e.getMessage());
            return Result.error("创建失败");
        }
    }
}

改进点

  • 使用专业日志框架
  • 添加了关键业务信息
  • 区分了不同日志级别

P6高手阶段:规范化的日志体系

高级工程师会建立完整的日志规范:

// P6高手写法 - 规范化日志
@RestController
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private TraceContext traceContext;
    
    @PostMapping("/api/orders")
    public Result createOrder(@RequestBody @Valid OrderRequest request) {
        // 生成追踪ID
        String traceId = traceContext.getTraceId();
        
        // 记录入口日志
        log.info("[{}] [ORDER_CREATE] 开始创建订单 | userId={}, productId={}, quantity={}", 
                traceId, request.getUserId(), request.getProductId(), request.getQuantity());
        
        long startTime = System.currentTimeMillis();
        
        try {
            // 业务处理
            Order order = orderService.create(request);
            
            // 记录成功日志
            long cost = System.currentTimeMillis() - startTime;
            log.info("[{}] [ORDER_CREATE] 订单创建成功 | orderId={}, cost={}ms", 
                    traceId, order.getId(), cost);
            
            return Result.success(order);
        } catch (BusinessException e) {
            // 业务异常
            long cost = System.currentTimeMillis() - startTime;
            log.warn("[{}] [ORDER_CREATE] 业务异常 | userId={}, code={}, message={}, cost={}ms", 
                    traceId, request.getUserId(), e.getCode(), e.getMessage(), cost);
            return Result.error(e.getCode(), e.getMessage());
        } catch (Exception e) {
            // 系统异常
            long cost = System.currentTimeMillis() - startTime;
            log.error("[{}] [ORDER_CREATE] 系统异常 | userId={}, error={}, cost={}ms", 
                    traceId, request.getUserId(), e.getMessage(), cost, e);
            return Result.error("系统繁忙,请稍后重试");
        }
    }
}

规范要点

  1. 统一格式:[追踪ID] [业务标识] 日志内容
  2. 关键信息:用户ID、订单ID、耗时等
  3. 异常处理:区分业务异常和系统异常
  4. 性能监控:记录方法执行耗时

P7专家阶段:全链路追踪日志

架构师级别的日志体系会考虑全链路追踪:

// P7专家写法 - 全链路追踪
@RestController
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private TraceService traceService;
    
    @PostMapping("/api/orders")
    @Trace(operation = "订单创建", module = "订单系统")
    public Result createOrder(@RequestBody @Valid OrderRequest request) {
        // 获取追踪上下文
        TraceContext context = traceService.getCurrentContext();
        
        // 记录入口日志
        log.info("[{}] [ORDER_CREATE] 接收到订单创建请求 | userId={}, clientIp={}, userAgent={}", 
                context.getTraceId(), 
                request.getUserId(), 
                context.getClientIp(), 
                context.getUserAgent());
        
        // 参数校验日志
        if (request.getQuantity() <= 0) {
            log.warn("[{}] [ORDER_CREATE] 参数校验失败 | userId={}, quantity={}", 
                    context.getTraceId(), request.getUserId(), request.getQuantity());
            return Result.error("商品数量必须大于0");
        }
        
        long startTime = System.currentTimeMillis();
        long dbStartTime, dbEndTime;
        
        try {
            // 调用库存服务前
            log.debug("[{}] [INVENTORY_CHECK] 开始检查库存 | productId={}, quantity={}", 
                    context.getTraceId(), request.getProductId(), request.getQuantity());
            
            dbStartTime = System.currentTimeMillis();
            boolean hasStock = inventoryService.checkStock(request.getProductId(), request.getQuantity());
            dbEndTime = System.currentTimeMillis();
            
            log.debug("[{}] [INVENTORY_CHECK] 库存检查完成 | productId={}, hasStock={}, dbCost={}ms", 
                    context.getTraceId(), request.getProductId(), hasStock, dbEndTime - dbStartTime);
            
            if (!hasStock) {
                log.warn("[{}] [ORDER_CREATE] 库存不足 | userId={}, productId={}, quantity={}", 
                        context.getTraceId(), request.getUserId(), request.getProductId(), request.getQuantity());
                return Result.error("库存不足");
            }
            
            // 创建订单
            dbStartTime = System.currentTimeMillis();
            Order order = orderService.create(request);
            dbEndTime = System.currentTimeMillis();
            
            long totalCost = System.currentTimeMillis() - startTime;
            log.info("[{}] [ORDER_CREATE] 订单创建成功 | userId={}, orderId={}, totalCost={}ms, dbCost={}ms", 
                    context.getTraceId(), request.getUserId(), order.getId(), totalCost, dbEndTime - dbStartTime);
            
            // 异步发送消息
            messageService.sendOrderCreatedEvent(order);
            
            return Result.success(order);
        } catch (BusinessException e) {
            long cost = System.currentTimeMillis() - startTime;
            log.warn("[{}] [ORDER_CREATE] 业务异常 | userId={}, code={}, message={}, cost={}ms", 
                    context.getTraceId(), request.getUserId(), e.getCode(), e.getMessage(), cost);
            return Result.error(e.getCode(), e.getMessage());
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - startTime;
            log.error("[{}] [ORDER_CREATE] 系统异常 | userId={}, error={}, cost={}ms | stackTrace={}", 
                    context.getTraceId(), request.getUserId(), e.getMessage(), cost, 
                    ExceptionUtils.getStackTrace(e));
            return Result.error("系统繁忙,请稍后重试");
        }
    }
}

专家级特性

  1. 全链路追踪:统一的追踪ID贯穿整个调用链
  2. 详细监控:记录每个关键步骤的耗时
  3. 分类日志:不同业务模块使用不同标识
  4. 异常堆栈:关键异常记录完整堆栈信息

10个日志打印黄金法则

1. 使用占位符,避免字符串拼接

// 错误写法
log.info("用户" + userId + "的订单" + orderId + "创建成功");

// 正确写法
log.info("用户{}的订单{}创建成功", userId, orderId);

2. 合理使用日志级别

// ERROR - 系统错误、数据丢失等严重问题
log.error("数据库连接失败", e);

// WARN - 可预期的异常情况
log.warn("用户{}尝试访问无权限资源", userId);

// INFO - 重要的业务流程节点
log.info("订单{}支付成功", orderId);

// DEBUG - 详细的调试信息
log.debug("方法执行参数: {}", params);

3. 添加关键业务上下文

// 包含追踪ID、用户ID、业务ID等关键信息
log.info("[{}] [USER_LOGIN] 用户登录成功 | userId={}, loginTime={}", 
        traceId, userId, LocalDateTime.now());

4. 记录方法执行耗时

long startTime = System.currentTimeMillis();
try {
    // 业务逻辑
    doSomething();
} finally {
    long cost = System.currentTimeMillis() - startTime;
    log.info("方法执行完成,耗时: {}ms", cost);
}

5. 异常日志要记录堆栈

try {
    // 业务逻辑
} catch (Exception e) {
    log.error("业务处理异常 | userId={}", userId, e);
}

6. 避免在循环中打印日志

// 错误写法
for (Item item : items) {
    log.info("处理商品: {}", item.getId()); // 性能问题
}

// 正确写法
log.info("开始处理{}个商品", items.size());
// 批量处理逻辑
log.info("商品处理完成");

7. 敏感信息要脱敏

// 错误写法
log.info("用户手机号: {}", phoneNumber);

// 正确写法
log.info("用户手机号: {}", maskPhone(phoneNumber));

private String maskPhone(String phone) {
    if (phone == null || phone.length() < 7) {
        return phone;
    }
    return phone.substring(0, 3) + "****" + phone.substring(7);
}

8. 日志格式要统一

// 推荐格式:[追踪ID] [业务标识] 日志内容 | key=value
log.info("[{}] [ORDER_PAY] 支付成功 | userId={}, orderId={}, amount={}", 
        traceId, userId, orderId, amount);

9. 避免重复日志

// 错误写法 - 在多层调用中重复记录相同信息
public void methodA() {
    log.info("进入方法A");
    methodB();
    log.info("退出方法A");
}

public void methodB() {
    log.info("进入方法B"); // 重复日志
    // 业务逻辑
    log.info("退出方法B"); // 重复日志
}

10. 生产环境日志级别要合理

# logback-spring.xml
<configuration>
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
    
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>
</configuration>

实战案例:某电商平台支付日志优化

优化前:混乱的日志

@Service
public class PaymentService {
    
    public void processPayment(PaymentRequest request) {
        System.out.println("开始支付处理");
        
        try {
            // 调用第三方支付
            String result = thirdPartyPayService.pay(request.getAmount(), request.getCardNo());
            System.out.println("支付结果: " + result);
        } catch (Exception e) {
            System.out.println("支付失败: " + e.getMessage());
        }
    }
}

优化后:规范的日志

@Service
@Slf4j
public class PaymentService {
    
    @Autowired
    private TraceService traceService;
    
    @Autowired
    private ThirdPartyPayService thirdPartyPayService;
    
    @Trace(operation = "支付处理", module = "支付中心")
    public PaymentResult processPayment(PaymentRequest request) {
        TraceContext context = traceService.getCurrentContext();
        
        // 入口日志
        log.info("[{}] [PAYMENT_PROCESS] 开始支付处理 | userId={}, amount={}, paymentMethod={}", 
                context.getTraceId(), request.getUserId(), request.getAmount(), request.getPaymentMethod());
        
        long startTime = System.currentTimeMillis();
        
        try {
            // 参数校验
            validatePaymentRequest(request);
            
            // 调用第三方支付前日志
            log.debug("[{}] [THIRD_PARTY_PAY] 调用第三方支付 | amount={}, cardNo={}", 
                    context.getTraceId(), request.getAmount(), maskCardNo(request.getCardNo()));
            
            long payStartTime = System.currentTimeMillis();
            ThirdPartyPayResult payResult = thirdPartyPayService.pay(request);
            long payCost = System.currentTimeMillis() - payStartTime;
            
            // 第三方支付结果日志
            log.debug("[{}] [THIRD_PARTY_PAY] 第三方支付返回 | result={}, cost={}ms", 
                    context.getTraceId(), payResult.isSuccess(), payCost);
            
            if (!payResult.isSuccess()) {
                log.warn("[{}] [PAYMENT_PROCESS] 第三方支付失败 | userId={}, errorCode={}, errorMsg={}", 
                        context.getTraceId(), request.getUserId(), 
                        payResult.getErrorCode(), payResult.getErrorMsg());
                return PaymentResult.failure(payResult.getErrorCode(), payResult.getErrorMsg());
            }
            
            // 保存支付记录
            long saveStartTime = System.currentTimeMillis();
            PaymentRecord record = savePaymentRecord(request, payResult);
            long saveCost = System.currentTimeMillis() - saveStartTime;
            
            long totalCost = System.currentTimeMillis() - startTime;
            log.info("[{}] [PAYMENT_PROCESS] 支付处理成功 | userId={}, paymentId={}, totalCost={}ms, payCost={}ms, saveCost={}ms", 
                    context.getTraceId(), request.getUserId(), record.getId(), 
                    totalCost, payCost, saveCost);
            
            return PaymentResult.success(record);
            
        } catch (ValidationException e) {
            long cost = System.currentTimeMillis() - startTime;
            log.warn("[{}] [PAYMENT_PROCESS] 参数校验失败 | userId={}, errors={}, cost={}ms", 
                    context.getTraceId(), request.getUserId(), e.getErrors(), cost);
            return PaymentResult.failure("PARAM_ERROR", e.getMessage());
        } catch (BusinessException e) {
            long cost = System.currentTimeMillis() - startTime;
            log.warn("[{}] [PAYMENT_PROCESS] 业务异常 | userId={}, code={}, message={}, cost={}ms", 
                    context.getTraceId(), request.getUserId(), e.getCode(), e.getMessage(), cost);
            return PaymentResult.failure(e.getCode(), e.getMessage());
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - startTime;
            log.error("[{}] [PAYMENT_PROCESS] 系统异常 | userId={}, error={}, cost={}ms", 
                    context.getTraceId(), request.getUserId(), e.getMessage(), cost, e);
            return PaymentResult.failure("SYSTEM_ERROR", "系统繁忙");
        }
    }
    
    private String maskCardNo(String cardNo) {
        if (cardNo == null || cardNo.length() < 8) {
            return "****";
        }
        return cardNo.substring(0, 4) + "****" + cardNo.substring(cardNo.length() - 4);
    }
}

5个核心监控指标,让你的日志发挥最大价值

1. 错误率监控

@Component
public class LogMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    
    public void recordError(String module, String operation) {
        Counter.builder("log.errors")
                .description("日志错误统计")
                .tag("module", module)
                .tag("operation", operation)
                .register(meterRegistry)
                .increment();
    }
}

2. 响应时间监控

public void recordResponseTime(String module, String operation, long durationMs) {
    Timer.builder("log.response.time")
            .description("接口响应时间")
            .tag("module", module)
            .tag("operation", operation)
            .register(meterRegistry)
            .record(durationMs, TimeUnit.MILLISECONDS);
}

3. 日志量监控

public void recordLogCount(String level, String module) {
    Counter.builder("log.count")
            .description("日志数量统计")
            .tag("level", level)
            .tag("module", module)
            .register(meterRegistry)
            .increment();
}

4. 业务指标监控

public void recordBusinessMetric(String metricName, String businessType, long value) {
    Gauge.builder("business." + metricName)
            .description("业务指标")
            .tag("type", businessType)
            .register(meterRegistry, value);
}

5. 异常类型监控

public void recordException(String exceptionType, String module) {
    Counter.builder("log.exceptions")
            .description("异常类型统计")
            .tag("type", exceptionType)
            .tag("module", module)
            .register(meterRegistry)
            .increment();
}

结语

掌握日志打印规范,核心不是记住所有规则,而是理解每个规范背后的业务价值和技术考量

  • P4小白:能打印日志就行
  • P5进阶:使用专业日志框架
  • P6高手:建立规范化日志体系
  • P7专家:构建全链路追踪日志

记住:好的日志不是一次到位的,而是在实践中不断优化的。从满足基本需求开始,根据实际情况逐步完善,最终你也能构建出高质量的日志体系!

日志规范只是系统稳定运行的一部分,后续我们还会分享更多监控和优化的实战技巧,记得关注我们的公众号"服务端技术精选"!

觉得这篇文章对你有帮助吗?欢迎点赞、在看、转发三连,你的支持是我们持续创作的最大动力!


服务端技术精选 | 专注分享实用的后端技术干货


标题:从P4小白到P7专家都是怎么打日志的?一文揭秘大厂日志规范
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304291741.html

    0 评论
avatar