1. 日志基础概念
1.1 什么是日志
日志是应用程序运行时记录的事件、状态和信息的集合,用于跟踪应用程序的运行状况、调试问题和监控系统行为。
通俗理解:就像飞机的黑匣子,记录着系统运行的所有关键信息,当出现问题时可以回放查看。
1.2 为什么需要日志管理
需求
说明
日常生活类比
问题诊断
当系统出现问题时快速定位
像医院的病历记录
性能监控
跟踪系统性能指标
汽车的仪表盘
安全审计
记录关键操作以备审查
银行的交易记录
行为分析
分析用户行为模式
超市的购物小票
1.3 Java常见日志框架对比
框架
特点
适用场景
Spring Boot默认支持
Log4j
老牌日志框架,配置灵活
传统Java项目
是(1.x)
Log4j2
Log4j升级版,性能更好
高性能需求项目
是
Logback
SLF4J原生实现,性能好
Spring Boot默认
是
JUL (java.util.logging)
JDK自带,功能简单
简单应用
是
2. Spring Boot日志基础
2.1 默认日志配置
Spring Boot默认使用Logback作为日志框架,并通过spring-boot-starter-logging自动配置。
简单使用示例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
public class MyController {
// 获取Logger实例(通常在每个类中声明)
private static final Logger logger = LoggerFactory.getLogger(MyController
.class);
@GetMapping(
"/hello")
public String hello() {
logger.trace(
"This is a TRACE message");
logger.debug(
"This is a DEBUG message");
logger.info(
"This is an INFO message");
// 最常用
logger.warn(
"This is a WARN message");
logger.error(
"This is an ERROR message");
return "Hello World";
}
}
2.2 日志级别详解
级别
数值
说明
使用场景
TRACE
0
最详细的跟踪信息
开发阶段深度调试
DEBUG
1
调试信息
开发阶段问题排查
INFO
2
运行重要信息
生产环境常规监控
WARN
3
潜在问题警告
需要注意但不紧急的问题
ERROR
4
错误事件但不影响系统
需要关注的问题
FATAL
5
严重错误导致系统退出
极少使用
通俗理解:就像医院的分诊系统,TRACE是全面体检,DEBUG是专科检查,INFO是常规体检,WARN是轻微症状,ERROR是需要立即处理的病症。
3. 日志配置详解
3.1 配置文件格式
Spring Boot支持以下格式的日志配置文件:
logback-spring.xml (推荐)logback.xmlapplication.properties/application.yml中的简单配置
3.2 application.properties配置
# 设置全局日志级别
logging.level.root=WARN
# 设置特定包日志级别
logging.level.com.myapp=DEBUG
# 文件输出配置
logging.file.name=myapp.log
# 或者使用logging.file.path指定目录
logging.file.path=/var/logs
# 日志格式配置
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-
5level %logger{
36} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-
5level %logger{
36} - %msg%n
# 日志文件大小限制和保留策略
logging.logback.rollingpolicy.max-file-size=
10MB
logging.logback.rollingpolicy.max-history=
7
3.3 logback-spring.xml详细配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 定义变量 -->
<property name="LOG_PATH" value="./logs" />
<property name="APP_NAME" value="my-application" />
<!-- 控制台输出appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 文件输出appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<maxFileSize>10MB
</maxFileSize>
<maxHistory>30
</maxHistory>
<totalSizeCap>1GB
</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 异步日志appender -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512
</queueSize>
<discardingThreshold>0
</discardingThreshold>
<appender-ref ref="FILE" />
</appender>
<!-- 日志级别配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_FILE" />
</root>
<!-- 特定包日志级别 -->
<logger name="com.myapp" level="DEBUG" />
<logger name="org.springframework" level="WARN" />
<!-- 环境特定配置 -->
<springProfile name="dev">
<logger name="com.myapp" level="TRACE" />
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE" />
</root>
</springProfile>
</configuration>
3.4 配置项详细解析
3.4.1 Appender类型
Appender类型
作用
适用场景
ConsoleAppender
输出到控制台
开发环境调试
RollingFileAppender
滚动文件输出
生产环境持久化
SMTPAppender
邮件发送日志
错误报警
DBAppender
数据库存储日志
日志分析系统
AsyncAppender
异步日志
高性能需求
3.4.2 RollingPolicy策略
策略类型
特点
配置示例
TimeBasedRollingPolicy
按时间滚动
%d{yyyy-MM-dd}.log
SizeAndTimeBasedRollingPolicy
按大小和时间滚动
%d{yyyy-MM-dd}.%i.log
FixedWindowRollingPolicy
固定窗口滚动
myapp.%i.log.zip
3.4.3 日志格式模式
模式
说明
示例输出
%d
日期时间
2023-01-01 12:00:00
%thread
线程名
main
%level
日志级别
INFO
%logger
Logger名称
com.myapp.MyClass
%msg
日志消息
User login success
%n
换行符
-
%X
MDC内容
{key:value}
4. 高级日志功能
4.1 MDC (Mapped Diagnostic Context)
MDC用于在日志中添加上下文信息,如用户ID、请求ID等。
使用示例:
import org.slf4j.MDC;
@RestController
public class OrderController {
private static
final Logger logger = LoggerFactory.getLogger(OrderController
.class);
@GetMapping("/order/{id}")
public Order getOrder(
@PathVariable String id) {
// 添加上下文信息
MDC.put(
"userId",
"user123");
MDC.put(
"orderId", id);
MDC.put(
"ip",
"192.168.1.1");
try {
logger.info(
"Fetching order details");
// 业务逻辑...
return orderService.getOrder(id);
}
finally {
// 清除MDC
MDC.clear();
}
}
}
logback配置中添加MDC:
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X
{userId}] [%X
{orderId}] %-
5level %logger
{36} - %msg%n<
/pattern>
4.2 日志过滤
可以根据条件过滤日志,只记录满足条件的日志。
示例:只记录包含"important"的ERROR日志
<appender name=
"IMPORTANT_ERRORS" class=
"ch.qos.logback.core.rolling.RollingFileAppender">
<file>important-errors.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>message.contains("important")</expression>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 其他配置 -->
</appender>
4.3 日志异步输出
对于性能敏感的应用,可以使用异步日志减少I/O阻塞。
配置示例:
<appender name=
"ASYNC" class=
"ch.qos.logback.classic.AsyncAppender">
<!-- 队列大小,默认256 -->
<queueSize>512</queueSize>
<!-- 当队列剩余容量小于此值时,丢弃TRACE/DEBUG/INFO级别日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 引用实际的appender -->
<appender-ref ref="FILE" />
</appender>
4.4 多环境日志配置
利用Spring Profile为不同环境配置不同的日志策略。
<!-- 开发环境配置 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- 生产环境配置 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE" />
</root>
<logger name="org.hibernate.SQL" level="WARN" />
</springProfile>
5. 日志最佳实践
5.1 日志记录原则
有意义的消息:避免无意义的日志,如"进入方法"、"退出方法"不好:logger.info("Method called");好:logger.info("Processing order {} for user {}", orderId, userId);适当的日志级别:ERROR:需要立即处理的问题WARN:潜在问题INFO:重要业务事件DEBUG:调试信息TRACE:详细跟踪避免副作用:日志记录不应该改变程序行为不好:logger.debug("Value: " + expensiveOperation());好:logger.debug("Value: {}", () -> expensiveOperation());
5.2 性能优化
使用参数化日志:
// 不好 - 即使日志级别高于DEBUG也会执行字符串拼接
logger.debug("User " + userId + " accessed resource " + resourceId);
// 好 - 只有在DEBUG级别才会格式化字符串
logger.debug("User {} accessed resource {}", userId, resourceId);
异步日志:对于文件、网络等慢速Appender使用异步方式合理配置日志级别:生产环境适当提高日志级别
5.3 日志监控与分析
ELK Stack (Elasticsearch, Logstash, Kibana)SplunkPrometheus + Grafana (配合日志指标)
6. 常见问题与解决方案
6.1 日志文件过大
解决方案:
配置合理的滚动策略
logs/app-%d{yyyy-MM-dd}.%i.log
50MB
30
5GB
</rollingPolicy>
定期归档和清理旧日志
6.2 日志输出混乱
解决方案:
使用MDC区分不同请求配置合理的日志格式<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
6.3 日志性能问题
解决方案:
使用异步日志减少不必要的日志记录避免在日志中执行复杂操作
7. 实战案例:电商系统日志配置
7.1 完整logback-spring.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 公共属性 -->
<property name="LOG_HOME" value="/var/logs/ecommerce" />
<property name="APP_NAME" value="ecommerce-app" />
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{userId}] [%X{requestId}] %-5level %logger{36} - %msg%n" />
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 主日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<maxFileSize>50MB
</maxFileSize>
<maxHistory>30
</maxHistory>
<totalSizeCap>10GB
</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 错误日志单独文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}-error.log
</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR
</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}-error-%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>90
</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 异步appender -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024
</queueSize>
<discardingThreshold>0
</discardingThreshold>
<includeCallerData>true
</includeCallerData>
<appender-ref ref="FILE" />
</appender>
<!-- 异步错误appender -->
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512
</queueSize>
<appender-ref ref="ERROR_FILE" />
</appender>
<!-- 慢查询日志 -->
<appender name="SLOW_QUERY" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/slow-query.log
</file>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>
(message.contains("SQL") || message.contains("Query"))
&& (contains("took") || contains("duration"))
&& (getMarker() != null && getMarker().contains("SLOW"))
</expression>
</evaluator>
<onMatch>ACCEPT
</onMatch>
<onMismatch>DENY
</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/slow-query-%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>60
</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}
</pattern>
<charset>UTF-8
</charset>
</encoder>
</appender>
<!-- 根日志配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_FILE" />
<appender-ref ref="ASYNC_ERROR" />
</root>
<!-- 特定包配置 -->
<logger name="com.ecommerce.dao" level="DEBUG" />
<logger name="com.ecommerce.service" level="INFO" />
<logger name="org.hibernate.SQL" level="WARN" />
<logger name="org.springframework" level="WARN" />
<!-- 开发环境特殊配置 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
<logger name="com.ecommerce" level="DEBUG" />
</springProfile>
<!-- 生产环境特殊配置 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE" />
<appender-ref ref="ASYNC_ERROR" />
</root>
<logger name="com.ecommerce.api" level="INFO" additivity="false">
<appender-ref ref="SLOW_QUERY" />
</logger>
</springProfile>
</configuration>
7.2 日志使用示例代码
@RestController
@RequestMapping("/orders")
public class OrderController {
private static
final Logger logger = LoggerFactory.getLogger(OrderController
.class);
private static
final Marker SLOW_OPERATION_MARKER = MarkerFactory.getMarker(
"SLOW");
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(
@PathVariable String id, HttpServletRequest request) {
// 设置MDC
MDC.put(
"requestId", UUID.randomUUID().toString());
MDC.put(
"userId", request.getRemoteUser());
MDC.put(
"clientIp", request.getRemoteAddr());
try {
logger.info(
"Fetching order with id: {}", id);
long startTime = System.currentTimeMillis();
Order order = orderService.getOrderById(id);
long duration = System.currentTimeMillis() - startTime;
if (duration >
500) {
logger.warn(SLOW_OPERATION_MARKER,
"Slow order retrieval took {}ms for order {}", duration, id);
}
logger.debug(
"Order details: {}", order);
return ResponseEntity.ok(order);
}
catch (OrderNotFoundException e) {
logger.error(
"Order not found with id: {}", id, e);
return ResponseEntity.notFound().build();
}
catch (Exception e) {
logger.error(
"Unexpected error fetching order {}", id, e);
return ResponseEntity.internalServerError().build();
}
finally {
MDC.clear();
}
}
@PostMapping
public ResponseEntity<Order> createOrder(
@RequestBody OrderRequest request,
@RequestHeader("X-User-Id") String userId) {
MDC.put(
"userId", userId);
try {
logger.info(
"Creating new order for user {}", userId);
logger.debug(
"Order request details: {}", request);
Order createdOrder = orderService.createOrder(request, userId);
logger.info(
"Order created successfully with id: {}", createdOrder.getId());
return ResponseEntity.ok(createdOrder);
}
catch (InvalidOrderException e) {
logger.warn(
"Invalid order request from user {}: {}", userId, e.getMessage());
return ResponseEntity.badRequest().build();
}
finally {
MDC.clear();
}
}
}
8. 日志框架切换
8.1 切换到Log4j2
排除默认的Logback依赖添加Log4j2依赖<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-logging
</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-log4j2
</artifactId>
</dependency>
8.2 Log4j2配置示例
log4j2-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%X{requestId}] %-5level %logger{36} - %msg%n
</Property>
<Property name="LOG_DIR">logs
</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<RollingFile name="File" fileName="${LOG_DIR}/app.log"
filePattern="${LOG_DIR}/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="50MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<Async name="AsyncFile" bufferSize="512">
<AppenderRef ref="File"/>
</Async>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="AsyncFile"/>
</Root>
<Logger name="com.myapp" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
</Loggers>
</Configuration>
9. 日志监控与告警
9.1 常用监控指标
指标
说明
监控方式
ERROR日志频率
单位时间内ERROR日志数量
计数/分钟
慢请求日志
超过阈值的请求响应时间
日志内容分析
关键操作日志
如登录、支付等
日志内容匹配
日志量突变
日志量突然增加或减少
数量对比
9.2 集成Prometheus监控
@Configuration
public class LogMetricsConfig {
private static final Counter errorCounter = Counter.build()
.name(
"log_errors_total")
.help(
"Total number of ERROR logs")
.labelNames(
"logger",
"exception")
.register();
@
Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application",
"my-spring-app");
}
@
Bean
public ApplicationListener<ApplicationReadyEvent> logMetricsListener() {
return event -> {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.getLoggerList().forEach(logger -> {
((ch.qos.logback.classic.Logger) logger).addAppender(
new AppenderBase<ILoggingEvent>() {
@
Override
protected void append(ILoggingEvent event) {
if (
event.getLevel().isGreaterOrEqual(Level.ERROR)) {
errorCounter.labels(
event.getLoggerName(),
event.getThrowableProxy() !=
null ?
event.getThrowableProxy().getClassName() :
"none"
).inc();
}
}
});
});
};
}
}
本文结束得如此突然,就像你永远猜不到老板下一秒要改的需求。
头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。