本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
揭秘Logback
Logback,作为log4j的演进之作,由资深日志专家Ceki Gülcü基于其丰富的行业经验打造。它在性能上实现了显著飞跃,提供了更加高效、资源友好的日志记录体验。Logback的特色功能包括灵活的Marker机制、参数化日志、条件堆栈跟踪和强大的事件过滤。这些功能使得Logback在日志管理上更加灵活和高效。
Logback利用状态对象来简化故障排查,这种创新的设计为用户提供了更便捷的错误处理手段。同时,Logback-core集成了Joran配置系统,为用户提供了更强大、更灵活的配置管理能力,进一步增强了项目的稳定性和可维护性。
Logback体系结构
Logback以其高度通用的基本结构,能够适应多种不同的应用场景和环境需求。目前,它主要由三个核心模块构成:Core、Classic和Access。
- Core模块:Logback的基石,为其他两个模块提供了坚实的基础。
- Classic模块:Core模块的进一步扩展和增强,它不仅继承了Core模块的核心功能,更是对log4j的显著优化和升级。这一模块直接实现了SLF4J API,赋予了用户无缝切换的能力,无论您原先使用的是log4j还是java.util.logging(JUL)等记录系统,现在都能轻松地与Logback实现互操作性。
- Access模块:专为与Servlet容器集成而设计的,它提供了HTTP访问记录的功能,帮助用户更全面地监控和分析Web应用的访问情况,不是本文的重点。
Logback-classic
Logback-classic
这个框架并非独立运作,而是依赖于两个核心组件:slf4j-api.jar
和logback-core.jar
。
-
slf4j-api.jar:Simple Logging Facade for Java(SLF4J)的一个组件,提供了日志记录的API接口。SLF4J本身并不执行实际的日志记录操作,而是作为一个抽象层,允许开发者使用统一的接口来编写日志代码,而不必关心底层的日志实现。
-
logback-core.jar :Logback框架的核心组件,提供了日志记录、格式化、输出等核心功能。它负责处理
slf4j-api.jar
提供的日志请求,并将其转化为实际的日志输出。
三大核心组件
Logback 的核心构建基于三个关键类:Logger、Appender 和 Layout。这三个组件相互协作,为开发者提供了灵活且强大的日志记录功能。开发者不仅能够根据消息的类型和级别来精确控制日志的记录,还能在程序运行期间动态调整日志的输出格式和最终目的地。 通过合理配置和组合这三个组件,开发者可以轻松地构建出符合项目需求的日志系统,实现高效、可靠的日志记录和管理。 导入了 SLF4J API 定义的 Logger 类和 LoggerFactory 类,更明确地说是定义在 org.slf4j 包里的两个类。
Logger上下文
Logger属于高级记录API,相较于System.out.println
的一个显著优势在于其具备的选择性记录能力,即在禁用特定记录语句时,不会对其他语句的输出造成任何影响。这种特性得益于记录隔离机制,它允许开发者根据自定义条件对各类记录语句进行精细化的分类和管理。
Logger原理分析
隔离机制
在logback-classic中,这种分类机制是通过logger来实现的。每个logger都与一个LoggerContext相关联,这个上下文不仅负 责创建logger实例,还以树状结构组织它们,使得日志的管理和配置更加灵活和高效。
通过这种架构,开发者可以轻松地启用或禁用特定logger的输出,而不影响其他logger的正常工作,从而实现对日志输出的精确控制。
命名层次
Logger
扮演着至关重要的角色,它们是通过特定名称标识的实体。这些名称不仅区分大小写,而且遵循一套层次化的命名规范。具体来说,当一个 logger
的名称作为另一个 logger
名称的前缀,并且它们之间通过点号(.
)分隔时,前者便被称为后者的父级。
举例来说,
com.aa
是一个logger
的名称,它充当了名为com.aa.bb
的logger
的直接父级。同样地,java
是java.util
的直接父级,并且由于层次化的命名规则,它也被视为java.util.Vector
父 的flogger
。这种命名机制确保了日志的层次化管理,使得在大型项目中能够清晰、有效地组织和管理日志记录。
若两者之间不存在其他任何中介的 logger
,则前者直接被称为后者的直接父级(或父 logger
)。这种层次结构使得日志管理更加灵活和有序。
根logger
根logger
占据着举足轻重的地位,它位于整个日志层次结构的顶端,是所有其他logger
的共同始祖。与其他logger
一样,根logger
同样可以通过其特定的名称来访问和引用。 这确保了整个日志系统的统一性和灵活性,使得开发者能够方便地对日志进行集中管理和配置。
根logger
的访问方式与其他logger
并无二致,它同样拥有一个独特的名称,使得开发者可以依据此名称轻松地检索和引用它,如下面的代码所示:
java
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
除了根logger
外,其他所有的logger
实例都是通过org.slf4j.LoggerFactory
类的静态方法getLogger
来获取的,这个方法接受一个表示logger
名称的字符串作为参数,并返回与该名称对应的logger
实例。
日志Level的级别继承
Logger
实例被赋予了不同的日志级别,这些级别定义了日志信息的详细程度和重要性。这些级别包括 TRACE
、DEBUG
、INFO
、WARN
和 ERROR
,它们都被定义在 ch.qos.logback.classic.Level
类中。
注意,
Level
类在 Logback 中被设计为final
,这意味着它不能被其他类继承以创建自定义的日志级别。
若某个logger
未被明确指定级别,它将遵循继承机制来确定其有效级别。具体来说,这个logger
会从其层次结构中的最近一个已分配级别的父logger
那里继承其级别。 对于给定的logger
,其有效级别将是从其自身开始,沿着层次结构向上追溯,直至根logger
,所遇到的第一个非空(non-null)级别。因此,为确保日志系统的一致性和完整性,每个logger
都能最终确定其日志级别。根logger
被赋予了一个固定的级别,该级别在整个日志层次结构中充当基准。
默认情况下,根
logger
的级别被设置为DEBUG
,这样即使其他logger
没有明确指定级别,它们也能从根logger
那里继承到一个默认的日志级别,确保日志的输出得到有效控制。
场景案例介绍
场景1:没有配置任何级别配置
Logger 名 | 分配级别 | 有效级别(若未指定) | 备注 |
---|---|---|---|
root | DEBUG | DEBUG | 根Logger默认级别为DEBUG |
X | none | DEBUG | 继承自根Logger的级别 |
X.Y | none | DEBUG | 继承自X Logger的级别(若X也为none,则继续向上继承) |
X.Y.Z | none | DEBUG | 继承自X.Y Logger的级别(若X.Y也为none,则继续向上继承) |
场景2:Logger 级别明确分配,无需继承
Logger 名 | 分配级别 | 有效级别 |
---|---|---|
root | ERROR | ERROR |
X | INFO | INFO |
X.Y | DEBUG | DEBUG |
场景3:部分子类Logger继承,部分不继承
根logger
、X
和X.Y.Z
分别被明确设置了DEBUG
、INFO
和ERROR
级别的日志记录。而对于X.Y
,由于其未指定特定的日志级别,它将从其父级X
那里继承INFO
级别的日志记录设置。这种继承机制确保了日志系统的一致性和灵活性。
Logger 名 | 分配级别 | 有效级别 |
---|---|---|
root | ERROR | ERROR |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | none | INFO |
打印方法与选择规则
打印方法的核心功能在于确定并记录日志的级别。假设log
是一个日志记录器(logger)的实例,那么调用log.info("...")
将触发一条级别为INFO
的日志记录。
请求级别规则
日志记录的启用与否取决于其请求的级别与日志记录器当前有效级别的比较。若请求的级别高于或等于有效级别,则记录被启用;反之,记录将被禁用。 记录请求级别为 p,其 logger 的有效级别为 q,只有则当 p>=q时,该请求才会被执行。该规则是 logback 的核心。级别排序为:TRACE < DEBUG < INFO < WARN < ERROR。
获取Logger
java
Logger logger = LoggerFactory.getLogger("com.xxx");
// 设其级别为 INFO
logger.setLevel(Level.INFO);
Logger barlogger = LoggerFactory.getLogger("com.xx.xx");
// 该请求有效,因为 WARN >= INFO
logger.warn("Low fuel level.");
// 该请求无效,因为 DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");
// 名为"com.foo.Bar"的 logger 实例 barlogger, 从"com.xx"继承级别
// 因此下面的请求有效,因为 INFO >= INFO.
barlogger.info("Located nearest gas station.");
// 该请求无效,因为 DEBUG < INFO.
barlogger.debug("Exiting gas station search");
在SLF4J(Simple Logging Facade for Java)的规范中,org.slf4j.Logger
接口并不包含 setLevel()
方法,因此你不能直接在通过SLF4J API获取的日志记录器(logger)实例上调用此方法以设置日志级别。但是,若你采用的是Logback作为底层日志框架,并且确实需要调整日志级别,那么你需要绕过SLF4J的抽象层,直接获取到Logback提供的ch.qos.logback.classic.Logger
实例,然后利用其实例上的setLevel()
方法来设置日志级别。
通过LoggerFactory.getLogger
方法,无论调用多少次,只要传入的名称相同,你将始终获得同一日志记录器对象的引用。这意味着,一旦你获得了Logback的ch.qos.logback.classic.Logger
实例并设置了其日志级别,该设置在应用程序的生命周期内将一直有效,除非你再次更改它。
总结介绍
在深入探讨Logback框架时,我们不仅要了解其核心组件,还需理解当用户调用日志记录器(logger)的打印方法时,Logback是如何处理这些请求的。以下是对用户调用com.xxx.yyy的info()方法时。Logback会比较logger的有效级别与请求的级别。如果请求级别低于有效级别,则日志请求被禁用,不再进行后续处理。
尽管功能全面且多样,但Logback的首要设计考量之一仍是追求卓越的执行速度,这一追求仅次于其对可靠性的高度重视。为了实现更高效的性能表现,Logback的多个核心组件经历了精心优化和多次重构,从而确保了日志记录过程的迅速与流畅。