福娃一地金 發表於 2025-9-26 09:00:00

Java 日志管理的黄金组合: SLF4J+Logback

<h2 id="slf4j-的前世今生">slf4j 的前世今生</h2>
<h3 id="log4jlog4j2和logback的历史故事">Log4J、Log4J2和LogBack的历史故事</h3>
<p>使用过Log4J和LogBack的同学肯定能发现,这两个框架的设计理念极为相似,使用方法也如出一辙。其实这个两个框架的作者都是一个人,Ceki Gülcü,俄罗斯程序员。</p>
<p>Log4J 最初是基于Java开发的日志框架,发展一段时间后,作者Ceki Gülcü将Log4j捐献给了Apache软件基金会,使之成为了Apache日志服务的一个子项目。 又由于Log4J出色的表现,后续又被孵化出了支持C, C++, C#, Perl, Python, Ruby等语言的子框架。</p>
<p>然而,伟大的程序员好像都比较有个性。Ceki Gülcü由于不满Apache对Log4J的管理,决定不再参加Log4J的开发维护。“出走”后的Ceki Gülcü另起炉灶,开发出了LogBack这个框架(SLF4J是和LogBack一起开发出来的)。LogBack改进了很多Log4J的缺点,在性能上有了很大的提升,同时使用方式几乎和Log4J一样,许多用户开始慢慢开始使用LogBack。</p>
<p>由于受到LogBack的冲击,Log4J开始式微。终于,2015年9月,Apache软件基金业宣布,Log4j不在维护,建议所有相关项目升级到Log4j2。Log4J2是Apache开发的一个新的日志框架,改进了很多Log4J的缺点,同时也借鉴了LogBack,号称在性能上也是完胜LogBack。性能这块后面我会仔细分析。</p>
<h3 id="那slf4j和这些有什么关系">那slf4j和这些有什么关系?</h3>
<p>SLF4J的全称是Simple Logging Facade for Java,slf4j是<strong>门面模式</strong>的典型应用</p>
<p>回答这个问题之前,我们先看看如果需要用上面几个日志框架来打印日志,一般怎么做,具体代码如下:</p>
<pre><code class="language-java">// 使用log4j,需要log4j.jar
import org.apache.log4j.Logger;
Logger logger_log4j = Logger.getLogger(Test.class);
logger_log4j.info("Hello World!");

// 使用log4j2,需要log4j-api.jar、log4j-core.jar
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger_log4j2 = LogManager.getLogger(Test.class);
logger_log4j2.info("Hello World!");

// logback,需要logback-classic.jar、logback-core.jar
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
Logger logger_logback = new LoggerContext().getLogger(Test.class); logger_logback.info("Hello World!");
</code></pre>
<p>从上面不难看出,使用不同的日志框架,就要引入不同的jar包,使用不同的代码获取Logger。如果项目升级需要更换不同的框架,那么就需要修改所有的地方来获取新的Logger,这将会产生巨大的工作量。</p>
<p>基于此,<strong>我们需要一种接口来将不同的日志框架的使用统一起来,这也是为什么要使用slf4j的原因。</strong></p>
<p><strong>SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。</strong></p>
<blockquote>
<p>注意:类似的日志门面还有Jakarta Common logging(JCL),主要区别在于,SLF4J是一个比较新的日志框架,它更加灵活,性能更好,支持更多的日志实现,而且JCL基于classLoader在运行时动态加载日志框架,可能会产生很多意想不到的安全问题</p>
</blockquote>
<p>通过上面的介绍,我们可以知道JCL和SLF4J都是日志门面(Facade),而Log4J、Log4J2和LogBack都是子系统角色(SunSystem),也就是具体的日志实现框架。他们的关系如下,JUL是JDK本身提供的一种实现。</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202509211008864.png" alt="" loading="lazy"></p>
<p><strong>SLF4J 的核心价值</strong>在于它提供了<strong>解耦设计</strong>​:应用程序代码只依赖 slf4j-api,而具体日志实现(如 Logback、Log4j2)可以在部署时动态绑定。这种架构使得项目升级或更换日志框架变得非常简单,无需修改业务代码中的日志记录语句。</p>
<h3 id="slf4j怎么和日志框架结合使用">slf4j怎么和日志框架结合使用?</h3>
<p>使用slf4j后,当我们在打印日志时,就可以使用下面的方式:</p>
<pre><code class="language-java">import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("Hello World!")
</code></pre>
<p>这又引入了另外一个问题,slf4j如何决定使用哪个框架日志呢,并且引入哪些jar包呢?官方为我们准备了组合依赖:</p>
<ul>
<li><strong>slf4j + logback</strong>: slf4j-api.jar + logback-classic.jar + logback-core.jar</li>
<li><strong>slf4j + log4j</strong>: slf4j-api.jar + slf4j-log412.jar + log4j.jar</li>
<li><strong>slf4j + jul</strong>: slf4j-api.jar + slf4j-jdk14.jar</li>
<li><strong>也可以只用slf4j无日志实现</strong>:slf4j-api.jar + slf4j-nop.jar</li>
</ul>
<h2 id="slf4j-的基本使用">SLF4J 的基本使用</h2>
<p>在代码中使用 SLF4J 非常简单,首先需要通过 Maven 添加依赖:</p>
<pre><code class="language-xml">&lt;!-- Maven 依赖配置 --&gt;
&lt;dependencies&gt;
    &lt;!-- SLF4J API --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
      &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
      &lt;version&gt;2.0.9&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;!-- Logback Classic 实现 --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
      &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
      &lt;version&gt;1.4.11&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<p>在代码中获取 Logger 并记录日志:</p>
<pre><code class="language-java">import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExampleService {
    // 获取Logger实例
    private static final Logger logger = LoggerFactory.getLogger(ExampleService.class);
   
    public void processUser(String userId) {
      logger.debug("Processing user: {}", userId); // 使用占位符,避免字符串拼接
      logger.info("User processing started");
      
      try {
            // 业务逻辑
            logger.info("User {} processed successfully", userId);
      } catch (Exception e) {
            logger.error("Failed to process user: " + userId, e); // 记录异常信息
      }
    }
}
</code></pre>
<p>SLF4J 的 ​<strong>参数化日志消息</strong>​(使用 <code>{}</code>占位符)是其一个重要特性,它不仅有更好的可读性,还能<strong>提升性能</strong>——当日志级别高于当前配置时(如配置为 INFO 级别时调用 debug 语句),不会执行字符串拼接操作。</p>
<h2 id="logback-架构与核心组件">Logback 架构与核心组件</h2>
<p>Logback 是 SLF4J 的<strong>原生实现</strong>框架,由三个相互协作的模块组成,每个模块都有独特的功能定位</p>
<h3 id="logback-的模块化设计">Logback 的模块化设计</h3>
<table>
<thead>
<tr>
<th>模块</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>logback-core</code></td>
<td>核心模块,提供基础日志服务,其他两个模块都依赖它</td>
</tr>
<tr>
<td><code>logback-classic</code></td>
<td>实现 SLF4J API,完全兼容 SLF4J 接口,同时兼容 Log4j</td>
</tr>
<tr>
<td><code>logback-access</code></td>
<td>与 Servlet 容器集成,用于 HTTP 访问日志记录</td>
</tr>
</tbody>
</table>
<p>Logback 相比 Log4j 有显著<strong>性能提升</strong>,特别是在异步日志记录方面,减少了线程阻塞和上下文切换开销。它还支持<strong>自动重载配置</strong>,可以在不重启应用的情况下修改日志配置。</p>
<h3 id="logback-核心概念">Logback 核心概念</h3>
<p>Logback 架构基于三个核心概念:Logger、Appender 和 Layout/Encoder。</p>
<p>​<strong>Logger(日志记录器)​</strong>​:</p>
<ul>
<li>采用<strong>层次化命名</strong>​(如 <code>com.example.service.UserService</code>)</li>
<li>具有<strong>继承性</strong>​:子 Logger 继承父 Logger 的 Appender 和 Level</li>
<li>通过 <code>LoggerFactory.getLogger()</code>获取实例</li>
</ul>
<p>​<strong>Appender(输出目的地)​</strong>​:</p>
<p>Appender 负责将日志事件发送到不同目标,Logback 支持多种 Appender:</p>
<table>
<thead>
<tr>
<th>Appender 类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ConsoleAppender</code></td>
<td>输出到控制台</td>
</tr>
<tr>
<td><code>FileAppender</code></td>
<td>输出到文件</td>
</tr>
<tr>
<td><code>RollingFileAppender</code></td>
<td>滚动文件(按大小/时间)</td>
</tr>
<tr>
<td><code>SocketAppender</code></td>
<td>发送到远程服务器</td>
</tr>
<tr>
<td><code>SMTPAppender</code></td>
<td>邮件告警</td>
</tr>
<tr>
<td><code>KafkaAppender</code></td>
<td>发送到 Kafka(需扩展)</td>
</tr>
</tbody>
</table>
<p>​<strong>Layout/Encoder(格式化器)​</strong>​:</p>
<p>定义日志输出格式,常用占位符包括:</p>
<ul>
<li><code>%d{yyyy-MM-dd HH:mm:ss.SSS}</code>:时间戳</li>
<li><code>%level</code>:日志级别</li>
<li><code>%thread</code>:线程名</li>
<li><code>%logger{36}</code>:Logger 名(缩写)</li>
<li><code>%msg</code>:日志消息</li>
<li><code>%n</code>:换行符</li>
</ul>
<h2 id="logback-配置详解与案例">Logback 配置详解与案例</h2>
<p>Logback 支持 XML 和 Groovy 两种配置格式,其中 XML 是最常用的方式。下面通过实际案例详细讲解 Logback 的配置。</p>
<h3 id="基础配置结构">基础配置结构</h3>
<p>Logback 配置文件通常命名为 <code>logback.xml</code>或 <code>logback-spring.xml</code>(Spring 环境),放置在 <code>src/main/resources/</code>目录下。</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;configuration scan="true" scanPeriod="30 seconds"&gt;
   
    &lt;!-- 定义变量 --&gt;
    &lt;property name="LOG_HOME" value="./logs"/&gt;
    &lt;property name="APP_NAME" value="myapp"/&gt;
   
    &lt;!-- 开发环境开关 --&gt;
    &lt;springProfile name="dev"&gt;
      &lt;property name="LOG_LEVEL" value="DEBUG"/&gt;
    &lt;/springProfile&gt;
    &lt;springProfile name="prod"&gt;
      &lt;property name="LOG_LEVEL" value="INFO"/&gt;
    &lt;/springProfile&gt;
   
    &lt;!-- 控制台输出 --&gt;
    &lt;appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"&gt;
      &lt;encoder&gt;
            &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %level %logger{36} - %msg%n&lt;/pattern&gt;
      &lt;/encoder&gt;
    &lt;/appender&gt;
   
    &lt;!-- 更多Appender配置 --&gt;
   
    &lt;!-- 根日志器 --&gt;
    &lt;root level="${LOG_LEVEL:-INFO}"&gt;
      &lt;appender-ref ref="CONSOLE"/&gt;
    &lt;/root&gt;
   
    &lt;!-- 特定包的日志级别 --&gt;
    &lt;logger name="com.example.service" level="DEBUG"/&gt;
&lt;/configuration&gt;
</code></pre>
<h3 id="console-appender-配置">Console Appender 配置</h3>
<p>Console Appender 用于将日志输出到控制台,是开发环境中最常用的 Appender:</p>
<pre><code class="language-xml">&lt;appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder&gt;
      &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
    &lt;/encoder&gt;
    &lt;!-- 过滤器:只输出INFO及以上级别 --&gt;
    &lt;filter class="ch.qos.logback.classic.filter.ThresholdFilter"&gt;
      &lt;level&gt;INFO&lt;/level&gt;
    &lt;/filter&gt;
&lt;/appender&gt;
</code></pre>
<h3 id="file-appender-与滚动策略">File Appender 与滚动策略</h3>
<p>生产环境中通常需要将日志输出到文件,并使用滚动策略防止文件过大:</p>
<pre><code class="language-xml">&lt;!-- 滚动文件输出(按天) --&gt;
&lt;appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"&gt;
    &lt;file&gt;${LOG_HOME}/${APP_NAME}.log&lt;/file&gt;
    &lt;rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"&gt;
      &lt;!-- 每天生成一个文件 --&gt;
      &lt;fileNamePattern&gt;${LOG_HOME}/archive/%d{yyyy-MM-dd}/${APP_NAME}.%i.log.gz&lt;/fileNamePattern&gt;
      &lt;!-- 保留30天 --&gt;
      &lt;maxHistory&gt;30&lt;/maxHistory&gt;
      &lt;!-- 单个文件最大100MB --&gt;
      &lt;timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"&gt;
            &lt;maxFileSize&gt;100MB&lt;/maxFileSize&gt;
      &lt;/timeBasedFileNamingAndTriggeringPolicy&gt;
    &lt;/rollingPolicy&gt;
    &lt;encoder&gt;
      &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
    &lt;/encoder&gt;
&lt;/appender&gt;
</code></pre>
<h3 id="日志级别配置">日志级别配置</h3>
<p>Logback 支持多个日志级别,合理配置级别对系统性能和可观测性至关重要</p>
<pre><code class="language-xml">&lt;!-- 根日志器设置 --&gt;
&lt;root level="${LOG_LEVEL:-INFO}"&gt;
    &lt;appender-ref ref="CONSOLE"/&gt;
    &lt;appender-ref ref="FILE"/&gt;
&lt;/root&gt;

&lt;!-- 特定包/类级别设置 --&gt;
&lt;!-- 特定日志器:将 com.example.service 包的日志级别设为 DEBUG,且日志仅输出到文件,不传递给根日志器(避免控制台重复输出) --&gt;
&lt;logger name="com.example.service" level="DEBUG" additivity="false"&gt;
    &lt;appender-ref ref="FILE" /&gt;
&lt;/logger&gt;

&lt;!-- 特定日志器:将 org.springframework 包的日志级别设为 WARN,减少不必要的日志输出 --&gt;
&lt;logger name="org.springframework" level="WARN" /&gt;
</code></pre>
<p>在这个配置中:</p>
<ul>
<li>绝大多数日志遵循根的 <code>INFO</code>级别设置。</li>
<li>唯独 <code>com.example.service</code>包下的日志可以输出 <code>DEBUG</code>级别及以上的内容,并且这些调试信息<strong>只写入文件</strong>,不会出现在控制台(因为 <code>additivity="false"</code>)。</li>
<li>所有来自 <code>org.springframework</code>包的日志,只有 <code>WARN</code>和 <code>ERROR</code>级别才会被记录。</li>
</ul>
<p><strong>根日志器 (<code>&lt;root&gt;</code>)​</strong>​ 和<strong>特定包/类日志器 (<code>&lt;logger&gt;</code>)​</strong>​ 的设置是日志配置的两个核心层面,主要在于作用和范围的区别:</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>根日志器 (<code>&lt;root&gt;</code>)</th>
<th>特定包/类日志器 (<code>&lt;logger&gt;</code>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>​<strong>作用范围</strong>​</td>
<td>​<strong>全局默认</strong>。影响所有未被特定 <code>&lt;logger&gt;</code>明确配置的日志记录器。</td>
<td>​<strong>局部特定</strong>。仅影响通过 <code>name</code>属性指定的包或类及其子包/子类。</td>
</tr>
<tr>
<td>​<strong>配置目的</strong>​</td>
<td>设置应用程序的<strong>基础日志级别和输出策略</strong>。</td>
<td>为<strong>特定模块</strong>提供更精细的日志控制(如更详细或更严格的级别)。</td>
</tr>
<tr>
<td>​<strong>继承性</strong>​</td>
<td>是所有日志器层次的<strong>根节点</strong>,其他日志器默认继承其配置。</td>
<td>从其父日志器(可能是根或其他上层日志器)​<strong>继承</strong>未被自身覆盖的设置。</td>
</tr>
<tr>
<td>​<strong>常用级别</strong>​</td>
<td>生产环境常设为 <code>INFO</code>或 <code>WARN</code>;开发环境可设为 <code>DEBUG</code>。</td>
<td>根据需求灵活设置,如将关注模块设为 <code>DEBUG</code>,将嘈杂的第三方库设为 <code>ERROR</code>。</td>
</tr>
</tbody>
</table>
<p>Logback 支持的日志级别从低到高依次为:</p>
<ul>
<li><code>TRACE</code>:最细粒度的信息,通常只在开发过程中使用</li>
<li><code>DEBUG</code>:logger.debug信息</li>
<li><code>INFO</code>:logger.info 信息</li>
<li><code>WARN</code>:logger.warn 信息</li>
<li><code>ERROR</code>:logger.error 信息</li>
</ul>
<h3 id="高级特性与性能优化">高级特性与性能优化</h3>
<p>Logback 提供了多种高级功能,可以满足复杂场景下的日志需求。</p>
<h4 id="mdcmapped-diagnostic-context">MDC(Mapped Diagnostic Context)</h4>
<p>MDC 用于在日志中添加上下文信息(如请求 ID、用户 ID),非常适合分布式系统跟踪:</p>
<pre><code class="language-java">// 在代码中使用MDC
import org.slf4j.MDC;

public class UserService {
    public void processUserRequest(String userId, String requestId) {
      // 将上下文信息放入MDC
      MDC.put("userId", userId);
      MDC.put("requestId", requestId);
      
      logger.info("Processing user request"); // 自动包含MDC信息
      
      // 业务处理...
      
      // 清理MDC
      MDC.clear();
    }
}
</code></pre>
<p>在配置中使用 MDC:</p>
<pre><code class="language-xml">&lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{userId}] [%X{requestId}] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
</code></pre>
<h4 id="异步日志提升性能">异步日志提升性能</h4>
<p>对于生产环境,特别是高并发场景,使用异步日志可以显著提升性能</p>
<pre><code class="language-xml">&lt;!-- 异步日志(提升性能) --&gt;
&lt;appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"&gt;
    &lt;appender-ref ref="FILE"/&gt;
    &lt;!-- 队列大小 --&gt;
    &lt;queueSize&gt;256&lt;/queueSize&gt;
    &lt;!-- 丢弃级别低于ERROR的日志(可选) --&gt;
    &lt;discardingThreshold&gt;0&lt;/discardingThreshold&gt;
&lt;/appender&gt;
</code></pre>
<h4 id="条件化配置">条件化配置</h4>
<p>Logback 支持根据不同的环境(如开发、测试、生产)使用不同的配置</p>
<pre><code class="language-xml">&lt;!-- 开发环境配置 --&gt;
&lt;springProfile name="dev"&gt;
    &lt;root level="DEBUG"&gt;
      &lt;appender-ref ref="CONSOLE"/&gt;
    &lt;/root&gt;
&lt;/springProfile&gt;

&lt;!-- 生产环境配置 --&gt;
&lt;springProfile name="prod"&gt;
    &lt;root level="INFO"&gt;
      &lt;appender-ref ref="ASYNC_FILE"/&gt;
    &lt;/root&gt;
&lt;/springProfile&gt;
</code></pre>
<h4 id="自定义过滤器">自定义过滤器</h4>
<p>Logback 允许创建自定义过滤器来实现复杂的日志过滤逻辑</p>
<pre><code class="language-xml">&lt;appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"&gt;
        ...
    &lt;!-- 只记录ERROR级别日志 --&gt;
    &lt;filter class="ch.qos.logback.classic.filter.LevelFilter"&gt;
      &lt;level&gt;ERROR&lt;/level&gt;
      &lt;onMatch&gt;ACCEPT&lt;/onMatch&gt;
      &lt;onMismatch&gt;DENY&lt;/onMismatch&gt;
    &lt;/filter&gt;
&lt;/appender&gt;
</code></pre>
<p>这个 Logback 配置定义了一个名为 <code>FILE</code>的 <code>RollingFileAppender</code>(滚动文件输出器),并为其配置了一个 ​<strong>LevelFilter(级别过滤器)​</strong>。这个过滤器的设置使得该 Appender ​<strong>只记录 <code>ERROR</code>级别的日志</strong>,而拒绝所有其他级别的日志(如 DEBUG、INFO、WARN 等)。</p>
<table>
<thead>
<tr>
<th><strong>配置项</strong></th>
<th>​<strong>值/类型</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><code>&lt;filter class&gt;</code></strong>​</td>
<td><code>LevelFilter</code></td>
<td>使用级别过滤器,根据日志事件的级别进行过滤。</td>
</tr>
<tr>
<td>​<strong><code>&lt;level&gt;</code></strong>​</td>
<td><code>ERROR</code></td>
<td>设置过滤器的级别为 <code>ERROR</code>。</td>
</tr>
<tr>
<td>​<strong><code>&lt;onMatch&gt;</code></strong>​</td>
<td><code>ACCEPT</code></td>
<td>​<strong>当日志事件的级别与过滤器设置的级别(<code>ERROR</code>)匹配时,接受(ACCEPT)该日志事件</strong>,允许其被输出。</td>
</tr>
<tr>
<td>​<strong><code>&lt;onMismatch&gt;</code></strong>​</td>
<td><code>DENY</code></td>
<td>​<strong>当日志事件的级别与过滤器设置的级别不匹配时,拒绝(DENY)该日志事件</strong>,该日志将不会被此 Appender 输出。</td>
</tr>
</tbody>
</table>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/19103282
頁: [1]
查看完整版本: Java 日志管理的黄金组合: SLF4J+Logback