日志框架是应用程序中用于记录运行时信息的工具,它可以帮助开发者:
// 简单的日志记录示例
public class OrderService {
private static final Logger logger = Logger.getLogger(OrderService.class);
public void createOrder(Order order) {
logger.info("开始创建订单,订单ID:" + order.getId());
try {
// 业务逻辑
logger.debug("订单处理中...");
} catch (Exception e) {
logger.error("创建订单失败", e);
}
logger.info("订单创建完成");
}
}
组件 | 说明 | 类比日常生活 |
---|---|---|
Logger | 日志记录器,负责捕获日志信息 | 像公司的不同部门(财务部、人事部)各自有记录本 |
Appender | 定义日志输出目的地 | 如同记录本可以写在纸上、黑板上或电子文档中 |
Layout | 定义日志输出格式 | 类似记录时的格式要求(日期+内容+记录人) |
Filter | 过滤特定日志 | 像部门领导只查看重要事项的记录 |
Level | 日志级别,控制日志重要性 | 类似消息分类(普通通知、重要提醒、紧急警报) |
级别 | 优先级 | 使用场景 | 日常类比 |
---|---|---|---|
OFF | 最高 | 关闭所有日志 | 关闭所有通知 |
FATAL | 高 | 严重错误导致应用崩溃 | 系统崩溃警报 |
ERROR | 高 | 错误事件但应用仍能运行 | 重要设备故障 |
WARN | 中 | 潜在错误情况 | 设备即将过期提醒 |
INFO | 低 | 重要运行时信息 | 日常工作报告 |
DEBUG | 低 | 调试信息 | 详细工作笔记 |
TRACE | 最低 | 比DEBUG更细致的信息 | 工作每一步的详细记录 |
ALL | 最低 | 所有级别日志 | 记录所有事情 |
步骤1:排除默认日志框架并添加Log4j依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
dependencies>
步骤2:创建log4j2.xml配置文件
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
步骤3:在代码中使用Logger
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class PaymentService {
// 获取Logger实例
private static final Logger logger = LogManager.getLogger(PaymentService.class);
public void processPayment(double amount) {
logger.info("开始处理支付,金额:{}", amount);
try {
// 支付逻辑
logger.debug("支付处理中...");
if (amount > 10000) {
logger.warn("大额支付警告:{}元", amount);
}
} catch (Exception e) {
logger.error("支付处理失败", e);
}
logger.info("支付处理完成");
}
}
<Configuration>
<Properties>
<Property name="logDir">./logsProperty>
Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
<File name="File" fileName="${logDir}/app.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
File>
Appenders>
<Loggers>
<Logger name="com.example.service" level="debug" additivity="false">
<AppenderRef ref="File"/>
Logger>
<Root level="info">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
Appender类型 | 说明 | 适用场景 |
---|---|---|
Console | 控制台输出 | 开发环境调试 |
File | 单个文件输出 | 简单日志记录 |
RollingFile | 滚动文件输出 | 生产环境日志 |
SMTP | 邮件发送 | 错误报警 |
JDBC | 数据库存储 | 日志分析 |
Kafka | Kafka消息队列 | 分布式日志收集 |
Async | 异步日志 | 高性能场景 |
滚动文件配置示例:
<RollingFile name="RollingFile" fileName="${logDir}/app.log"
filePattern="${logDir}/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%nPattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="30">
<Delete basePath="${logDir}" maxDepth="1">
<IfFileName glob="app-*.log" />
<IfLastModified age="30d" />
Delete>
DefaultRolloverStrategy>
RollingFile>
模式字符 | 说明 | 示例输出 |
---|---|---|
%d | 日期时间 | 2023-05-15 14:30:22,123 |
%t | 线程名 | main |
%level | 日志级别 | INFO |
%logger | 日志记录器名 | com.example.MyClass |
%msg | 日志消息 | 用户登录成功 |
%n | 换行符 | - |
%throwable | 异常堆栈 | java.lang.NullPointerException |
%L | 行号 | 42 |
%M | 方法名 | doSomething |
%highlight | 高亮显示 | (彩色输出) |
高级模式示例:
<PatternLayout pattern="%style{%d{ISO8601}}{cyan} %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue} %logger{36} - %msg%n"/>
<Configuration>
<Properties>
<Property name="logDir">./logsProperty>
<Property name="env">${spring.profiles.active:dev}Property>
Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%style{%d{HH:mm:ss.SSS}}{cyan} %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue} %logger{36} - %msg%n"
disableAnsi="false"/>
Console>
<Console name="JsonConsole" target="SYSTEM_OUT">
<JsonLayout complete="false" compact="true">
<KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd'T'HH:mm:ss.SSSZ}"/>
<KeyValuePair key="level" value="$${level}"/>
<KeyValuePair key="logger" value="$${logger}"/>
<KeyValuePair key="message" value="$${message}"/>
<KeyValuePair key="stacktrace" value="$${exception:stacktrace}"/>
JsonLayout>
Console>
Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="${env == 'prod' ? 'JsonConsole' : 'Console'}"/>
Root>
Loggers>
Configuration>
<Configuration>
<Properties>
<Property name="logDir">./logsProperty>
<Property name="asyncLoggerRingBufferSize">262144Property>
Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${logDir}/app.log"
filePattern="${logDir}/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%nPattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
RollingFile>
Appenders>
<Loggers>
<Logger name="com.example.sync" level="debug" additivity="false">
<AppenderRef ref="RollingFile"/>
Logger>
<AsyncLogger name="com.example.async" level="debug" includeLocation="true">
<AppenderRef ref="RollingFile"/>
AsyncLogger>
<Root level="info">
<AppenderRef ref="RollingFile"/>
Root>
Loggers>
Configuration>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
<RollingFile name="ErrorFile" fileName="${logDir}/error.log"
filePattern="${logDir}/error-%d{yyyy-MM-dd}-%i.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%nPattern>
PatternLayout>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
RollingFile>
Appenders>
<Loggers>
<Logger name="com.example.security.UserActivity" level="info" additivity="false">
<AppenderRef ref="UserActivityFile"/>
Logger>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="ErrorFile"/>
Root>
Loggers>
Configuration>
优化点 | 说明 | 配置示例 |
---|---|---|
异步日志 | 减少I/O阻塞 |
|
缓冲区 | 减少磁盘I/O |
|
日志级别 | 生产环境合理设置 |
|
位置信息 | 避免频繁获取 | includeLocation="false" |
格式化 | 避免复杂计算 | 简化PatternLayout |
合理使用日志级别
日志内容规范
// 不好的写法
logger.info("Processing data: " + data);
// 好的写法 - 使用参数化日志
logger.info("Processing data: {}", data);
// 包含上下文信息
logger.info("User [{}] purchased product [{}], amount: {}", userId, productId, amount);
异常处理
// 不好的写法 - 丢失堆栈信息
try {
// code
} catch (Exception e) {
logger.error("Error occurred");
}
// 好的写法
try {
// code
} catch (Exception e) {
logger.error("Failed to process order: {}", orderId, e);
}
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="logDir">./logsProperty>
<Property name="appName">ecommerceProperty>
<Property name="pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%nProperty>
Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
Console>
<RollingFile name="AppFile" fileName="${logDir}/${appName}.log"
filePattern="${logDir}/${appName}-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="50 MB"/>
Policies>
<DefaultRolloverStrategy max="30"/>
RollingFile>
<RollingFile name="ErrorFile" fileName="${logDir}/error.log"
filePattern="${logDir}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
RollingFile>
<RollingFile name="AuditFile" fileName="${logDir}/audit.log"
filePattern="${logDir}/audit-%d{yyyy-MM-dd}-%i.log">
<JsonLayout complete="true" compact="false">
<KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd'T'HH:mm:ss.SSSZ}"/>
<KeyValuePair key="level" value="$${level}"/>
<KeyValuePair key="logger" value="$${logger}"/>
<KeyValuePair key="message" value="$${message}"/>
<KeyValuePair key="userId" value="$${ctx:userId}"/>
<KeyValuePair key="ip" value="$${ctx:ip}"/>
JsonLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
RollingFile>
Appenders>
<Loggers>
<Logger name="com.ecommerce" level="debug" additivity="false">
<AppenderRef ref="AppFile"/>
<AppenderRef ref="ErrorFile"/>
Logger>
<Logger name="com.ecommerce.audit" level="info" additivity="false">
<AppenderRef ref="AuditFile"/>
Logger>
<Logger name="org.hibernate" level="warn"/>
<Logger name="org.springframework" level="warn"/>
<Root level="warn">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
订单服务示例:
public class OrderServiceImpl implements OrderService {
private static final Logger logger = LogManager.getLogger(OrderServiceImpl.class);
private static final Logger auditLogger = LogManager.getLogger("com.ecommerce.audit");
@Override
public Order createOrder(OrderRequest request) {
// 使用ThreadContext记录用户信息
ThreadContext.put("userId", request.getUserId());
ThreadContext.put("ip", request.getClientIp());
logger.info("开始创建订单,用户ID: {}, 商品数量: {}",
request.getUserId(), request.getItems().size());
try {
// 验证库存
validateStock(request.getItems());
// 计算价格
BigDecimal totalAmount = calculateTotal(request.getItems());
logger.debug("订单计算完成,总金额: {}", totalAmount);
// 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setTotalAmount(totalAmount);
order.setStatus(OrderStatus.CREATED);
// 审计日志
auditLogger.info("订单创建成功,订单ID: {}, 金额: {}",
order.getId(), totalAmount);
return order;
} catch (OutOfStockException e) {
logger.error("库存不足,商品ID: {}, 请求数量: {}, 可用数量: {}",
e.getProductId(), e.getRequested(), e.getAvailable());
throw new BusinessException("库存不足", e);
} catch (Exception e) {
logger.error("创建订单失败", e);
throw new SystemException("系统错误", e);
} finally {
ThreadContext.clearAll();
}
}
private void validateStock(List<OrderItem> items) throws OutOfStockException {
// 验证逻辑...
logger.trace("库存验证中...");
}
private BigDecimal calculateTotal(List<OrderItem> items) {
// 计算逻辑...
logger.trace("价格计算中...");
return BigDecimal.ZERO;
}
}
问题现象 | 可能原因 | 解决方案 |
---|---|---|
日志不输出 | 1. 日志级别设置过高 2. 配置文件未加载 3. Appender配置错误 |
1. 检查日志级别 2. 确认配置文件位置和名称 3. 检查Appender引用 |
日志文件不滚动 | 1. 滚动策略配置错误 2. 文件权限问题 |
1. 检查TimeBased/SizeBased策略 2. 检查文件写入权限 |
日志内容缺失 | 1. 过滤器设置过严 2. PatternLayout不完整 |
1. 检查ThresholdFilter设置 2. 完善Pattern模式 |
性能问题 | 1. 同步日志I/O阻塞 2. 位置信息获取开销大 |
1. 使用异步日志 2. 设置includeLocation=“false” |
日志重复输出 | additivity属性设置为true | 设置additivity=“false” |
日志监控方案
日志维护策略
# 示例:清理30天前的日志文件
find /var/log/myapp/ -name "*.log" -mtime +30 -exec rm {} \;
特性 | Log4j2 | Logback | JUL (java.util.logging) |
---|---|---|---|
性能 | 高 | 中 | 低 |
配置方式 | XML/JSON/YAML/Properties | XML/Groovy | Properties |
异步日志 | 支持 | 支持 | 有限支持 |
过滤功能 | 强大 | 中等 | 基础 |
社区支持 | 活跃 | 活跃 | 官方维护 |
云原生支持 | 好 | 一般 | 差 |
JUL (JDK1.4) → Log4j1 → SLF4J+Logback → Log4j2
收藏?算了算了,这么优秀的文章你肯定记不住(狗头)。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!