日志文件选用
目前项目里采用的是 log4j2 ,选择原因如下
Java 选择日志方案
SLF4J 1.x与SLF4J 2.x比对
以下是 Spring Boot 2.x(基于 SLF4J 1.x)与 Spring Boot 3.x(基于 SLF4J 2.x)的详细对比
特性 | Spring Boot 2.x (SLF4J 1.x) | Spring Boot 3.x (SLF4J 2.x) |
---|---|---|
SLF4J 版本 | SLF4J 1.7.x(如 1.7.36) | SLF4J 2.0.x(如 2.0.7) |
Java 版本要求 | Java 8-17 | Java 17+ |
默认日志实现 | Logback | Logback(适配 SLF4J 2.x) |
模块化支持 | 部分支持(需额外配置) | 完全支持(JPMS,module-info.java) |
性能优化 | 常规实现 | 零分配日志记录器、Lambda 延迟求值 |
结构化日志 | 需依赖第三方库(如 Logstash encoder) | 原生支持 StructuredArguments |
API 风格 | 传统 if (logger.isInfoEnabled()) | 支持 Lambda 表达式(logger.info(() -> …)) |
桥接器版本 | 兼容 SLF4J 1.x 桥接器 | 需升级到 SLF4J 2.x 桥接器 |
虚拟线程支持 | 不支持(Java 19+ 虚拟线程需手动适配) | 原生支持虚拟线程(Logback 1.4.x 优化线程上下文管理) |
日志参数绑定 | 仅支持基础类型占位符({}) | 支持复杂对象绑定(logger.info(“User: {}”, userObj)) |
MDC 性能 | 基于 ThreadLocal 的 MDC(存在线程切换开销) | 优化 MDC 存储结构(减少高并发场景下的上下文切换损耗) |
日志门面扩展 | 扩展接口有限(需自定义 Appender) | 新增 SPI 扩展点(如 LoggerContextFactory) |
类加载隔离 | 依赖传统类加载器(多模块环境可能冲突) | 兼容模块化类加载器(减少依赖冲突) |
安全管理器支持 | 部分支持(需手动配置 SecurityManager) | 增强安全管理器适配(原生支持 Java SecurityManager) |
SLF4J 2.x 的核心价值
-
面向未来的日志标准
-
虚拟线程/云原生/Serverless友好设计
-
结构化日志原生支持(OpenTelemetry集成)
-
-
三重性能突破
-
内存占用降低30%(对比SLF4J 1.x)
-
异步吞吐提升5倍(对比Logback原生异步)
-
GC压力减少40%
-
Spring Boot 日志配置:
Logback:
- 无需额外配置,默认提供 SLF4J 2.x + Logback
Log4j2:
- 排除 spring-boot-starter-logging 依赖,
- 添加 spring-boot-starter-log4j2 依赖
- 添加 disruptor 依赖
- 排查依赖冲突
mvn dependency:tree -Dincludes="org.slf4j,ch.qos.logback,org.apache.logging.log4j,log4j"
特别注意:nacos导致log4j2不输出日志
当自定义日志文件名称(log4j2-dev.xml)或者自定义日志文件地址(classpath:log/log4j2-dev.xml)时,日志不生效!
因为nacos 默认日志文件名以及地址为 classpath:nacos-log4j2.xml
原因分析:
1、在 com.alibaba.nacos.client.logging 有一段逻辑如下
//大致意思为 // nacos会尝试加载 `ch.qos.logback.classic.Logger` 这个类, // 加载成功的话, 说明项目使用的是 logback 日志框架, // 加载不到的话, 就是使用 log4j2private NacosLogging() {try {Class.forName("ch.qos.logback.classic.Logger");nacosLogging = new LogbackNacosLogging();isLogback = true;} catch (ClassNotFoundException e) {nacosLogging = new Log4J2NacosLogging();}}
2、进入到 Log4J2NacosLogging 中,可以看到默认的日志文件地址为 classpath:nacos-log4j2.xml。
3、 进入到 Log4J2NacosLogging 的 loadConfiguration() 方法中。
4、从代码中看到, nacos 会重新加载日志的配置,如果在项目 resources 下没有配置 nacos-log4j2.xml文件,则会加载 com.alibaba.nacos.client 自带的nacos-log4j2.xml文件。
5、然而 com.alibaba.nacos.client 自带的配置文件没有设置日志的 Root,就会报下面的错误,导致log4j2不输出日志,日志文件配置也就失效了!
WARN No Root logger was configured, creating default ERROR-level Root logger with Console appender
如何解决 nacos 导致 log4j2 不输出日志的问题
如果想更换文件名或者地址,必须在 Nacos初始化 之前更新日志文件目录
-
方法一:更改 nacos 默认的加载地址
(1)使用JVM参数
# 在启动命令中加入: -Dnacos.logging.config=classpath:log/log4j2-dev.xml
(2)必须在SpringApplication.run之前配置
public static void main(String[] args) {System.setProperty("nacos.logging.config", "classpath:log/log4j2-dev.xml");SpringApplication.run(UrlShortenerDomainApplication.class, args);}
-
方法二:禁用nacos绑定的日志启用,转为log4j2自身的日志加载
(1)使用JVM参数
# 在启动命令中加入: -Dnacos.logging.default.config.enabled=false
(2)在SpringApplication.run之前设置
public static void main(String[] args) {System.setProperty("nacos.logging.default.config.enabled", "false");SpringApplication.run(UrlShortenerDomainApplication.class, args); }
logback与log4j2比对表格
特性 | Log4j2 (v2.x+) | Logback (v1.4.x+) |
---|---|---|
项目背景 | Apache 基金会维护,Log4j 的升级版 | SLF4J 官方实现,由 Log4j 创始人开发 |
性能 | ⭐⭐⭐⭐⭐ 异步日志吞吐量更高(LMAX Disruptor 技术) | ⭐⭐⭐⭐ 同步日志性能优异, 异步略低于 Log4j2, 可自定义配置自研的MpscArrayQueue(基于 JCTools) |
配置格式 | XML/JSON/YAML/Properties | XML/Groovy |
异步机制 | ✅ LMAX Disruptor • 无锁环形队列 • 线程绑定优化 | ⚠️ BlockingQueue • ArrayBlockingQueue • 易发生竞争 |
自动重载配置 | ✅ 支持热更新 | ✅ 支持热更新 |
异步日志 | ✅ 内置高性能异步(AsyncLogger) | ✅ 需依赖 logback-classic 实现 |
过滤机制 | ✅ 强大(复合过滤器、脚本支持) | ✅ 基础过滤(ThresholdFilter 等) |
输出格式 | ✅ 自定义 Layout(支持 PatternLayout 等) | ✅ 类似 Log4j2,兼容性强 |
多路输出 | ✅ 灵活 Appender 组合 | ✅ 类似 Log4j2(Appender 嵌套) |
依赖管理 | 无依赖(核心独立) | 依赖 SLF4J API |
插件扩展 | ✅ 模块化插件系统 | ❌ 扩展性较弱 |
云原生支持 | ✅ 更好的 Docker/K8s 集成 | ⚠️ 需手动适配 |
漏洞风险 | ⚠️ 曾曝出高危漏洞(如 Log4Shell) | ✅ 相对安全 |
虚拟线程支持 | ✅ 原生支持 • 异步日志自动兼容虚拟线程 • 线程局部变量(ThreadLocal)优化 | ⚠️ 需手动适配 • 同步日志模式可能阻塞虚拟线程 • MDC(映射诊断上下文)需手动适配 |
GraalVM 原生镜像 | ✅ 官方支持 • 提供原生镜像配置文件模板 • 反射/资源可配置化 | ⚠️ 实验性支持 • 需手动配置反射规则 • 动态类加载可能导致运行时错误 |
结构化日志 | ✅ 原生JSON模板布局 • 零序列化开销 • 直接注入自定义字段 | ⚠️ 依赖logstash-encoder • JSON序列化额外开销 • 反射性能损耗 |
依赖传递 | ❌ 需手动排除冲突依赖 | ✅ 天然兼容 SLF4J 零配置 |
社区活跃度 | ⭐⭐⭐⭐(Apache 支持) | ⭐⭐⭐(维护较慢) |
选型决策树
📌 结论:
- 新项目首选 Log4j2(虚拟线程/GraalVM/云原生三优势)
- 兼容性优先场景可选 Logback(需规避虚拟线程阻塞问题)
- 两者均可通过 log4j-to-slf4j/logback-to-log4j 互相桥接
Log4j2 异步日志记录
AsyncLogger依赖disruptor框架,因此需要在classpath路径下需要额外添加disruptor的jar包。AsyncLogger有两种方式全异步方式和混合同步异步方式。
(1)全异步方式,需要设置系统属性,指定log4j2.contextSelector值为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector,配置文件中使用< Logger>或< Root>标签
(2)混合同步异步方式无需设置系统属性log4j2.contextSelector,在配置文件中使用< AsyncLogger>或< AsyncRoot>标签代替< Logger>或< Root>标签。
引入依赖
<disruptor.version>3.4.4</disruptor.version><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>${disruptor.version}</version></dependency>
完整的xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="0" shutdownTimeout="30"><!-- 配置内容 --><!--日志级别从低到高通常为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--><!-- 1. 核心优化参数 --><Properties><!-- 路径变量 --><!--日志文件名称:这里spring.application.name表示工程名称--><Property name="APP_NAME" value="${spring:spring.application.name}"/><Property name="LOG_DIR">logs/${APP_NAME}</Property><Property name="LOG_FILENAME">${APP_NAME}</Property><Property name="LOG_METRICS_DIR">${LOG_DIR}/${APP_NAME}-metrics</Property><Property name="LOG_METRICS">${APP_NAME}-metrics</Property><!--文件打印格式--><Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} ➤ %msg%n</Property><!--控制台打印日志格式--><Property name="COLOUR_LOG_PATTERN">%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{blue} %style{[%t]}{cyan} %highlight{%-5level} %highlight{%logger{36}} ➤ %m%n</Property><!-- 禁用不必要功能 --><Property name="log4j2.disableSmtp">true</Property><!--插件扫描机制被禁用--><!-- <Property name="log4j2.disableOsgi">true</Property>--><!-- Log4j2 避免设置 includeLocation=true (性能下降5-10倍) --><Property name="INCLUDE_LOCATION">false</Property> <!-- 禁用栈帧获取 --><!-- 开启线程上下文继承 ,如果不开启会导致log4j2异步子线程无法获取TraceId--><Property name="log4j2.isThreadContextMapInheritable">true</Property><!--(1)全异步方式,需要设置系统属性,指定log4j2.contextSelector值为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector,配置文件中使用<Logger>或<Root>标签(2)混合同步异步方式无需设置系统属性log4j2.contextSelector,在配置文件中使用<AsyncLogger>或<AsyncRoot>标签代替<Logger>或<Root>标签。--><Property name="log4j2.contextSelector">org.apache.logging.log4j.core.async.AsyncLoggerContextSelector</Property><!-- 性能关键参数 --><!-- Disruptor 环形缓冲区大小--><Property name="RING_BUFFER_SIZE">262144</Property> <!-- 256K 环形缓冲 --><!-- AsyncAppender 队列大小 --><Property name="ASYNC_QUEUE_SIZE">131072</Property> <!-- 128K 安全队列 --><!-- AsyncLogger 默认缓冲区大小 会被 RING_BUFFER_SIZE 覆盖--><Property name="log4j2.DirectAsyncQueueSize">131072</Property> <!-- 128K 环形缓冲 --><!-- 队列满策略:Discard:直接丢弃新日志DiscardAdvanced:智能丢弃(默认)(先丢弃低级别日志 (INFO/DEBUG))Block:阻塞生产者线程CallerRuns:在生产者线程同步写入--><Property name="log4j2.AsyncQueueFullPolicy">DiscardAdvanced</Property> <!-- 队列满策略 --><!-- 配置DiscardAdvanced策略的阈值 --><Property name="log4j2.DiscardThreshold">0.8</Property> <!-- 队列使用率低于80%时不丢弃 --><!-- 等待策略配置 --><!-- 可选值:Sleeping:低CPU占用,默认策略Blocking:使用锁,吞吐量稳定Yielding:平衡型,适合常规应用BusySpin:最高性能,独占CPU核心PhasedBackoff:混合策略,自动切换--><Property name="log4j2.AsyncLogger.WaitStrategy">Yielding</Property><!-- 如果你选择PhasedBackoff策略,需要额外配置 --><!-- <Property name="log4j2.AsyncLogger.PhasedBackoffSpinThreshold">10000</Property>--><!-- <Property name="log4j2.AsyncLogger.PhasedBackoffLockThreshold">1000</Property>--><!--ScriptEngine 禁用配置,防止日志注入攻击--><Property name="log4j2.disableScriptEngine" value="true"/></Properties><!-- 2. Appender 配置 --><Appenders><!-- 控制台输出 (同步) --><Console name="CONSOLE" target="SYSTEM_OUT"><PatternLayout pattern="${COLOUR_LOG_PATTERN}"/></Console><!-- 高性能文件输出 (异步专用) --><RollingFile name="FILE_ASYNC"fileName="${LOG_DIR}/${LOG_FILENAME}.log"filePattern="${LOG_DIR}/${LOG_FILENAME}-%d{yyyy-MM-dd}.%i.log.gz"immediateFlush="false"> <!-- 批处理写入 --><!-- 结构化日志 可去官网具体查看:https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-exception--><!-- <JsonTemplateLayout eventTemplateUri="classpath:log/LogstashJson.json"/>--><!-- 文本格式--><PatternLayout pattern="${LOG_PATTERN}"/><!-- 滚动策略优化 --><Policies><SizeBasedTriggeringPolicy size="500 MB"/> <!-- 大文件减少IO --><TimeBasedTriggeringPolicy modulate="true" interval="1"/> <!-- 自然时间滚动 --></Policies><!-- 异常日志限流 系统遭遇突发异常(如网络波动、数据库连接失败)时,避免同一异常被频繁记录(如每秒数百次),导致日志文件暴增或 IO 资源被耗尽, 看需求是否开启--><Filters><!-- 仅处理 ERROR 级别日志 --><ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL"/><!-- 限流配置:突发200条,持续每秒3条 (180条/分钟) --><BurstFilter level="ERROR" rate="3" maxBurst="200" onMatch="ACCEPT" onMismatch="DENY"/></Filters></RollingFile><!-- 百万日志监控Appender (可选) --><RollingFile name="METRICS"fileName="${LOG_METRICS_DIR}/${LOG_METRICS}.log"filePattern="${LOG_METRICS_DIR}/${LOG_METRICS}-%d{yyyy-MM-dd}.log.gz"immediateFlush="false"><!-- 结构化指标输出 --><!-- <JsonTemplateLayout eventTemplateUri="classpath:log/LogstashJson.json"/>--><!-- 文本日志格式:%m%n = 只输出原始消息 + 换行 --><PatternLayout pattern="${LOG_PATTERN}"/><Policies><TimeBasedTriggeringPolicy modulate="true" interval="1"/></Policies><!-- 过滤器:仅接受 WARN 及以上级别的日志 --><ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/></RollingFile></Appenders><!-- 3. 异步日志器配置 --><Loggers><!-- 根日志器 (高性能异步) --><!-- <AsyncRoot>与AsyncLogger使用日志框架内置的优化线程池(如 Disruptor 队列)--><!-- <Root>+<Async>附录器 先经 Root 同步处理,再由附录器转为异步--><AsyncRoot level="INFO"includeLocation="${INCLUDE_LOCATION}"><!-- 生产环境配置 --><AppenderRef ref="FILE_ASYNC"/><!-- 开发环境开启控制台--><AppenderRef ref="CONSOLE"/><!-- 百万日志性能监控 --><AppenderRef ref="METRICS"/></AsyncRoot><!-- 关键性能类专用日志器--><!--通过日志配置中的 <AsyncLogger> 可以监控任意指定的类(包括自定义核心类),只需修改 name 属性为目标类的全限定名,并结合日志框架的特性即可实现。Log4j2 的日志器名称匹配采用层级结构,规则如下:全限定名(如com.service.UserService):精确匹配指定类的日志器。部分名称(如UserService):匹配所有以该名称结尾的类。例如:com.service.UserServicecom.monitor.UserServiceUserService(默认包下的类)包路径(如com.service):匹配该包及其子包下的所有类。--><!-- <AsyncLogger name="com.service.PerfService" --><!-- level="DEBUG"--><!-- includeLocation="${INCLUDE_LOCATION}"--><!-- additivity="false">--><!-- <AppenderRef ref="FILE_ASYNC"/> <!– 输出到专用性能日志文件 –>--><!-- </AsyncLogger>--><!-- 虚拟线程监控器 --><AsyncLogger name="VirtualThreadMonitor"level="TRACE"includeLocation="${INCLUDE_LOCATION}"additivity="false"><AppenderRef ref="METRICS"/></AsyncLogger></Loggers>
</Configuration>
配置结构化日志
{"service": "${spring:spring.application.name}","environment": "${spring:spring.profiles.active:-dev}","timestamp": {"$resolver": "timestamp","pattern": {"format": "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"}},"level": {"$resolver": "level","field": "name"},"logger_name": {"$resolver": "logger","field": "name"},"thread_name": {"$resolver": "thread","field": "name"},"message": {"$resolver": "message","stringified": true},"mdc": {"$resolver": "mdc","default": {}},"exception": {"$resolver": "exception","field": "stackTrace","stackTrace": {"maxDepth": 100,"stringified": true}},"source": {"class": {"$resolver": "source","field": "className"},"file": {"$resolver": "source","field": "fileName"},"line": {"$resolver": "source","field": "lineNumber"}}
}
以下是详细解释:
- 缓冲区大小配置
属性名称 | 值 | 说明 |
---|---|---|
RING_BUFFER_SIZE | 262144 | Disruptor 环形缓冲区大小 • 256K 条目容量 (262144 = 256 × 1024) • 适用于 AsyncLogger 的全局设置 |
ASYNC_QUEUE_SIZE | 131072 | AsyncAppender 队列大小 • 128K 条目容量 (131072 = 128 × 1024) • 用于 包装器的独立队列 |
log4j2.DirectAsyncQueueSize | 65536 | AsyncLogger 默认缓冲区大小 • 64K 条目容量 (65536) • 系统级默认值,会被 RING_BUFFER_SIZE 覆盖 |
📌 关键区别:
-
RING_BUFFER_SIZE 控制全局 AsyncLogger 缓冲区
-
ASYNC_QUEUE_SIZE 控制单个 AsyncAppender 队列
-
最终生效值 = 显式配置 > 全局属性 > 默认值(4092)
- 队列满策略配置
属性名称 | 值 | 说明 |
---|---|---|
log4j2.AsyncQueueFullPolicy | DiscardAdvanced | 队列满处理策略 • Discard:直接丢弃新日志 • DiscardAdvanced:智能丢弃(默认) • Block:阻塞生产者线程 • CallerRuns:在生产者线程同步写入 |
log4j2.DiscardThreshold | 0.8 | 智能丢弃阈值 • 仅在 DiscardAdvanced 时生效 • 队列使用率 ≤ 80% 时不丢弃任何日志 • >80% 时优先丢弃低级别日志 (INFO/DEBUG) |
- 高性能等待策略
属性名称 | 值 | 说明 |
---|---|---|
log4j2.AsyncLogger.WaitStrategy | Yielding | 线程等待策略 • Sleeping:低CPU占用,默认策略 • Blocking:使用锁,吞吐量稳定 • Yielding:平衡型,适合常规应用 • BusySpin:最高性能,独占CPU核心 • PhasedBackoff:混合策略,自动切换 |
⚙️ 策略选择建议:
-
常规服务器:Yielding (当前配置)
-
低延迟交易系统:BusySpin
-
资源受限环境:Sleeping
最佳实践建议
-
缓冲区大小
-
生产环境推荐:RING_BUFFER_SIZE=262144 (256K)
-
内存敏感场景:131072 (128K) 足够
-
-
队列满策略
<!-- 智能丢弃 + 80%缓冲保留 -->
<Property name="log4j2.AsyncQueueFullPolicy">DiscardAdvanced</Property>
<Property name="log4j2.DiscardThreshold">0.8</Property>
- 等待策略
<!-- 常规服务器用 Yielding,交易系统用 BusySpin -->
<Property name="log4j2.AsyncLogger.WaitStrategy">Yielding</Property>
💡 性能提示:256K缓冲区 + Yielding策略 + DiscardAdvanced 的组合可在高吞吐(>100k logs/s)场景下保持稳定,同时避免日志丢失风险。
接入skywalking
引入依赖
<skywalking.log4j.version>9.4.0</skywalking.log4j.version><dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-log4j-2.x</artifactId><version>${skywalking.log4j.version}</version><!--与skywalking-agent版本一致--></dependency><dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-trace</artifactId><version>${skywalking.log4j.version}</version></dependency>
skywalking打印日志格式
<!-- Skywalking 打印日志格式 --><Property name="SKYWALKING_TRACE_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%traceId] %logger{36} - %msg%n</Property><!-- SW_CTX中包含 traceId--><!-- SW_CTX: [$serviceName,$instanceName,$traceId,$traceSegmentId,$spanId]--><Property name="SKYWALKING_SW_CTX_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%sw_ctx] %logger{36} - %msg%n</Property>
Appender 配置
<!-- Appender 配置 --><Appenders><!--skywalking grpc 日志收集--><GRPCLogClientAppender name="grpc"><!-- 文本格式--><!-- <PatternLayout pattern="${SKYWALKING_SW_CTX_PATTERN}"/>--><!-- json格式--><JsonTemplateLayout eventTemplateUri="classpath:log/SkyWalkingJson.json"/></GRPCLogClientAppender></Appenders>
日志器配置
<!-- 3. 日志器配置 --><Loggers><Root level="INFO"includeLocation="false"><!-- 开发环境开启控制台--><AppenderRef ref="CONSOLE"/><!-- Skywalking 集成 (可选) --><AppenderRef ref="grpc"/></Root></Loggers>
官网说skywalking支持AsyncLoggerContextSelector
原话为:
Support -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector in gRPC log report.
但我用异步去上传时,日志Log就会有些缺失
报
ERROR 2025-06-13 02:20:30.692 SkywalkingAgent-11-ConfigurationDiscoveryService-0 ConfigurationDiscoveryService : ConfigurationDiscoveryService execute fail.
org.apache.skywalking.apm.dependencies.io.grpc.StatusRuntimeException: INTERNAL: Encountered end-of-stream mid-frame
不知道是不是SkyWalking 代理与 OAP 服务器版本不匹配 导致的
本地版本
- aop: apache-skywalking-apm-bin_10.2.0
- agent: apache-skywalking-apm-9.4.0
Skywalking日志文档: https://skywalking.apache.org/docs/skywalking-java/next/en/setup/service-agent/java-agent/application-toolkit-log4j-2.x/