1. 日志技术概述
日志是程序开发中不可或缺的组成部分,它就像程序的"黑匣子",记录着应用程序运行过程中的各种关键信息。在实际开发中,我经常遇到这样的情况:线上环境出现问题时,如果没有完善的日志记录,排查问题就像大海捞针。而良好的日志实践可以让我们快速定位问题,甚至能在问题发生前通过日志分析发现潜在风险。
日志主要记录三类信息:
- 运行信息:程序执行路径、关键节点状态
- 状态信息:系统资源使用情况、服务健康状态
- 错误信息:异常堆栈、错误上下文
提示:日志记录不是越多越好,过度日志会影响性能,关键是要在合适的地方记录有价值的信息。
2. Java日志框架选型
2.1 主流日志框架对比
在Java生态中,常见的日志框架有以下几种:
| 框架名称 | 特点 | 适用场景 | 性能表现 |
|---|---|---|---|
| JUL (java.util.logging) | Java官方内置,无需额外依赖 | 小型应用、简单场景 | 中等 |
| Log4j 1.x | 配置灵活,功能丰富 | 传统Java项目 | 较好 |
| Log4j 2.x | 异步日志、插件体系 | 高性能要求项目 | 优秀 |
| Logback | SLF4J原生实现,兼容性好 | Spring Boot默认 | 优秀 |
2.2 为什么选择Logback
在我的项目实践中,Logback通常是首选方案,原因有三:
- 性能优异:异步日志和内部优化使它的吞吐量比Log4j高出10倍以上
- 零配置友好:即使不配置xml文件,也能提供合理的默认行为
- 与SLF4J完美配合:作为SLF4J的原生实现,没有适配层性能损耗
注意:虽然Log4j2性能更好,但在大多数场景下Logback已经足够,且更稳定易用。
3. Logback实战配置
3.1 基础环境搭建
首先在Maven项目中添加依赖:
xml复制<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
这个依赖会自动引入:
- logback-core (核心功能)
- slf4j-api (日志门面)
3.2 配置文件详解
Logback支持XML、Groovy和JSON三种配置方式,XML是最常用的。下面是一个生产级配置示例:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 定义变量 -->
<property name="LOG_HOME" value="/var/log/myapp"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 只接受INFO及以上级别 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- 滚动文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 异步日志提升性能 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="FILE"/>
</appender>
<!-- 日志级别设置 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
<!-- 特定包/类日志级别 -->
<logger name="com.myapp.dao" level="DEBUG"/>
<logger name="org.springframework" level="WARN"/>
</configuration>
关键配置说明:
scan="true"支持热更新配置- 异步日志(
AsyncAppender)可提升性能但会丢失部分日志 - 文件滚动策略支持按时间和大小双重维度
3.3 代码中使用日志
推荐使用Lombok简化日志对象声明:
java复制@Slf4j
@Service
public class OrderService {
public void createOrder(Order order) {
log.debug("开始创建订单,参数:{}", order);
try {
// 业务逻辑
log.info("订单创建成功,订单号:{}", order.getOrderNo());
} catch (Exception e) {
log.error("订单创建失败", e);
throw new RuntimeException(e);
}
}
}
日志输出最佳实践:
- 使用参数化日志(
{}占位符)而非字符串拼接 - ERROR级别必须记录异常堆栈
- 敏感信息需要脱敏处理
4. 高级日志技巧
4.1 日志级别策略
不同环境应该采用不同的日志级别策略:
| 环境 | 推荐级别 | 说明 |
|---|---|---|
| 开发 | DEBUG | 需要详细调试信息 |
| 测试 | INFO | 关注业务流程验证 |
| 生产 | WARN | 只记录重要事件和错误 |
可以通过Spring Profile实现环境差异化配置:
xml复制<springProfile name="dev">
<root level="DEBUG">
<!-- 开发环境配置 -->
</root>
</springProfile>
<springProfile name="prod">
<root level="WARN">
<!-- 生产环境配置 -->
</root>
</springProfile>
4.2 日志脱敏处理
对于敏感信息如手机号、身份证号等,需要实现脱敏处理。可以通过自定义Converter实现:
java复制public class SensitiveDataConverter extends ClassicConverter {
@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
// 手机号脱敏
message = message.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
// 身份证脱敏
message = message.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2");
return message;
}
}
然后在配置中注册:
xml复制<conversionRule conversionWord="msg"
converterClass="com.myapp.logging.SensitiveDataConverter"/>
4.3 分布式日志追踪
在微服务架构下,可以使用MDC(Mapped Diagnostic Context)实现请求链路追踪:
java复制@Slf4j
@RestController
public class OrderController {
@GetMapping("/orders")
public List<Order> listOrders(HttpServletRequest request) {
// 设置追踪ID
MDC.put("traceId", UUID.randomUUID().toString());
log.info("查询订单列表");
// 业务逻辑
MDC.clear();
}
}
日志格式中添加%X{traceId}:
xml复制<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
5. 常见问题排查
5.1 日志不输出
可能原因及解决方案:
- 级别设置过高:检查root和具体logger的level配置
- 配置未加载:确认配置文件在classpath下且命名正确(logback.xml/logback-spring.xml)
- 依赖冲突:使用
mvn dependency:tree检查是否有多个日志框架冲突
5.2 日志文件不滚动
检查点:
- 文件路径是否有写权限
- 滚动策略配置是否正确
- 是否达到了滚动条件(时间或大小)
5.3 性能问题优化
如果日志影响性能,可以考虑:
- 使用异步日志(AsyncAppender)
- 减少不必要的日志输出
- 使用
isDebugEnabled()判断避免字符串拼接开销
java复制if(log.isDebugEnabled()) {
log.debug("大量数据:{}", expensiveOperation());
}
6. 日志分析实践
好的日志不仅要记录,还要能够分析。推荐几种日志分析方案:
-
ELK Stack:
- Filebeat收集日志
- Logstash解析处理
- Elasticsearch存储索引
- Kibana可视化分析
-
Grafana+Loki:
- Loki轻量级日志聚合
- Grafana强大的查询和展示
-
商业方案:
- Splunk
- Datadog
- 阿里云日志服务
对于小型项目,可以先用简单的shell命令分析:
bash复制# 统计ERROR出现次数
grep -c "ERROR" application.log
# 查看最近10个ERROR
grep -A 5 "ERROR" application.log | tail -n 20
# 按小时统计日志量
awk '{print $1}' application.log | cut -d: -f1 | uniq -c
在实际项目中,我发现合理的日志分级和规范的日志格式可以大幅提升问题排查效率。特别是在微服务架构下,统一的日志规范和追踪机制是必不可少的。