关于 java-logging
面向 Java 开发者的日志技能,介绍 SLF4J 用法、Logback 和 Log4j2 配置、结构化 JSON 日志及运维实践,强调参数化消息、上下文记录和敏感数据保护。
主要功能
- 01SLF4J API 使用模式
- 02Logback 和 Log4j2 配置示例
- 03结合 MDC 的结构化 JSON 日志
- 04日志级别与参数化消息
- 05安全和故障排查建议
适用场景
- 01为 Java 应用配置日志
- 02为应用日志添加请求上下文
- 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