日志框架简介-Slf4j+Logback入门实践 | 京东云技术团队
前言
随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息。因此,作为工程师,了解主流的日志记录框架非常重要。虽然应用程序的运行结果不受日志的有无影响,但没有日志的应用程序是不完整的,甚至可以说是有缺陷的。优秀的日志系统可以记录操作轨迹、监控系统运行状态和解决系统故障。
Java 日志框架进化史
早期 Java 日志框架没有制定统一的标准,使得很多应用程序会同时使用多种日志框架。Java 日志框架的发展历程大致可分为以下几个阶段:

1.**Log4j:**Apache Log4j是一种基于Java的日志记录工具。该项目由Ceki Gülcü于1999年创建,并几乎成为了Java日志框架的实际标准。
2.**JUL:**Apache 希望将 Log4j 引入 jdk,不过被 sun 公司拒绝了。随后,sun 模仿 Log4j,在 jdk1.4 中引入了 JUL(java.util.logging)。
3.**Commons Logging:**为了解耦日志接口与实现,Apache在2002年推出了JCL(Jakarta Commons Logging)。JCL定义了一套日志接口,具体的实现由Log4j或JUL完成。Commons Logging使用动态绑定来实现日志记录,编码时只需要使用它定义的接口即可,程序运行时会使用ClassLoader来查找和加载底层的日志库,因此可以灵活选择Log4j或JUL来实现日志功能。
4.**Slf4j&Logback:**Ceki Gülcü与Apache基金会在Commons-Logging标准上存在分歧。后来,Ceki Gülcü离开了Apache,并创建了Slf4j和Logback两个项目。Slf4j是一个日志门面,仅提供接口,可以支持Logback、JUL、log4j等日志实现。而Logback则提供了具体的实现。相比于log4j,Logback具有更快的执行速度和更完善的功能。
5.**Log4j 2:**为了保持在Java日志领域的地位,防止JCL和Log4j被Slf4j和Logback取代,Apache在2014年推出了Log4j 2。Log4j 2与log4j不兼容,经过大量深度优化,其性能得到显著提升。
日志框架介绍
在上文中已经提及,目前常用的日志框架有 Log4j,Log4j 2,Commons Logging,Slf4j,Logback,JUL。这些日志框架可以分为两种类型:门面日志和日志系统。
日志门面
**日志门面(Logging Facade)**是一种设计模式,用于在应用程序中实现日志记录的抽象层。它提供了一组统一的接口和方法,即相应的 API,而不提供具体的接口实现。日志门面在使用时,可以动态或者静态地指定具体的日志框架实现,解除了接口和实现的耦合,使用户可以灵活地选择日志的具体实现框架。
日志系统
**日志系统(Logging System)**是指用于记录和管理应用程序运行时产生的日志信息的软件工具或框架。与日志门面相对,它提供了具体的日志接口实现,应用程序通过它执行日志打印的功能,如日志级别管理、日志格式化、日志输出目标设置等。常见的日志系统包括Log4j、Logback、Java Util Logging等。

通过使用日志门面,我们可以在应用程序中使用统一的API进行日志记录,而具体的日志实现可以根据需要选择和配置。这样,我们可以根据项目需求和团队喜好来灵活选择、切换和配置日志系统,而不会对应用程序代码造成太大影响。
避免环形依赖
Slf4j 的作者 Ceki Gülcü 当年因为觉得 Commons-Logging 的 API 设计的不好,性能也不够高,因而设计了 Slf4j。而他为了 Slf4j 能够兼容各种类型的日志系统实现,还设计了相当多的 adapter 和 bridge 来连接,如下图所示:

鉴于此,在引入日志框架依赖的时候要尽力避免,比如以下组合就不能同时出现:
•jcl-over-slf4j 和 slf4j-jcl
•log4j-over-slf4j 和 slf4j-log4j12
•jul-to-slf4j 和 slf4j-jdk14
日志框架的使用选择
常用的组合使用方式是 Slf4j & Logback 组合使用,Commons Logging & Log4j 组合使用。
推荐:
Slf4j & Logback
原因:
1. Slf4j 实现机制决定 Slf4j 限制较少,使用范围更广。相较于 Commons-Logging,Slf4j 在编译期间便静态绑定本地的 Log 库,其通用性要好得多;
2. Logback 拥有更好的性能。Logback 声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高,这个操作在 Logback 中只需 3 ns,而在 Log4j 则需要 30 ns;
3. Slf4j 支持参数化,使用占位符号,代码更为简洁,如下例子:
// 在使用 Commons-Logging 时,通常的做法是 
if(log.isDebugEnabled()){ log.debug("User name: " + user.getName() + " buy goods id :" + good.getId()); 
} // 在 Slf4j 阵营,你只需这么做: 
log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId());
4. Logback 的所有文档是免费提供的,Log4j 只提供部分免费文档而需要用户去购买付费文档;
5. MDC (Mapped Diagnostic Contexts) 用 Filter,将当前用户名等业务信息放入 MDC 中,在日志 format 定义中即可使用该变量。具体而言,在诊断问题时,通常需要打出日志。如果使用 Log4j,则只能降低日志级别,但是这样会打出大量的日志,影响应用性能;如果使用 Logback,保持原定日志级别而过滤某种特殊情况,如 Alice 这个用户登录,日志将打在 DEBUG 级别而其它用户可以继续打在 WARN 级别。实现这个功能只需加 4 行 XML 配置;
6. 自动压缩日志。RollingFileAppender 在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩过程是异步的,因此在压缩过程中应用几乎不会受影响。
Slf4j+Logback入门实践
maven依赖
pom.xml
<!--日志框架接口-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId>
</dependency>
<!--日志框架接口实现-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId>
</dependency>
<!--日志框架核心组件-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId>
</dependency><!--自动化注解工具-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version>
</dependency>
配置文件
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--默认日志配置--><include resource="org/springframework/boot/logging/logback/defaults.xml"/><!-- 控制台日志 --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder charset="UTF-8"><pattern>${CONSOLE_LOG_PATTERN}</pattern></encoder></appender><!-- Info日志 --><appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/${LOG_FILE}-info.log</file><append>true</append><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>NEUTRAL</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/${LOG_FILE}-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 日志文件的路径和名称 --><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>200MB</maxFileSize> <!-- 单个日志文件的最大大小 --></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory> <!-- 保留的历史日志文件数量 --><totalSizeCap>2GB</totalSizeCap> <!-- 所有日志文件的总大小上限 --><cleanHistoryOnStart>true</cleanHistoryOnStart> <!-- 在启动时清除历史日志文件 --></rollingPolicy><encoder charset="UTF-8"><pattern>${FILE_LOG_PATTERN}</pattern></encoder></appender><!-- Warn日志 --><appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/${LOG_FILE}-warn.log</file><append>true</append><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>WARN</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/${LOG_FILE}-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 日志文件的路径和名称 --><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>200MB</maxFileSize> <!-- 单个日志文件的最大大小 --></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory> <!-- 保留的历史日志文件数量 --><totalSizeCap>2GB</totalSizeCap> <!-- 所有日志文件的总大小上限 --><cleanHistoryOnStart>true</cleanHistoryOnStart> <!-- 在启动时清除历史日志文件 --></rollingPolicy><encoder charset="UTF-8"><pattern>${FILE_LOG_PATTERN}</pattern></encoder></appender><!-- Error日志 --><appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/${LOG_FILE}-error.log</file><append>true</append><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/${LOG_FILE}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>200MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory><totalSizeCap>2GB</totalSizeCap><cleanHistoryOnStart>true</cleanHistoryOnStart></rollingPolicy><encoder charset="UTF-8"><pattern>${FILE_LOG_PATTERN}</pattern></encoder></appender><!-- 异步输出 --><appender name="info-asyn" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="FILE-INFO"/><queueSize>512</queueSize> <!-- 异步队列的大小 --></appender><appender name="warn-asyn" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="FILE-WARN"/><queueSize>512</queueSize> <!-- 异步队列的大小 --></appender><appender name="error-asyn" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="FILE-ERROR"/><queueSize>512</queueSize></appender><!-- 应用日志 --><logger name="com.improve.fuqige.bronze" additivity="false"><appender-ref ref="CONSOLE"/><appender-ref ref="FILE-INFO"/><appender-ref ref="FILE-WARN"/><appender-ref ref="FILE-ERROR"/></logger><!-- 总日志出口 --><root level="${logging.level.root}"><appender-ref ref="CONSOLE"/><appender-ref ref="info-asyn"/><appender-ref ref="warn-asyn"/><appender-ref ref="error-asyn"/></root>
</configuration>
applicantion.properties
logging.file=fuqige-bronze
logging.path=XXXXXX/Logs/XXXXXX
logging.level.root=info
logging.level.com.improve.fuqige.bronze=info
logging.pattern.console=%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %yellow([%thread]) %highlight(%-5level) %boldGreen(%logger{80}[LineNumber:%L]): %highlight(%msg%n)
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] %-5level --- [%thread] %logger{80}[LineNumber:%L]: %msg%n
测试用例
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/hello")public String hello() {log.info("进来了!");log.warn("进来了!");log.error("进来了!");return "hello, world! requestId=" + MDC.get("requestId");}
}
参考资料
Java 日志框架: https://zhuanlan.zhihu.com/p/365154773
SLF4J框架常见的用法和最佳实践: https://juejin.cn/post/7215569601161166906
作者:京东零售 张洪
来源:京东云开发者社区 转载请注明来源
相关文章:
 
日志框架简介-Slf4j+Logback入门实践 | 京东云技术团队
前言 随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息。因此,作为工程师,了解主流的日志记录框架非常重要。虽然应用程序的运行结果不受日志的有无影…...
 
c 语言, 随机数,一个不像随机数的随机数
c 语言, 随机数,一个不像随机数的随机数 使用两种方式获取随机数,总感觉使用比例的那个不太像随机数。 方法一: rand() 获取一个随机数,计算这个随机数跟最大可能值 RAND_MAX(定义在 stdlib.h 中…...
Git三种方法从远程仓库拉取指定分支
克隆指定分支 git clone -b dev开发分支 https://github.com/521/springboot-rabbitmq.git切换到远程分支 git checkout -b dev开发分支 origin/dev开发分支参考 Git三种方法从远程仓库拉取指定的某一个分支...
 
7.6分割回文串(LC131-M)
算法: 有很多分割结果,按照for循环去做肯定做不来 这个时候就要想到回溯!那就要画树! 画树 分割的画树过程其实和组合很像。 例如对于字符串aab: 组合问题:选取一个a之后,在ab中再去选取第…...
 
stata回归结果输出中,R方和F值到底是用来干嘛的?
先直接回答问题,R方表示可决系数,反映模型的拟合优度,也就是模型的解释能力如何,也可以理解为模型中的各个解释变量联合起来能够在多大程度上解释被解释变量;F值用于模型整体的统计显著性,对应的P值越小&am…...
 
Windows搭建RTMP视频流服务(Nginx服务器版)
文章目录 引言1、安装FFmpeg2、安装Nginx服务器3、实现本地视频推流服务4、使用VLC或PotPlayer可视化播放器播放视频5、RTSP / RTMP系列文章 引言 RTSP和RTMP视频流的区别 RTSP (Real-Time Streaming Protocol)实时流媒体协议。 RTSP定义流格式ÿ…...
 
IP地址SSL证书
IP地址SSL证书是一种专门针对公网IP地址颁发的数字证书。与常规的域名SSL证书类似,其主要目标是提供数据加密和身份验证。以下几点概述了IP地址SSL证书的重要特性及其申请过程: 1. 保护直接IP访问: 当用户直接通过IP地址访问服务时ÿ…...
 
关于“Python”的核心知识点整理大全49
目录 16.2.10 加亮颜色主题 16.3 小结 第17 章 使用API 17.1 使用 Web API 17.1.1 Git 和 GitHub 17.1.2 使用 API 调用请求数据 17.1.3 安装 requests 17.1.4 处理 API 响应 python_repos.py 注意 17.1.5 处理响应字典 python_repos.py import json i…...
 
爬虫学习(1)--requests模块的使用
前言 什么是爬虫 爬虫是一种自动化工具,用于从互联网或其他计算机网络上获取数据。它可以模拟人的行为,自动访问网页,提取感兴趣的数据,并将其存储到本地计算机或数据库中。爬虫通常用于搜索引擎、数据分析、信息聚合等领域&…...
 
【Vue2 + ElementUI】el-table中校验表单
一. 案例 校验金额 阐述:校验输入的金额是否正确。如下所示,点击【编辑图标】会变为input输入框当,输入金额。当输入框失去焦点时,若正确则调用接口更新金额且变为不可输入状态,否则返回不合法金额提示 <templat…...
 
PgSQL技术内幕 - ereport ERROR跳转机制
PgSQL技术内幕 - ereport ERROR跳转机制 使用客户端执行SQL的时候经常遇到报ERROR错误,然后SQL语句就退出了。当然,事务也会回滚掉。本文我们看下它是如何做到退出SQL语句并回滚事务的。 1、以insert一个numeric类型值为例 表一个字段为numeric(10,2)类型…...
 
【验证概括 SV的数据类型_2023.12.18】
验证概括 验证的过程是保证芯片实现符合规格说明书(Specification,spec)的过程 验证的两项任务: RTL sim:前仿真,验证功能 GLS-Gate (Level Simulation):后仿真,验证功能和时序 验…...
 
如何在无公网IP环境下远程访问Serv-U FTP服务器共享文件
文章目录 1. 前言2. 本地FTP搭建2.1 Serv-U下载和安装2.2 Serv-U共享网页测试2.3 Cpolar下载和安装 3. 本地FTP发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 科技日益发展的今天,移动电子设备似乎成了我们生活的主角,智能…...
 
电子工程师如何接私活赚外快?
对电子工程师来说,利用业余时间接私活是个很常见的技术,不仅可以赚取额外收入,也能提升巩固技术,可以说国内十个工程师,必有五个在接私活养家糊口,如果第一次接私活,该如何做? 很多工…...
 
数据库进阶教学——读写分离(Mycat1.6+Ubuntu22.04主+Win10从)
目录 1、概述 2、环境准备 3、读写分离实验 3.1、安装jdk 3.2、安装Mycat 3.3、配置Mycat 3.3.1、配置schema.xml 3.3.2、配置server.xml 3.4、修改主从机远程登陆权限 3.4.1、主机 3.4.2、从机 3.5、启动Mycat 3.6、登录Mycat 3.7、验证 1、概述 读写分…...
 
MidJourney笔记(9)-daily_theme-docs-describe
/daily_theme 切换 #daily-theme 频道更新的通知。 但我发现在对话框那里,是没有这个命令的: 但官网是有介绍,不知道是不是版本问题还是这个命令已经无效。 但后来,我发现这个命令是要在Midjourney服务对话框那里才有,在我们后面添加的Mid...
鸿蒙 - arkTs:网络请求封装和使用
1. module.json5文件配置网络请求 {"module": {"requestPermissions": [{"name": "ohos.permission.INTERNET"}]} } 2. 在pages同级创建一个文件夹,起名为api 3. api文件夹下创建index.ts文件,文件内容&…...
 
多功能演示工具ProVideoPlayer2 mac特色介绍
ProVideoPlayer2 mac是用于大多数任何生产的首选多功能演示工具。ProVideoPlayer 2是一种动态视频播放和处理媒体服务器,可将视频映射(包括播放和实时视频输入)实时控制到一个或多个输出。包括实时效果,调度,网络同步和…...
 
java设计模式学习之【责任链模式】
文章目录 引言责任链模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用日志示例代码地址 引言 在现实生活中,常常会遇到这样的场景:一个请求或命令需要经过多个层级的处理。例如,一个行政审批流程可能需要通过多个部门的审…...
 
docker 安装可视化工具 Protainer 以及 汉化
一、创建保存数据的卷 安装网址:Install Portainer BE with Docker on Linux - Portainer Documentation docker pull portainer/portainer二、根据portainer镜像创建容器 docker run -d -p 8000:8000 -p 9000:9000\ --name portainer --restartalways \ -v /var/r…...
 
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
 
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
 
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
 
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
 
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)
这是系统中断服务程序的默认处理汇编函数,如果我们没有定义实现某个中断函数,那么当stm32产生了该中断时,就会默认跑这里来了,所以我们打开了什么中断,一定要记得实现对应的系统中断函数,否则会进来一直循环…...
Java中栈的多种实现类详解
Java中栈的多种实现类详解:Stack、LinkedList与ArrayDeque全方位对比 前言一、Stack类——Java最早的栈实现1.1 Stack类简介1.2 常用方法1.3 优缺点分析 二、LinkedList类——灵活的双端链表2.1 LinkedList类简介2.2 常用方法2.3 优缺点分析 三、ArrayDeque类——高…...
手动给中文分词和 直接用神经网络RNN做有什么区别
手动分词和基于神经网络(如 RNN)的自动分词在原理、实现方式和效果上有显著差异,以下是核心对比: 1. 实现原理对比 对比维度手动分词(规则 / 词典驱动)神经网络 RNN 分词(数据驱动)…...
