- 在 Java 中,日志(Logging)是记录程序的 运行状态、调试信息和错误的关键工具。
- 它帮助开发者追踪代码执行流程、排查问题,并为生产环境提供监控支持。
| 特性 | 日志 | 断言 |
|---|
| 用途 | 记录程序运行状态(开发和生产环境) | 验证代码逻辑正确性(开发阶段) |
| 输出控制 | 动态调整级别,灵活过滤 | 需通过 JVM 参数启用/禁用 |
| 性能影响 | 可优化(异步、级别控制) | 禁用时无开销 |
| 错误处理 | 记录错误,不中断程序 | 失败时抛出 AssertionError |
一、日志的核心组件
1、Logger(日志记录器)
作用
- 生成日志事件:通过代码调用(如:logger.info(“message”))触发日志记录。
- 层级结构:基于类或包的命名空间(如:com.example.service)形成继承关系。
- 子 Logger 继承父 Logger 的配置(如:日志级别、Appender)。
- 日志级别控制:决定哪些级别的日志会被记录(如:DEBUG, INFO, ERROR)。
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="SERVICE_FILE" />
<appender-ref ref="Console"/>
</logger>
<Logger name="com.example.dao" level="INFO" additivity="false">
<AppenderRef ref="DAO_FILE"/>
<AppenderRef ref="Console"/>
</Logger>
关键点
- 命名规则:通常使用类的全限定名(如:MyClass.class.getName())作为 Logger 名称。
- 继承性:子包 Logger(如:com.example.service.impl)默认继承父包(如:com.example.service)的配置,除非显式覆盖。
2、Appender(日志输出器)
作用
- 定义日志输出目标:将日志事件发送到指定位置。
- 多目标支持:一个 Logger 可绑定多个 Appender,实现日志的多路复用。
<Logger name="com.example.dao" level="INFO" additivity="false">
<AppenderRef ref="DAO_FILE"/>
<AppenderRef ref="Console"/>
</Logger>
| 类型 | 描述 |
|---|
| ConsoleAppender | 输出到控制台(System.out 或 System.err)。 |
| FileAppender | 输出到单一文件。 |
| RollingFileAppender | 支持文件滚动(按时间或大小分割),避免单个文件过大。 |
| SocketAppender | 将日志发送到远程 Socket 服务器。 |
| AsyncAppender | 异步输出日志,提升性能(需配合其他 Appender 使用)。 |
配置示例(Logback)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
配置示例(log4j2)
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>
<RollingRandomAccessFile name="DebugLog" immediateFlush="true" fileName="${LOG_HOME}/${DEBUG_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${DEBUG_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<Filters>
<ThresholdFilter level="TRACE"/>
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}"/>
</RollingRandomAccessFile>
最佳实践
- 按需选择Appender:生产环境推荐使用 RollingFileAppender 结合异步输出。
- 避免资源竞争:高频日志场景使用 AsyncAppender 减少 I/O 阻塞。
3、Layout(日志格式化器)
作用
- 定义日志格式:将日志事件转换为特定格式的字符串(如:包含时间戳、线程名、日志级别等)。
- 自定义扩展:支持通过占位符或自定义类实现复杂格式(如:JSON、XML)。
常用格式占位符
| 占位符 | 描述 |
|---|
| %d{pattern} | 日期时间(如:%d{yyyy-MM-dd}) |
| %level | 日志级别(如:INFO, ERROR) |
| %logger{length} | Logger 名称(%logger{36} 截断) |
| %msg | 日志消息内容 |
| %n | 换行符 |
| %thread | 线程名 |
配置示例(Log4j2 的 JSON 格式)
<Console name="CONSOLE" target="SYSTEM_OUT">
<JsonLayout compact="true">
<KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd HH:mm:ss}" />
<KeyValuePair key="level" value="%level" />
<KeyValuePair key="message" value="%message" />
</JsonLayout>
</Console>
配置示例(Log4j2 )
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-6level}[%t] %highlight{%logger{36}.%M} %-5line: %msg%xEx%n" disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>
4、Filter(日志过滤器)
作用
- 条件过滤:根据规则决定是否记录某条日志(如:按级别、关键词、来源类名过滤)。
- 多级过滤:支持链式过滤(如:先按级别过滤,再按关键词过滤)。
| 过滤器 | 描述 |
|---|
| ThresholdFilter | 仅允许≥指定级别的日志通过(如:只记录 ERROR 及以上)。 |
| LevelFilter | 精确匹配某个日志级别(如:仅记录 INFO 级别)。 |
| RegexFilter | 通过正则表达式匹配日志消息内容。 |
| MarkerFilter | 根据日志标记(Marker)过滤。 |
配置示例(Log4j2 )
<RollingRandomAccessFile name="ErrorLog" immediateFlush="true" fileName="${LOG_HOME}/${ERROR_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${ERROR_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}"/>
</RollingRandomAccessFile>
5、Logger 上下文(Logger Context)
作用
- 全局管理:统一管理所有 Logger、Appender、Filter 等组件的生命周期和配置。
- 动态调整:支持运行时修改日志级别或重新加载配置(如:Log4j2 的 ConfigurationAdmin)
Log4j2 动态调整示例
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig("com.example.service");
loggerConfig.setLevel(Level.DEBUG);
ctx.updateLoggers(config);
6、异步日志处理(Async Logging)
作用
- 提升性能:将日志I/O操作与业务线程分离,减少阻塞。
- 实现方式:
- Logback:使用 AsyncAppender 包裹其他Appender。
- Log4j2:直接配置 AsyncLogger 或全局异步模式。
配置示例(Log4j2异步日志)
<AsyncLogger name="com.qiaoqiao" level="info" includeLocation="true" additivity="false">
<appender-ref ref="InfoLog"/>
<appender-ref ref="ErrorLog"/>
<appender-ref ref="Console"/>
</AsyncLogger>
7、日志门面(SLF4J)
作用
- 统一接口:提供与具体日志框架无关的API(如:LoggerFactory.getLogger())。
- 桥接器:通过适配器兼容旧框架(如:log4j-over-slf4j 将 Log4j1.x 调用路由到 SLF4J)。
8、小结
- Java 日志系统的核心组件通过分工协作,提供了灵活、高效的日志管理能力:
- Logger 负责生成日志事件。
- Appender 决定日志输出目标。
- Layout 格式化日志内容。
- Filter 过滤无关日志。
- 异步机制 优化性能。
- SLF4J 实现框架解耦。
- 合理配置这些组件(如:按包分离日志、按级别过滤、异步输出),可显著提升系统的可维护性和稳定性。
- 在生产环境中,推荐结合 RollingFileAppender、异步日志和 JSON 格式化,以满足监控和分析需求。
二、日志级别(Level)和 常见日志框架
1、日志级别(Level)
| 级别 | 描述 |
|---|
| TRACE | 最详细的调试信息,通常用于追踪代码执行路径。 |
| DEBUG | 调试信息,用于开发阶段的问题定位。 |
| INFO | 程序运行的关键流程信息(如:启动、配置加载)。 |
| WARN | 潜在问题,不影响程序运行但需关注(如:参数不合法)。 |
| ERROR | 严重错误,导致功能中断(如:数据库连接失败)。 |
| FATAL | 致命错误,可能导致程序崩溃(如:内存溢出)。 |
2、常见日志框架
- JUL (Java Util Logging)
- Java 原生日志框架(java.util.logging),功能简单,但扩展性较弱。
- Log4j 1.x/2.x
- 高性能、灵活的日志框架,支持多种输出格式和异步日志。
- Logback
- Log4j 的继承者,性能更优,原生支持 SLF4J。
- SLF4J (Simple Logging Facade for Java)
- 日志门面(抽象层),允许动态切换底层日志框架(如:Log4j、Logback、JUL)。
三、log4j2 的配置模板
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" monitorInterval="1800" packages="org.apache.logging.log4j.core,io.sentry.log4j2">
<properties>
<property name="LOG_PATTERN" value="%highlight{%d{yyyy-MM-dd HH:mm:ss.SSS} %-6p[%-16t] [%X{traceId}] %logger{36}.%M %-5L : %msg%xEx%n}" />
<property name="LOG_HOME">logs</property>
<property name="DEBUG_LOG_FILE_NAME">settlecenter-debug</property>
<property name="INFO_LOG_FILE_NAME">settlecenter-info</property>
<property name="ERROR_LOG_FILE_NAME">settlecenter-error</property>
<property name="DRUID_LOG_FILE_NAME">settlecenter-druid-sql</property>
<property name="DUBBO_LOG_FILE_NAME">settlecenter-dubbo</property>
<property name="GOMEPAY_LOG_FILE_NAME">settlecenter-gomepay</property>
<property name="ADAPAY_LOG_FILE_NAME">settlecenter-adapay</property>
<property name="SETTLE_NETWORK_LOG_FILE_NAME">settlecenter-settle-network</property>
<property name="every_file_size">500M</property>
<property name="file_count">10</property>
<property name="output_log_level">info</property>
</properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<LevelRangeFilter minLevel="error" maxLevel="trace" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%highlight{%d{MM-dd HH:mm:ss.SSS} %-6p[%-16t] [%X{traceId}] %logger{1.}.%M %-6L : %msg%xEx%n}" disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>
<RollingRandomAccessFile name="DebugLog" immediateFlush="true" fileName="${LOG_HOME}/${DEBUG_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${DEBUG_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<LevelRangeFilter minLevel="debug" maxLevel="trace" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}">
<Delete basePath="${LOG_HOME}/" maxDepth="1">
<IfFileName glob="*debug.log.*-*" />
<IfLastModified age="2d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="InfoLog" immediateFlush="true" fileName="${LOG_HOME}/${INFO_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${INFO_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}">
<Delete basePath="${LOG_HOME}/" maxDepth="1">
<IfFileName glob="*info.log.*" />
<IfLastModified age="10d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="ErrorLog" immediateFlush="true" fileName="${LOG_HOME}/${ERROR_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${ERROR_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<LevelRangeFilter minLevel="error" maxLevel="warn" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}">
<Delete basePath="${LOG_HOME}/" maxDepth="1">
<IfFileName glob="*error.log.*" />
<IfLastModified age="10d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="DruidLog" immediateFlush="true" fileName="${LOG_HOME}/${DRUID_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${DRUID_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}">
<Delete basePath="${LOG_HOME}/" maxDepth="1">
<IfFileName glob="*sql.log.*" />
<IfLastModified age="5d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="DubboLog" immediateFlush="true" fileName="${LOG_HOME}/${DUBBO_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/${DUBBO_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="${file_count}">
<Delete basePath="${LOG_HOME}/" maxDepth="1">
<IfFileName glob="*dubbo.log.*" />
<IfLastModified age="5d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="GoMePayLog" immediateFlush="true" fileName="${LOG_HOME}/payment/${GOMEPAY_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/payment/${GOMEPAY_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<Filters>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="30">
<Delete basePath="${LOG_HOME}/payment/" maxDepth="1">
<IfFileName glob="*gomepay.log.*" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="AdaPayLog" immediateFlush="true" fileName="${LOG_HOME}/payment/${ADAPAY_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/payment/${ADAPAY_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<Filters>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="30">
<Delete basePath="${LOG_HOME}/payment/" maxDepth="1">
<IfFileName glob="*adapay.log.*" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="SettleNetworkLog" immediateFlush="true" fileName="${LOG_HOME}/payment/${SETTLE_NETWORK_LOG_FILE_NAME}.log" filePattern="${LOG_HOME}/payment/${SETTLE_NETWORK_LOG_FILE_NAME}.log.%d{yyyy-MM-dd}-%i">
<Filters>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<DefaultRolloverStrategy max="30">
<Delete basePath="${LOG_HOME}/payment/" maxDepth="1">
<IfFileName glob="*settle-work.log.*" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<Sentry name="Sentry" />
</Appenders>
<Loggers>
<AsyncLogger name="druid.sql.Statement" level="error" additivity="false">
<appender-ref ref="DruidLog"/>
<appender-ref ref="Console"/>
<appender-ref ref="Sentry" level="ERROR" />
</AsyncLogger>
<AsyncLogger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<AsyncLogger name="org.apache.catalina.util.LifecycleBase" level="error" />
<AsyncLogger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<AsyncLogger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<AsyncLogger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<AsyncLogger name="org.crsh.plugin" level="warn" />
<AsyncLogger name="org.crsh.ssh" level="warn"/>
<AsyncLogger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<AsyncLogger name="org.hibernate.validator.internal.util.Version" level="warn" />
<AsyncLogger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<AsyncLogger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<AsyncLogger name="org.thymeleaf" level="warn"/>
<AsyncLogger name="com.qiaoqiao.common.web.exception.DubboExceptionAspect" level="info" includeLocation="true" additivity="false">
<appender-ref ref="DubboLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao.payment.gomepay.util" level="info" includeLocation="true" additivity="false">
<appender-ref ref="GoMePayLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao.payment.adapay.util" level="info" includeLocation="true" additivity="false">
<appender-ref ref="AdaPayLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao.settlecenter.biz.payment.ziyoutong.service.impl" level="info" includeLocation="true" additivity="false">
<appender-ref ref="SettleNetworkLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="org.springframework.jms.listener.DefaultMessageListenerContainer" level="error" includeLocation="true" additivity="false">
<appender-ref ref="ErrorLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="org.springframework.transaction.support.TransactionSynchronizationManager" level="error" includeLocation="true" additivity="false">
<appender-ref ref="ErrorLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao" level="info" includeLocation="true" additivity="false">
<appender-ref ref="InfoLog"/>
<appender-ref ref="ErrorLog"/>
<appender-ref ref="Sentry" level="ERROR" />
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao.settlecenter.dal" level="error" includeLocation="true" additivity="false">
<appender-ref ref="DebugLog"/>
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncLogger name="com.qiaoqiao.common.web.config" level="debug" includeLocation="true" additivity="false">
<appender-ref ref="DebugLog"/>
<appender-ref ref="Console"/>
</AsyncLogger>
<AsyncRoot level="${output_log_level}" includeLocation="true">
<appender-ref ref="Console"/>
<appender-ref ref="DebugLog"/>
<appender-ref ref="InfoLog"/>
<appender-ref ref="ErrorLog"/>
<appender-ref ref="Sentry" level="ERROR" />
</AsyncRoot>
</Loggers>
</Configuration>