mcpField
J
java-logging

java-logging

github.com/claude-dev-suite开发4647 次安装2 天前更新
代码仓库

关于 java-logging

面向 Java 开发者的日志技能,介绍 SLF4J 用法、Logback 和 Log4j2 配置、结构化 JSON 日志及运维实践,强调参数化消息、上下文记录和敏感数据保护。

主要功能

  1. 01SLF4J API 使用模式
  2. 02Logback 和 Log4j2 配置示例
  3. 03结合 MDC 的结构化 JSON 日志
  4. 04日志级别与参数化消息
  5. 05安全和故障排查建议

适用场景

  1. 01为 Java 应用配置日志
  2. 02为应用日志添加请求上下文
  3. 03排查日志缺失或配置不当

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill java-logging

安装后在 Claude Code 中输入/java-logging确认已加载。

附带文件quick-ref/log-levels.md

详细介绍

Java 日志记录

架构概览

┌─────────────────────────────────────────────────────────────┐
│                    Application Code                         │
│              (uses SLF4J API only)                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      SLF4J Facade                           │
│              (abstraction layer)                            │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
        ┌──────────┐   ┌──────────┐   ┌──────────────┐
        │ Logback  │   │ Log4j2   │   │ java.util    │
        │          │   │          │   │ .logging     │
        └──────────┘   └──────────┘   └──────────────┘

规则:始终面向 SLF4J API 编写代码。实现是运行时依赖项。

SLF4J API 用法

基本日志记录
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
    private static final Logger log = LoggerFactory.getLogger(UserService.class);

    public User findUser(Long id) {
        log.debug("Finding user with id: {}", id);

        try {
            User user = repository.findById(id);
            log.info("User found: {}", user.getEmail());
            return user;
        } catch (Exception e) {
            log.error("Failed to find user with id: {}", id, e);
            throw e;
        }
    }
}
使用 Lombok
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class OrderService {
    public void processOrder(Order order) {
        log.info("Processing order: {}", order.getId());
    }
}
日志级别
级别用途示例
TRACE非常详细的调试信息循环迭代、变量值
DEBUG调试信息方法进入/退出、查询参数
INFO业务事件用户登录、下单
WARN潜在问题使用了已弃用的 API、重试尝试
ERROR需要关注的错误捕获到异常、操作失败
log.trace("Entering loop iteration {}", i);
log.debug("Query parameters: userId={}, status={}", userId, status);
log.info("Order {} placed successfully", orderId);
log.warn("Payment retry attempt {} of {}", attempt, maxRetries);
log.error("Failed to process payment for order {}", orderId, exception);
参数化消息(最佳实践)
// GOOD - uses parameterized logging (efficient)
log.debug("Processing user {} with role {}", userId, role);

// BAD - string concatenation (always evaluated)
log.debug("Processing user " + userId + " with role " + role);

// For expensive operations, use isEnabled check
if (log.isDebugEnabled()) {
    log.debug("Complex data: {}", computeExpensiveDebugInfo());
}

Logback 配置

Spring Boot(logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    
    <property name="LOG_PATH" value="${LOG_PATH:-logs}"/>
    <property name="LOG_FILE" value="${LOG_FILE:-application}"/>

    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
        </encoder>
    </appender>

    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    
    <appender name="JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}-json.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE}-json.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>userId</includeMdcKeyName>
        </encoder>
    </appender>

    
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="FILE"/>
    </appender>

    
    <logger name="com.yourcompany" level="DEBUG"/>
    <logger name="org.springframework" level="INFO"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>
    <logger name="org.hibernate.type.descriptor.sql" level="TRACE"/>

    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_FILE"/>
    </root>

    
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="JSON"/>
        </root>
    </springProfile>
</configuration>
application.yml 配置
logging:
  level:
    root: INFO
    com.yourcompany: DEBUG
    org.springframework.web: INFO
    org.hibernate.SQL: DEBUG
  pattern:
    console: "%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
  file:
    name: logs/application.log
  logback:
    rollingpolicy:
      max-file-size: 100MB
      max-history: 30

Log4j2 配置

log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <Property name="LOG_PATH">logs</Property>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</Property>
    </Properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>

        <RollingFile name="File" fileName="${LOG_PATH}/app.log"
                     filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="100MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        
        <Async name="AsyncFile">
            <AppenderRef ref="File"/>
        </Async>
    </Appenders>

    <Loggers>
        <Logger name="com.yourcompany" level="debug"/>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncFile"/>
        </Root>
    </Loggers>
</Configuration>

结构化日志(JSON)

依赖项

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>
MDC(映射诊断上下文)
import org.slf4j.MDC;

@Component
public class RequestLoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        try {
            MDC.put("traceId", generateTraceId());
            MDC.put("userId", getCurrentUserId());
            MDC.put("requestPath", ((HttpServletRequest) request).getRequestURI());

            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}
结构化日志输出
import static net.logstash.logback.argument.StructuredArguments.*;

log.info("Order processed",
    kv("orderId", order.getId()),
    kv("customerId", order.getCustomerId()),
    kv("amount", order.getTotal()),
    kv("currency", "USD"));

输出:

{
  "@timestamp": "2025-01-15T10:30:00.000Z",
  "level": "INFO",
  "logger": "com.example.OrderService",
  "message": "Order processed",
  "orderId": "ORD-12345",
  "customerId": "CUST-789",
  "amount": 99.99,
  "currency": "USD",
  "traceId": "abc123",
  "userId": "user456"
}

最佳实践

应该做
  • 在所有地方使用 SLF4J API
  • 使用参数化消息 log.info("User {}", userId)
  • 包含关联 ID(traceId、requestId)
  • 使用合适的日志级别
  • 在生产环境中使用异步追加器
  • 配置日志轮转
  • 使用 JSON 格式进行日志聚合
不应该做
  • 不要在日志消息中使用字符串拼接
  • 不要记录敏感数据(密码、令牌、个人身份信息)
  • 不要在紧密循环中记录日志,除非先检查日志级别
  • 不要使用 System.out.println 记录日志
  • 不要捕获异常后悄然忽略
安全性
// BAD - logs sensitive data
log.info("User login: email={}, password={}", email, password);

// GOOD - mask sensitive data
log.info("User login: email={}", maskEmail(email));

// Helper
private String maskEmail(String email) {
    int atIndex = email.indexOf('@');
    if (atIndex > 2) {
        return email.substring(0, 2) + "***" + email.substring(atIndex);
    }
    return "***";
}

不适用此技能的情况

  • 仅涉及 SLF4J API 的问题:使用 slf4j 技能了解 API 用法模式
  • Logback 配置细节:使用 logback 技能了解 XML 配置
  • Node.js/Python 项目:使用适合相应语言的日志技能
  • 应用程序代码模式:专注于 SLF4J API,而非实现
  • 框架迁移:参考迁移专用指南

反模式

反模式不良原因解决方案
使用 System.out.println无法控制、无法持久化、无法筛选使用 SLF4J 记录器
在日志中进行字符串拼接始终会执行,影响性能使用参数化日志:log.info("User {}", id)
生产环境中不使用异步追加器会阻塞应用程序线程使用 AsyncAppender 封装
多线程应用中不使用 MDC会丢失请求上下文使用 MDC 传递关联 ID
在生产环境中使用 DEBUG 级别影响性能、占用磁盘空间在生产环境中使用 INFO 或 WARN
不屏蔽敏感数据违反安全/合规要求记录日志前过滤密码、令牌、个人身份信息

快速故障排查

问题原因解决方案
NoClassDefFoundError: StaticLoggerBinder缺少 SLF4J 实现添加 Logback 或 Log4j2 依赖
多个绑定警告类路径中存在多个实现只保留一个:Logback 或 Log4j2
未显示日志日志级别错误或缺少配置检查 logback.xml 和日志级别
性能下降使用了同步追加器使用 AsyncAppender 封装
日志未轮转缺少滚动策略配置 RollingFileAppender
未显示 MDC 值模式中缺少 %X{key}在日志模式中添加 MDC 占位符

参考资料

评论

支持 Markdown