mcpField
L
logging-patterns

logging-patterns

github.com/piomin开发1.4k68 次安装6 天前更新
代码仓库

关于 logging-patterns

面向 Java 开发者的日志实践技能,涵盖结构化 JSON 日志、上下文关联与日志分析。重点是让生产和 CI/CD 日志便于排查及 AI 读取。

主要功能

  1. 01推荐以 JSON 作为默认日志格式
  2. 02配置 Spring Boot 结构化日志
  3. 03使用 MDC 添加请求上下文
  4. 04通过 requestId 追踪请求流程
  5. 05提供 SLF4J 与异常日志规范

适用场景

  1. 01为 Spring Boot 应用配置 JSON 日志
  2. 02按请求 ID 排查应用调用流程
  3. 03分析错误及耗时操作
  4. 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 随后可以:

  1. 直接解析 JSON(无需猜测)
  2. 通过 requestId 跟踪请求流程
  3. 准确找出错误发生的位置
  4. 测量各步骤之间的耗时

快速设置(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