Spring Boot/Spring Cloud 应用日志书写详细指南

Spring Boot/Spring Cloud 应用日志书写详细指南

1. 日志框架选择与配置

1.1 推荐日志框架组合

<!-- Spring Boot 默认使用,无需额外引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

<!-- 如需使用 Log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

1.2 基础配置

# application.yml
logging:
  level:
    com.yourcompany: INFO
    org.springframework: WARN
    org.hibernate: ERROR
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30

2. 日志级别规范

2.1 级别定义与使用场景

@Component
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User createUser(UserDTO userDTO) {
        // TRACE: 详细的调试信息,通常在生产环境关闭
        logger.trace("开始创建用户,输入参数: {}", userDTO);
        
        // DEBUG: 调试信息,用于开发阶段问题定位
        logger.debug("用户数据验证通过,准备持久化");
        
        try {
            User user = userRepository.save(convertToEntity(userDTO));
            
            // INFO: 重要的业务流程节点
            logger.info("用户创建成功,用户ID: {}, 用户名: {}", user.getId(), user.getUsername());
            
            return user;
        } catch (DataIntegrityViolationException e) {
            // WARN: 非预期但可处理的情况
            logger.warn("用户数据重复,用户名: {}", userDTO.getUsername(), e);
            throw new BusinessException("用户已存在");
        } catch (Exception e) {
            // ERROR: 系统错误和异常情况
            logger.error("用户创建失败,用户名: {}", userDTO.getUsername(), e);
            throw new SystemException("系统内部错误");
        }
    }
}

3. 日志内容规范

3.1 结构化日志格式

@Service
@Slf4j // 推荐使用 Lombok @Slf4j 注解
public class OrderService {
    
    public Order createOrder(OrderRequest request) {
        // 关键业务操作开始日志
        log.info("创建订单开始 | 用户ID:{} | 商品数量:{} | 总金额:{}", 
                request.getUserId(), 
                request.getItems().size(),
                request.getTotalAmount());
        
        try {
            // 业务逻辑处理
            Order order = processOrder(request);
            
            // 成功日志包含关键业务ID
            log.info("订单创建成功 | 订单ID:{} | 状态:{} | 用户ID:{}", 
                    order.getId(), 
                    order.getStatus(),
                    order.getUserId());
            
            return order;
        } catch (InventoryException e) {
            // 异常日志包含上下文信息和异常
            log.error("库存不足创建订单失败 | 用户ID:{} | 错误码:{}", 
                     request.getUserId(), 
                     "INSUFFICIENT_INVENTORY", e);
            throw e;
        }
    }
}

3.2 JSON 结构化日志(推荐用于微服务)

@Component
public class StructuredLogger {
    
    public void logPaymentEvent(PaymentEvent event) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("event", "payment_processed");
        logData.put("orderId", event.getOrderId());
        logData.put("amount", event.getAmount());
        logData.put("paymentMethod", event.getPaymentMethod());
        logData.put("timestamp", Instant.now().toString());
        logData.put("traceId", MDC.get("traceId"));
        
        log.info(JSON.toJSONString(logData));
    }
    
    public void logError(String errorCode, String message, String module, Throwable t) {
        Map<String, Object> errorLog = new HashMap<>();
        errorLog.put("level", "ERROR");
        errorLog.put("errorCode", errorCode);
        errorLog.put("message", message);
        errorLog.put("module", module);
        errorLog.put("timestamp", Instant.now().toString());
        errorLog.put("stackTrace", getStackTrace(t));
        errorLog.put("traceId", MDC.get("traceId"));
        
        log.error(JSON.toJSONString(errorLog));
    }
    
    private String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }
}

4. 日志上下文信息

4.1 使用 MDC (Mapped Diagnostic Context)

@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    @Around("execution(* com.yourcompany..*Controller.*(..))")
    public Object logController(ProceedingJoinPoint joinPoint) throws Throwable {
        // 设置请求上下文
        MDC.put("traceId", generateTraceId());
        MDC.put("userId", getCurrentUserId());
        MDC.put("clientIp", getClientIp());
        
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        
        log.info("请求开始 | 类:{} | 方法:{}", className, methodName);
        
        long startTime = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long executionTime = System.currentTimeMillis() - startTime;
            
            log.info("请求完成 | 类:{} | 方法:{} | 耗时:{}ms", 
                    className, methodName, executionTime);
            return result;
        } catch (Exception e) {
            long executionTime = System.currentTimeMillis() - startTime;
            log.error("请求异常 | 类:{} | 方法:{} | 耗时:{}ms | 异常:{}", 
                     className, methodName, executionTime, e.getMessage(), e);
            throw e;
        } finally {
            // 清理 MDC
            MDC.clear();
        }
    }
}

4.2 请求链路追踪

@Component
public class TraceFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String traceId = httpRequest.getHeader("X-Trace-Id");
        if (traceId == null || traceId.isEmpty()) {
            traceId = generateTraceId();
        }
        
        MDC.put("traceId", traceId);
        MDC.put("requestUri", httpRequest.getRequestURI());
        MDC.put("userAgent", httpRequest.getHeader("User-Agent"));
        
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

5. 性能优化建议

5.1 避免日志性能问题

@Service
@Slf4j
public class PerformanceSensitiveService {
    
    // 不好的写法:在调试级别进行字符串拼接
    public void badLogging(User user) {
        // 即使日志级别高于DEBUG,字符串拼接仍然会执行
        log.debug("用户信息: " + user.toString() + ", 时间: " + System.currentTimeMillis());
    }
    
    // 好的写法:使用参数化日志
    public void goodLogging(User user) {
        // 只有在DEBUG级别启用时才会进行字符串处理
        log.debug("用户信息: {}, 时间: {}", user, System.currentTimeMillis());
    }
    
    // 对于复杂的日志消息,使用条件判断
    public void conditionalLogging(List<User> users) {
        if (log.isDebugEnabled()) {
            log.debug("处理用户列表: {}", formatUserList(users)); // formatUserList 比较耗时
        }
    }
}

6. 异常日志处理

6.1 统一的异常日志处理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        // WARN 级别记录业务异常
        log.warn("业务异常 | 错误码:{} | 错误信息:{} | 请求路径:{}", 
                e.getErrorCode(), e.getMessage(), getCurrentRequestPath());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(e.getErrorCode(), e.getMessage()));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleSystemException(Exception e) {
        // ERROR 级别记录系统异常
        log.error("系统异常 | 异常类型:{} | 请求路径:{}", 
                 e.getClass().getSimpleName(), getCurrentRequestPath(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("SYSTEM_ERROR", "系统内部错误"));
    }
}

7. 日志配置文件示例

7.1 Logback 配置 (logback-spring.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n"/>
    <property name="LOG_PATH" value="logs"/>
    
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    
    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    
    <!-- 错误日志单独文件 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/error.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
    
    <!-- 特定包日志级别 -->
    <logger name="com.yourcompany" level="DEBUG"/>
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate" level="WARN"/>
</configuration>

8. 最佳实践总结

8.1 必须遵循的原则

  1. 适当的日志级别:生产环境使用 INFO,开发环境使用 DEBUG
  2. 有意义的日志消息:包含足够的上下文信息
  3. 统一格式:团队使用统一的日志格式
  4. 性能考虑:避免在日志中执行耗时操作
  5. 安全考虑:不要记录敏感信息(密码、token等)

8.2 推荐的日志场景

// 1. 方法入口和出口
log.info("方法开始: {},参数: {}", methodName, parameters);
log.info("方法结束: {},结果: {}", methodName, result);

// 2. 业务流程关键节点
log.info("订单状态变更 | 订单ID:{} | 原状态:{} | 新状态:{}", orderId, oldStatus, newStatus);

// 3. 外部调用
log.info("调用外部服务开始 | 服务名:{} | 请求参数:{}", serviceName, request);
log.info("调用外部服务结束 | 服务名:{} | 响应结果:{}", serviceName, response);

// 4. 异常情况
log.error("数据库操作失败 | SQL:{} | 参数:{}", sql, params, e);

// 5. 定时任务
log.info("定时任务开始 | 任务名:{} | 执行时间:{}", taskName, startTime);
log.info("定时任务结束 | 任务名:{} | 执行结果:{} | 耗时:{}ms", taskName, result, costTime);

通过遵循这些指南,可以建立统一、可维护、高效的日志系统,大大提高系统的可观测性和问题排查效率。

Spring Boot/Cloud 全场景日志书写示例

1. 控制器层日志

1.1 REST Controller 完整示例

@RestController
@RequestMapping("/api/v1/users")
@Slf4j
public class UserController {
    
    @GetMapping("/{userId}")
    public ResponseEntity<UserVO> getUser(@PathVariable Long userId,
                                         @RequestHeader("User-Agent") String userAgent) {
        // 请求入口日志
        log.info("查询用户开始 | 用户ID:{} | 客户端:{} | 线程:{}", 
                userId, userAgent, Thread.currentThread().getName());
        
        long startTime = System.currentTimeMillis();
        try {
            UserVO user = userService.getUserById(userId);
            
            // 成功响应日志
            log.info("查询用户成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                    userId, user.getUsername(), System.currentTimeMillis() - startTime);
            
            return ResponseEntity.ok(user);
        } catch (UserNotFoundException e) {
            // 业务异常日志
            log.warn("用户不存在 | 用户ID:{} | 耗时:{}ms", 
                    userId, System.currentTimeMillis() - startTime, e);
            return ResponseEntity.notFound().build();
        } catch (Exception e) {
            // 系统异常日志
            log.error("查询用户异常 | 用户ID:{} | 异常类型:{} | 耗时:{}ms", 
                     userId, e.getClass().getSimpleName(), 
                     System.currentTimeMillis() - startTime, e);
            return ResponseEntity.internalServerError().build();
        }
    }
    
    @PostMapping
    public ResponseEntity<CreateUserResponse> createUser(@Valid @RequestBody CreateUserRequest request,
                                                        HttpServletRequest httpRequest) {
        String clientIp = httpRequest.getRemoteAddr();
        log.info("创建用户开始 | 用户名:{} | 邮箱:{} | 客户端IP:{}", 
                request.getUsername(), request.getEmail(), clientIp);
        
        // 敏感信息脱敏
        log.debug("创建用户详细参数 | 用户名:{} | 角色:{} | 部门:{}", 
                 request.getUsername(), request.getRole(), request.getDepartment());
        
        long startTime = System.currentTimeMillis();
        try {
            UserVO user = userService.createUser(request);
            
            log.info("创建用户成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                    user.getId(), user.getUsername(), System.currentTimeMillis() - startTime);
            
            return ResponseEntity.status(HttpStatus.CREATED)
                    .body(new CreateUserResponse(user.getId(), "创建成功"));
        } catch (DuplicateUserException e) {
            log.warn("创建用户失败-用户已存在 | 用户名:{} | 邮箱:{}", 
                    request.getUsername(), request.getEmail(), e);
            return ResponseEntity.badRequest()
                    .body(new CreateUserResponse(null, "用户已存在"));
        }
    }
    
    @PutMapping("/{userId}/status")
    public ResponseEntity<Void> updateUserStatus(@PathVariable Long userId,
                                                @RequestBody UpdateStatusRequest request) {
        log.info("更新用户状态开始 | 用户ID:{} | 原状态:{} | 新状态:{}", 
                userId, getCurrentStatus(userId), request.getStatus());
        
        userService.updateStatus(userId, request.getStatus());
        
        log.info("更新用户状态成功 | 用户ID:{} | 新状态:{}", userId, request.getStatus());
        return ResponseEntity.ok().build();
    }
}

2. 服务层日志

2.1 业务服务完整示例

@Service
@Slf4j
@Transactional
public class OrderService {
    
    public Order createOrder(CreateOrderRequest request) {
        log.info("创建订单开始 | 用户ID:{} | 商品数量:{} | 总金额:{}", 
                request.getUserId(), request.getItems().size(), request.getTotalAmount());
        
        // 参数验证日志
        if (log.isDebugEnabled()) {
            log.debug("订单详细参数 | 商品列表:{} | 收货地址:{}", 
                     request.getItems(), maskSensitiveInfo(request.getAddress()));
        }
        
        try {
            // 1. 验证库存
            log.debug("开始验证库存 | 商品数量:{}", request.getItems().size());
            inventoryService.validateStock(request.getItems());
            log.debug("库存验证通过");
            
            // 2. 计算价格
            log.debug("开始计算订单价格");
            BigDecimal finalAmount = calculateFinalAmount(request);
            log.debug("价格计算完成 | 最终金额:{}", finalAmount);
            
            // 3. 创建订单
            Order order = buildOrder(request, finalAmount);
            order = orderRepository.save(order);
            log.info("订单创建完成 | 订单ID:{} | 订单号:{} | 状态:{}", 
                    order.getId(), order.getOrderNo(), order.getStatus());
            
            // 4. 扣减库存
            log.debug("开始扣减库存 | 订单ID:{}", order.getId());
            inventoryService.deductStock(order.getId(), request.getItems());
            log.debug("库存扣减完成");
            
            // 5. 发送创建事件
            eventPublisher.publishEvent(new OrderCreatedEvent(order));
            log.debug("订单创建事件已发布");
            
            log.info("订单流程全部完成 | 订单ID:{} | 总耗时:{}ms", 
                    order.getId(), System.currentTimeMillis() - startTime);
            
            return order;
        } catch (InventoryException e) {
            log.error("创建订单失败-库存不足 | 用户ID:{} | 错误详情:{}", 
                     request.getUserId(), e.getMessage(), e);
            throw new BusinessException("库存不足,创建订单失败");
        } catch (PaymentException e) {
            log.error("创建订单失败-支付异常 | 用户ID:{} | 错误码:{}", 
                     request.getUserId(), e.getErrorCode(), e);
            throw new BusinessException("支付处理失败");
        } catch (Exception e) {
            log.error("创建订单失败-系统异常 | 用户ID:{} | 异常类型:{}", 
                     request.getUserId(), e.getClass().getSimpleName(), e);
            throw new SystemException("系统繁忙,请稍后重试");
        }
    }
    
    public void processExpiredOrders() {
        log.info("开始处理过期订单 | 线程:{}", Thread.currentThread().getName());
        
        int processedCount = 0;
        int successCount = 0;
        int failureCount = 0;
        long startTime = System.currentTimeMillis();
        
        try {
            List<Order> expiredOrders = orderRepository.findExpiredOrders();
            log.info("找到过期订单数量:{}", expiredOrders.size());
            
            for (Order order : expiredOrders) {
                processedCount++;
                try {
                    log.debug("处理过期订单 | 订单ID:{} | 订单号:{}", order.getId(), order.getOrderNo());
                    cancelOrder(order.getId(), "系统自动取消-超时未支付");
                    successCount++;
                    log.debug("过期订单处理成功 | 订单ID:{}", order.getId());
                } catch (Exception e) {
                    failureCount++;
                    log.error("处理过期订单失败 | 订单ID:{} | 异常:{}", order.getId(), e.getMessage(), e);
                }
            }
            
            log.info("过期订单处理完成 | 总数:{} | 成功:{} | 失败:{} | 总耗时:{}ms", 
                    processedCount, successCount, failureCount, 
                    System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("处理过期订单任务异常 | 异常类型:{}", e.getClass().getSimpleName(), e);
            throw e;
        }
    }
}

3. 数据访问层日志

3.1 Repository 日志示例

@Repository
@Slf4j
public class UserRepository {
    
    public User findByUsername(String username) {
        log.debug("根据用户名查询用户 | 用户名:{}", username);
        
        long startTime = System.currentTimeMillis();
        try {
            User user = userMapper.selectByUsername(username);
            
            if (user == null) {
                log.debug("用户不存在 | 用户名:{} | 耗时:{}ms", 
                         username, System.currentTimeMillis() - startTime);
            } else {
                log.debug("用户查询成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                         user.getId(), username, System.currentTimeMillis() - startTime);
            }
            
            return user;
        } catch (DataAccessException e) {
            log.error("数据库查询异常 | SQL:查询用户 | 参数:{} | 异常类型:{}", 
                     username, e.getClass().getSimpleName(), e);
            throw new RepositoryException("用户查询失败", e);
        }
    }
    
    @Transactional
    public void updateUserStatus(Long userId, UserStatus newStatus) {
        UserStatus oldStatus = getUserStatus(userId);
        log.info("更新用户状态 | 用户ID:{} | 原状态:{} | 新状态:{}", userId, oldStatus, newStatus);
        
        try {
            int affectedRows = userMapper.updateStatus(userId, newStatus);
            
            if (affectedRows == 0) {
                log.warn("更新用户状态失败-记录不存在 | 用户ID:{}", userId);
                throw new EntityNotFoundException("用户不存在");
            }
            
            log.info("用户状态更新成功 | 用户ID:{} | 影响行数:{}", userId, affectedRows);
        } catch (DataIntegrityViolationException e) {
            log.error("更新用户状态失败-数据完整性异常 | 用户ID:{} | 新状态:{}", userId, newStatus, e);
            throw new RepositoryException("数据完整性约束冲突", e);
        }
    }
    
    public Page<User> findUsersByCondition(UserQuery query, Pageable pageable) {
        if (log.isDebugEnabled()) {
            log.debug("分页查询用户 | 查询条件:{} | 分页参数:{}", 
                     query.toString(), pageable.toString());
        }
        
        long startTime = System.currentTimeMillis();
        try {
            Page<User> result = userMapper.selectByCondition(query, pageable);
            
            log.info("用户分页查询完成 | 总数:{} | 当前页:{} | 耗时:{}ms", 
                    result.getTotalElements(), result.getNumber(), 
                    System.currentTimeMillis() - startTime);
            
            return result;
        } catch (DataAccessException e) {
            log.error("用户分页查询异常 | 查询条件:{} | 异常类型:{}", 
                     query, e.getClass().getSimpleName(), e);
            throw new RepositoryException("用户查询失败", e);
        }
    }
}

4. 外部服务调用日志

4.1 HTTP 客户端调用

@Component
@Slf4j
public class PaymentServiceClient {
    
    public PaymentResponse createPayment(PaymentRequest request) {
        String requestId = generateRequestId();
        log.info("调用支付服务开始 | 请求ID:{} | 订单号:{} | 金额:{} | 端点:{}", 
                requestId, request.getOrderNo(), request.getAmount(), "/api/payments");
        
        long startTime = System.currentTimeMillis();
        try {
            // 记录请求详情(敏感信息脱敏)
            if (log.isDebugEnabled()) {
                log.debug("支付请求详情 | 请求ID:{} | 支付方式:{} | 银行编码:{}", 
                         requestId, request.getPaymentMethod(), 
                         maskBankCode(request.getBankCode()));
            }
            
            PaymentResponse response = restTemplate.postForObject(
                paymentServiceUrl + "/api/payments", request, PaymentResponse.class);
            
            log.info("支付服务调用成功 | 请求ID:{} | 支付状态:{} | 耗时:{}ms", 
                    requestId, response.getStatus(), System.currentTimeMillis() - startTime);
            
            return response;
        } catch (HttpClientErrorException e) {
            log.warn("支付服务调用失败-客户端错误 | 请求ID:{} | 状态码:{} | 响应:{}", 
                    requestId, e.getStatusCode(), e.getResponseBodyAsString(), e);
            throw new PaymentException("支付请求参数错误");
        } catch (HttpServerErrorException e) {
            log.error("支付服务调用失败-服务端错误 | 请求ID:{} | 状态码:{} | 异常:{}", 
                     requestId, e.getStatusCode(), e.getMessage(), e);
            throw new PaymentException("支付服务暂时不可用");
        } catch (ResourceAccessException e) {
            log.error("支付服务调用失败-网络异常 | 请求ID:{} | 异常类型:{}", 
                     requestId, e.getClass().getSimpleName(), e);
            throw new PaymentException("网络连接超时");
        } catch (Exception e) {
            log.error("支付服务调用失败-未知异常 | 请求ID:{} | 异常类型:{}", 
                     requestId, e.getClass().getSimpleName(), e);
            throw new PaymentException("支付处理失败");
        }
    }
}

4.2 Feign Client 调用

@FeignClient(name = "inventory-service")
@Slf4j
public class InventoryServiceClient {
    
    @PostMapping("/api/inventory/deduct")
    ResponseEntity<InventoryResponse> deductStock(@RequestBody DeductRequest request);
    
    // Feign 配置类中的日志
    @Component
    @Slf4j
    public class FeignLogger extends Logger {
        @Override
        protected void log(String configKey, String format, Object... args) {
            if (log.isInfoEnabled()) {
                log.info(String.format(methodTag(configKey) + format, args));
            }
        }
    }
}

5. 消息队列处理日志

5.1 RabbitMQ 消费者

@Component
@Slf4j
public class OrderMessageConsumer {
    
    @RabbitListener(queues = "order.create.queue")
    public void handleOrderCreate(OrderCreateMessage message, Channel channel, 
                                @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        log.info("收到订单创建消息 | 消息ID:{} | 订单号:{} | 路由键:{}", 
                message.getMessageId(), message.getOrderNo(), "order.create");
        
        long startTime = System.currentTimeMillis();
        try {
            // 业务处理
            orderService.processOrderCreate(message);
            
            // 手动确认消息
            channel.basicAck(deliveryTag, false);
            log.info("订单消息处理成功 | 消息ID:{} | 订单号:{} | 耗时:{}ms", 
                    message.getMessageId(), message.getOrderNo(), 
                    System.currentTimeMillis() - startTime);
        } catch (BusinessException e) {
            log.warn("订单消息处理失败-业务异常 | 消息ID:{} | 订单号:{} | 错误:{}", 
                    message.getMessageId(), message.getOrderNo(), e.getMessage());
            
            // 业务异常,拒绝消息并不再重试
            channel.basicReject(deliveryTag, false);
        } catch (Exception e) {
            log.error("订单消息处理失败-系统异常 | 消息ID:{} | 订单号:{} | 异常类型:{}", 
                     message.getMessageId(), message.getOrderNo(), 
                     e.getClass().getSimpleName(), e);
            
            // 系统异常,拒绝消息并重新入队
            channel.basicReject(deliveryTag, true);
        }
    }
}

6. 定时任务日志

6.1 分布式定时任务

@Component
@Slf4j
public class ScheduledTasks {
    
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    @SchedulerLock(name = "dataCleanupTask", lockAtMostFor = "30m")
    public void dataCleanupTask() {
        String taskId = UUID.randomUUID().toString();
        log.info("数据清理任务开始 | 任务ID:{} | 执行时间:{}", 
                taskId, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        int failureCount = 0;
        
        try {
            // 清理过期订单
            log.debug("开始清理过期订单 | 任务ID:{}", taskId);
            int orderCount = orderService.cleanExpiredOrders();
            log.debug("过期订单清理完成 | 清理数量:{}", orderCount);
            successCount++;
            
            // 清理日志文件
            log.debug("开始清理日志文件 | 任务ID:{}", taskId);
            int logCount = logService.cleanOldLogs();
            log.debug("日志文件清理完成 | 清理数量:{}", logCount);
            successCount++;
            
            log.info("数据清理任务完成 | 任务ID:{} | 成功子任务:{} | 失败子任务:{} | 总耗时:{}ms", 
                    taskId, successCount, failureCount, 
                    System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("数据清理任务异常 | 任务ID:{} | 异常类型:{}", 
                     taskId, e.getClass().getSimpleName(), e);
            throw e;
        }
    }
}

7. 安全认证日志

7.1 JWT 认证日志

@Component
@Slf4j
public class JwtTokenProvider {
    
    public String generateToken(UserDetails userDetails) {
        log.debug("生成JWT Token开始 | 用户名:{} | 角色:{}", 
                 userDetails.getUsername(), userDetails.getAuthorities());
        
        String token = doGenerateToken(userDetails);
        
        log.info("JWT Token生成成功 | 用户名:{} | Token前缀:{}", 
                userDetails.getUsername(), token.substring(0, 20));
        return token;
    }
    
    public boolean validateToken(String token) {
        log.debug("验证JWT Token | Token长度:{}", token.length());
        
        try {
            boolean isValid = doValidateToken(token);
            
            if (isValid) {
                log.debug("JWT Token验证通过");
            } else {
                log.warn("JWT Token验证失败");
            }
            
            return isValid;
        } catch (ExpiredJwtException e) {
            log.warn("JWT Token已过期 | 过期时间:{}", e.getClaims().getExpiration());
            return false;
        } catch (MalformedJwtException e) {
            log.warn("JWT Token格式错误");
            return false;
        } catch (Exception e) {
            log.error("JWT Token验证异常 | 异常类型:{}", e.getClass().getSimpleName(), e);
            return false;
        }
    }
}

8. 文件操作日志

8.1 文件上传下载

@Service
@Slf4j
public class FileService {
    
    public FileUploadResponse uploadFile(MultipartFile file, String businessType) {
        log.info("文件上传开始 | 文件名:{} | 文件大小:{} | 业务类型:{}", 
                file.getOriginalFilename(), file.getSize(), businessType);
        
        long startTime = System.currentTimeMillis();
        try {
            // 文件验证
            validateFile(file);
            log.debug("文件验证通过 | 文件名:{}", file.getOriginalFilename());
            
            // 上传处理
            String filePath = saveFile(file);
            log.debug("文件保存完成 | 存储路径:{}", filePath);
            
            FileUploadResponse response = new FileUploadResponse(filePath, "上传成功");
            
            log.info("文件上传成功 | 文件名:{} | 文件路径:{} | 耗时:{}ms", 
                    file.getOriginalFilename(), filePath, 
                    System.currentTimeMillis() - startTime);
            
            return response;
        } catch (FileSizeException e) {
            log.warn("文件上传失败-文件大小超限 | 文件名:{} | 实际大小:{} | 限制大小:{}", 
                    file.getOriginalFilename(), file.getSize(), e.getLimitSize());
            throw new BusinessException("文件大小超过限制");
        } catch (FileTypeException e) {
            log.warn("文件上传失败-文件类型不支持 | 文件名:{} | 文件类型:{}", 
                    file.getOriginalFilename(), e.getFileType());
            throw new BusinessException("不支持的文件类型");
        } catch (IOException e) {
            log.error("文件上传失败-IO异常 | 文件名:{} | 异常类型:{}", 
                     file.getOriginalFilename(), e.getClass().getSimpleName(), e);
            throw new SystemException("文件上传失败");
        }
    }
}

9. 缓存操作日志

9.1 Redis 缓存操作

@Service
@Slf4j
public class CacheService {
    
    public <T> T getFromCache(String key, Class<T> clazz) {
        log.debug("查询缓存开始 | 缓存键:{} | 类型:{}", key, clazz.getSimpleName());
        
        long startTime = System.currentTimeMillis();
        try {
            T value = redisTemplate.opsForValue().get(key);
            
            if (value == null) {
                log.debug("缓存未命中 | 缓存键:{} | 耗时:{}ms", 
                         key, System.currentTimeMillis() - startTime);
            } else {
                log.debug("缓存命中 | 缓存键:{} | 耗时:{}ms", 
                         key, System.currentTimeMillis() - startTime);
            }
            
            return value;
        } catch (RedisConnectionFailureException e) {
            log.error("缓存查询失败-连接异常 | 缓存键:{}", key, e);
            throw new CacheException("缓存服务不可用");
        } catch (Exception e) {
            log.error("缓存查询失败-未知异常 | 缓存键:{} | 异常类型:{}", 
                     key, e.getClass().getSimpleName(), e);
            throw new CacheException("缓存查询失败");
        }
    }
    
    public void setToCache(String key, Object value, Duration ttl) {
        log.debug("设置缓存开始 | 缓存键:{} | TTL:{}", key, ttl);
        
        try {
            redisTemplate.opsForValue().set(key, value, ttl);
            log.debug("缓存设置成功 | 缓存键:{}", key);
        } catch (Exception e) {
            log.error("缓存设置失败 | 缓存键:{} | 异常类型:{}", 
                     key, e.getClass().getSimpleName(), e);
            // 缓存设置失败不应该影响主流程
        }
    }
}

10. 全局异常处理日志

10.1 统一异常处理器

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e, 
                                                               HttpServletRequest request) {
        String errorId = generateErrorId();
        log.warn("业务异常处理 | 错误ID:{} | 错误码:{} | 错误信息:{} | 请求路径:{} | 客户端IP:{}", 
                errorId, e.getErrorCode(), e.getMessage(), 
                request.getRequestURI(), getClientIp(request));
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(errorId, e.getErrorCode(), e.getMessage()));
    }
    
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthException(AuthenticationException e,
                                                           HttpServletRequest request) {
        String errorId = generateErrorId();
        log.warn("认证异常处理 | 错误ID:{} | 错误信息:{} | 请求路径:{} | 客户端IP:{}", 
                errorId, e.getMessage(), request.getRequestURI(), getClientIp(request));
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ErrorResponse(errorId, "UNAUTHORIZED", "认证失败"));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleSystemException(Exception e,
                                                             HttpServletRequest request) {
        String errorId = generateErrorId();
        log.error("系统异常处理 | 错误ID:{} | 异常类型:{} | 请求路径:{} | 客户端IP:{} | 堆栈跟踪:{}", 
                 errorId, e.getClass().getSimpleName(), request.getRequestURI(), 
                 getClientIp(request), getStackTrace(e));
        
        // 生产环境隐藏详细错误信息
        String message = isProduction() ? "系统内部错误" : e.getMessage();
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse(errorId, "SYSTEM_ERROR", message));
    }
}

11. 配置类日志

11.1 应用启动配置

@SpringBootApplication
@Slf4j
public class Application {
    
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        
        // 启动前日志
        log.info("应用启动开始 | 环境:{} | 版本:{} | 启动时间:{}", 
                getActiveProfile(), getAppVersion(), LocalDateTime.now());
        
        try {
            ConfigurableApplicationContext context = app.run(args);
            
            // 启动成功日志
            Environment env = context.getEnvironment();
            String port = env.getProperty("server.port");
            String contextPath = env.getProperty("server.servlet.context-path", "");
            
            log.info("应用启动成功 | 环境:{} | 端口:{} | 上下文路径:{} | 启动耗时:{}ms", 
                    getActiveProfile(), port, contextPath, 
                    System.currentTimeMillis() - startTime);
            
            // 记录重要的配置信息
            log.info("数据源配置 | URL:{} | 用户名:{}", 
                    maskDatabaseUrl(env.getProperty("spring.datasource.url")),
                    env.getProperty("spring.datasource.username"));
                    
        } catch (Exception e) {
            log.error("应用启动失败 | 异常类型:{}", e.getClass().getSimpleName(), e);
            System.exit(1);
        }
    }
}

这些示例覆盖了 Spring Boot/Cloud 应用中绝大多数常见的日志场景,提供了完整的、生产可用的日志记录模式。在实际开发中,可以根据具体业务需求进行调整和扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值