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 必须遵循的原则
- 适当的日志级别:生产环境使用 INFO,开发环境使用 DEBUG
- 有意义的日志消息:包含足够的上下文信息
- 统一格式:团队使用统一的日志格式
- 性能考虑:避免在日志中执行耗时操作
- 安全考虑:不要记录敏感信息(密码、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 应用中绝大多数常见的日志场景,提供了完整的、生产可用的日志记录模式。在实际开发中,可以根据具体业务需求进行调整和扩展。

被折叠的 条评论
为什么被折叠?



