关于 logging-patterns
面向 Java 开发者的日志实践技能,涵盖结构化 JSON 日志、上下文关联与日志分析。重点是让生产和 CI/CD 日志便于排查及 AI 读取。
主要功能
- 01推荐以 JSON 作为默认日志格式
- 02配置 Spring Boot 结构化日志
- 03使用 MDC 添加请求上下文
- 04通过 requestId 追踪请求流程
- 05提供 SLF4J 与异常日志规范
适用场景
- 01为 Spring Boot 应用配置 JSON 日志
- 02按请求 ID 排查应用调用流程
- 03分析错误及耗时操作
- 04统一团队日志级别和记录方式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ piomin/ claude-ai-spring-boot --skill logging-patterns安装后在 Claude Code 中输入/logging-patterns确认已加载。
详细介绍
日志模式技能
规则:JSON 是默认日志格式。 纯文本日志仅用于使用 human-logs 配置的本地开发。生产、CI/CD 和 Kubernetes 环境必须使用结构化 JSON。
适用场景
- 用户说“添加日志”/“改进日志”/“调试这个”
- 根据日志分析应用流程
- 设置结构化日志(JSON)
- 使用关联 ID 进行请求跟踪
- AI/Claude Code 需要分析应用行为
适合 AI 的日志
关键要点: JSON 日志更适合 AI 分析——解析更快、消耗的令牌更少,还能直接访问字段。
为什么 AI/Claude Code 应使用 JSON?
# Text format - AI must "interpret" the string
2026-01-29 10:15:30 INFO OrderService - Order 12345 created for user-789, total: 99.99
# JSON format - AI extracts fields directly
{"timestamp":"2026-01-29T10:15:30Z","level":"INFO","orderId":12345,"userId":"user-789","total":99.99}
| 方面 | 文本 | JSON |
|---|---|---|
| 解析 | 正则表达式/解释 | 直接访问字段 |
| 令牌用量 | 更高(模式重复) | 更低(结构化) |
| 提取错误 | 解析堆栈跟踪文本 | exception 字段 |
| 筛选 | grep 模式 | jq 查询 |
AI 辅助开发的推荐设置
# application.yml - JSON by default
logging:
structured:
format:
console: logstash # Spring Boot 4.x
# When YOU need to read logs manually:
# Option 1: Use jq
# tail -f app.log | jq .
# Option 2: Switch profile temporarily
# java -jar app.jar --spring.profiles.active=human-logs
针对 AI 分析优化的日志格式
{
"timestamp": "2026-01-29T10:15:30.123Z",
"level": "INFO",
"logger": "pl.piomin.services.OrderService",
"message": "Order created",
"requestId": "req-abc123",
"traceId": "trace-xyz",
"orderId": 12345,
"userId": "user-789",
"duration_ms": 45,
"step": "payment_completed"
}
用于 AI 调试的关键字段:
requestId- 对同一请求的所有日志进行分组step- 跟踪流程进度duration_ms- 找出耗时较长的操作level- 快速筛选错误
使用 AI/Claude Code 读取日志
让 AI 分析日志时:
# Get recent errors
cat app.log | jq 'select(.level == "ERROR")' | tail -20
# Follow specific request
cat app.log | jq 'select(.requestId == "req-abc123")'
# Find slow operations
cat app.log | jq 'select(.duration_ms > 1000)'
AI 随后可以:
- 直接解析 JSON(无需猜测)
- 通过 requestId 跟踪请求流程
- 准确找出错误发生的位置
- 测量各步骤之间的耗时
快速设置(Spring Boot 4.x)
原生结构化日志
Spring Boot 4.x 内置 JSON 日志功能,无需额外依赖。
# application.yml — this is the REQUIRED default for every app
logging:
structured:
format:
console: logstash # or "ecs" for Elastic Common Schema
# Supported formats:
# logstash - JSON (most common, compatible with ELK and OpenTelemetry Collector)
# ecs - Elastic Common Schema (native Elastic/Kibana integration)
# gelf - Graylog Extended Log Format
OpenTelemetry 集成
当类路径中存在 Micrometer Tracing 时,traceId 和 spanId 会自动包含在每一行 JSON 日志中。这使 OTel Collector 能够将日志与分布式跟踪关联起来。
# application.yml — tracing + structured logging together
management:
tracing:
sampling:
probability: 1.0
logging:
structured:
format:
console: logstash
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
输出:
{"@timestamp":"2026-01-29T10:15:30.123Z","level":"INFO","logger_name":"pl.piomin.services.OrderService","message":"Order created","traceId":"abc123def456","spanId":"789xyz","service.name":"order-service"}
JSON 日志中的 traceId 字段与 Jaeger、Tempo 或任何兼容 OTel 的后端中显示的跟踪 ID 相同,因此可以直接关联日志与跟踪。
基于配置切换
# application.yml (default - JSON for AI/prod)
spring:
profiles:
default: json-logs
---
spring:
config:
activate:
on-profile: json-logs
logging:
structured:
format:
console: logstash
---
spring:
config:
activate:
on-profile: human-logs
# No structured format = human-readable default
logging:
pattern:
console: "%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
用法:
# Default: JSON (for AI, CI/CD, production)
./mvnw spring-boot:run
# Human-readable when needed
./mvnw spring-boot:run -Dspring.profiles.active=human-logs
Spring Boot < 3.4 的设置(旧版)
Logstash Logback Encoder
pom.xml:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProfile name="!human-logs">
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>requestId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</springProfile>
<springProfile name="human-logs">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
添加自定义字段(Logstash Encoder)
import static net.logstash.logback.argument.StructuredArguments.kv;
// Fields appear as separate JSON keys
log.info("Order created",
kv("orderId", order.getId()),
kv("userId", user.getId()),
kv("total", order.getTotal()),
kv("step", "order_created")
);
// Output:
// {"message":"Order created","orderId":123,"userId":"u-456","total":99.99,"step":"order_created"}
SLF4J 基础知识
Logger 声明
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
// use `log` directly for logging
}
参数化日志
// ✅ GOOD: Evaluated only if level enabled
log.debug("Processing order {} for user {}", orderId, userId);
// ❌ BAD: Always concatenates
log.debug("Processing order " + orderId + " for user " + userId);
// ✅ For expensive operations
if (log.isDebugEnabled()) {
log.debug("Order details: {}", order.toDetailedString());
}
异常日志
// ✅ GOOD: Pass exception as last argument
try {
processOrder(order);
} catch (PaymentException e) {
log.error("Payment failed for order {}", order.getId(), e);
}
// ❌ BAD: Loses stack trace
log.error("Payment failed: " + e.getMessage());
// ❌ BAD: Exception in placeholder (toString only)
log.error("Payment failed for order {}: {}", order.getId(), e);
日志级别指南
| 级别 | 使用时机 |
|---|---|
ERROR | 出现故障,需要采取措施。付款失败、状态无法恢复 |
WARN | 出现意外情况,但已处理。重试成功、使用了回退方案 |
INFO | 业务事件。订单创建、用户登录、任务完成 |
DEBUG | 技术细节。SQL 查询、HTTP 请求、缓存命中 |
TRACE | 详细的内部信息。方法进入/退出、循环迭代 |
MDC(映射诊断上下文)
MDC 会自动将上下文(请求 ID、用户 ID)附加到当前线程的每一行日志中。
用于关联 ID 的请求过滤器
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MdcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestId = httpRequest.getHeader("X-Request-Id");
if (requestId == null) {
requestId = UUID.randomUUID().toString().substring(0, 8);
}
MDC.put("requestId", requestId);
MDC.put("clientIp", httpRequest.getRemoteAddr());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
服务层中的 MDC
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(OrderRequest request) {
MDC.put("userId", request.userId());
MDC.put("orderId", String.valueOf(request.orderId()));
log.info("Creating order");
// All subsequent log lines in this thread automatically include userId and orderId
return processOrder(request);
}
}
虚拟线程中的 MDC
虚拟线程不会从父线程继承 MDC。使用 TaskDecorator 进行传播:
@Configuration
public class AsyncConfig {
@Bean
public TaskDecorator mdcTaskDecorator() {
return runnable -> {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
if (context != null) {
MDC.setContextMap(context);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
};
}
}
快速参考
| 模式 | 用途 |
|---|---|
LoggerFactory.getLogger(MyClass.class) | 声明 logger(不使用 @Slf4j) |
log.info("msg {}", var) | 参数化日志 |
log.error("msg {}", var, exception) | 记录日志并附带堆栈跟踪 |
MDC.put("key", "value") | 将上下文添加到线程中的所有日志 |
MDC.clear() | 在 finally 块中清理 |
logging.structured.format.console: logstash | 启用 JSON(Spring Boot 4.x) |
kv("field", value) | 结构化参数(Logstash encoder) |
评论
支持 Markdown