- Logback Project
- Reasons to prefer logback over log4j
- Faster implementation
- Extensive battery of tests
- logback-classic speaks SLF4J natively
- Extensive documentation
- Configuration files in XML or Groovy
- Automatic reloading of configuration files
- Graceful recovery from I/O failures I/O故障的优雅恢复
- Automatic removal of old log archives
- Automatic compression of archived log files
- 谨慎的模式
- Lilith
- 配置文件的条件处理
- Filters
- SiftingAppender
- Stack traces with packaging data
- logback -access,即使用大脑的http访问日志,是logback的一个组成部分
- In summary
- The logback manual
- Chapter 1: Introduction
- Chapter 2: Architecture
- Chapter 3: Logback configuration
- Configuration file syntax
- Adding a context listener
Logback Project
Logback的目的是作为流行的log4j项目的继承者,继承log4j遗留下来的内容。
Logback的架构是足够通用的,因此可以应用于不同的环境。目前,logback分为logback-core、logback-classic和logback-access三个模块。
logback-core模块为其他两个模块奠定了基础。logback-classic模块可以被同化为log4j的一个显著改进版本。此外,logback-classic本地实现了SLF4J API,因此您可以随时在logback和其他日志框架(如log4j或java.util.logging (JUL))之间来回切换。
logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供http访问日志功能。注意,您可以轻松地在logback-core上构建自己的模块。
Reasons to prefer logback over log4j
Logback比log4j带来了很多大大小小的改进。他们多得不胜枚举。不过,这里有一个从log4j切换到logback的原因的非详尽列表。请记住,logback在概念上与log4j非常相似,因为两个项目都是由相同的开发人员创建的。如果您已经熟悉log4j,那么您很快就会熟悉使用logback。如果您喜欢log4j,您可能也会喜欢logback。
Faster implementation
基于我们之前在log4j上的工作,logback内部已经被重写,在某些关键执行路径上的执行速度提高了十倍。logback组件不仅更快,而且占用的内存也更少。
Extensive battery of tests
Logback提供了一个非常广泛的测试电池,经过几年的时间和数小时的工作。虽然log4j也进行了测试,但logback将测试提升到了一个完全不同的级别。在我们看来,这是选择logback而不是log4j的最重要原因。您希望您的日志框架即使在不利条件下也非常可靠。
logback-classic speaks SLF4J natively
因为logback-classic中的Logger类本机实现了SLF4J API,所以在调用以logback-classic作为底层实现的SLF4J Logger时,不会产生任何开销。此外,由于logback-classic强烈鼓励使用SLF4J作为它的客户端API,如果您需要切换到log4j或j.u.l.,您可以通过将一个jar文件替换为另一个jar文件来实现。您不需要通过SLF4J API修改代码日志记录。这可以大大减少切换日志记录框架所涉及的工作。
Extensive documentation
Configuration files in XML or Groovy
配置logback的传统方法是通过XML文件。文档中的大多数示例都使用这种XML语法。然而,从logback 0.9.22版本开始,也支持用Groovy编写的配置文件。与XML相比,groovy样式的配置更直观、一致,语法更短。
还有一个工具可以自动将logback.xml文件迁移到logback.groovy。
http://logback.qos.ch/translator/asGroovy.html
Automatic reloading of configuration files
Logback-classic可以在修改后自动重新加载配置文件。扫描过程快速、无争用,并可动态扩展到在数百个线程上每秒数百万次调用。它在应用服务器和JEE环境中也运行得很好,因为它不需要创建单独的线程来进行扫描。
Graceful recovery from I/O failures I/O故障的优雅恢复
Logback的FileAppender及其所有子类,包括RollingFileAppender,都可以从I/O失败中优雅地恢复。因此,如果文件服务器临时故障,您不再需要重新启动应用程序以使日志记录再次工作。一旦文件服务器恢复,相关的logback appender将透明且快速地从之前的错误状态中恢复。
Automatic removal of old log archives
通过设置TimeBasedRollingPolicy或SizeAndTimeBasedFNATP的maxHistory属性,可以控制归档文件的最大数量。如果滚动策略要求每月滚动,并且希望保留一年的日志,只需将maxHistory属性设置为12。超过12个月的归档日志文件将被自动删除。
Automatic compression of archived log files
RollingFileAppender可以在滚动期间自动压缩归档日志文件。压缩总是异步发生的,因此即使对于大的日志文件,您的应用程序在压缩期间也不会阻塞。
谨慎的模式
在谨慎模式下,运行在多个jvm上的多个FileAppender实例可以安全地写入同一个日志文件。由于某些限制,谨慎模式扩展到RollingFileAppender。
Lilith
Lilith是一个日志记录和访问事件查看器。它可以与log4j的电锯相媲美,只不过Lilith被设计成可以毫不畏缩地处理大量日志数据。
配置文件的条件处理
开发人员经常需要在针对不同环境(如开发、测试和生产)的多个日志回退配置文件之间来回切换。这些配置文件有很多共同之处,只是在少数地方有所不同。为了避免重复,logback支持在
Filters
Logback提供了大量的过滤功能,比log4j提供的要多得多。例如,假设您在生产服务器上部署了一个业务关键型应用程序。如果处理的事务量很大,则将日志级别设置为WARN,以便只记录警告和错误。现在,假设您遇到了一个可以在生产系统上复制的bug,但是由于这两个环境(生产/测试)之间未指明的差异,这个bug在测试平台上仍然是不可捉摸的。
使用log4j,您唯一的选择是在生产系统上将日志级别降低到DEBUG,以便识别问题。不幸的是,这将产生大量的日志数据,使分析变得困难。更重要的是,大量的日志记录会影响应用程序在生产系统上的性能。
使用logback,您可以选择将所有用户的日志记录保持在WARN级别,除了负责识别问题的一个用户(例如Alice)。当Alice登录时,她将在DEBUG级别进行日志记录,而其他用户可以继续在WARN级别进行日志记录。可以通过在配置文件中添加4行XML来完成这一壮举。在手册的相关部分搜索MDCFilter。
SiftingAppender
筛选appender是一个惊人的多功能appender。它可以用于根据任何给定的运行时属性分离(或筛选)日志记录。例如,SiftingAppender可以根据用户会话分离日志事件,这样每个用户生成的日志将进入不同的日志文件,每个用户一个日志文件。
Stack traces with packaging data
当logback打印一个异常时,堆栈跟踪将包括打包数据。这是一个由logback-demo web应用程序生成的堆栈跟踪示例。
14:28:48.835 [btpool0-7] INFO c.q.l.demo.prime.PrimeAction - 99 is not a valid valuejava.lang.Exception: 99 is invalidat ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
从上面可以看出,应用程序使用的是Struts版本1.2.9,部署在jetty版本6.1.12下。因此,堆栈跟踪将迅速告知读者异常中插入的类以及它们所属的包和包版本。当您的客户向您发送堆栈跟踪时,作为开发人员,您将不再需要要求他们向您发送关于他们正在使用的包的版本的信息。该信息将是堆栈跟踪的一部分。详情请参阅“%xThrowable”转换词。
这个特性非常有用,以至于一些用户错误地认为它是他们IDE的特性。
logback -access,即使用大脑的http访问日志,是logback的一个组成部分
最后但并非最不重要的是,logback发行版的logback-access模块与Servlet容器(如Jetty或Tomcat)集成,以提供丰富而强大的http访问日志功能。由于logback-access是最初设计的一部分,所以您喜欢的所有logback-classic特性也可以在logback-access中使用。
In summary
我们列出了许多选择logback而不是log4j的原因。鉴于logback建立在我们之前对log4j的工作之上,简单地说,logback只是一种更好的log4j。
The logback manual
完整的logback手册记录了logback框架的最新版本。在超过150页和几十个具体的例子,它涵盖了基本和高级logback功能,包括:
- the overall logback architecture
- 讨论最佳回调实践和反模式
- logback configuration scripts in XML format
- appenders
- encoders
- layouts
- filters
- mapped diagnostic contexts
- Joran, logback’s configuration system
logback手册相当详细地描述了logback API,包括它的特性和设计原理。由logback项目的主要贡献者Ceki Gülcü和Sébastien Pennec编写,logback手册的目标读者是已经熟悉Java语言但刚刚开始使用logback的开发人员,以及有经验的logback用户。在介绍性材料和许多示例的帮助下,新用户应该很快就能跟上进度。
Chapter 1: Introduction
What is logback?
Logback的目的是作为流行的log4j项目的继承者。它由log4j的创始人Ceki Gülcü设计。它建立在十年的设计工业强度测井系统的经验之上。由此产生的产品,即logback,比所有现有的日志系统更快,占用的空间更小,有时甚至大得多。同样重要的是,logback提供了其他日志记录系统所缺少的独特和相当有用的特性。
First Baby Step
Requirements
除了Logback-classic .jar之外,Logback-classic模块还需要在类路径中存在slf4j-api.jar和logback-core.jar。
logback-*.jar文件是logback发行版的一部分,而SLF4J -api-2.0.0-alpha4.jar是随SLF4J一起发布的,这是一个单独的项目。
现在让我们开始使用logback进行实验。
Example 1.1: Basic template for logging
package chapters.introduction;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld1 {public static void main(String[] args) {Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");logger.debug("Hello world.");}}
HelloWorld1类在chapter .introduction包中定义。它首先导入SLF4J API中定义的Logger和LoggerFactory类
在main()方法的第一行,为名为logger的变量赋值一个logger实例,该实例通过调用LoggerFactory类的静态getLogger方法获取。这个日志被命名为“chapters.introduction.HelloWorld1”。main方法继续调用该日志记录器的调试方法,传递“Hello World”作为参数。我们说主方法包含一个DEBUG级别的日志语句,消息为“Hello world”。
注意,上面的示例没有引用任何logback类。在大多数情况下,就日志记录而言,您的类只需要导入SLF4J类。因此,绝大多数(如果不是全部)类将使用SLF4J API,并且不会注意到logback的存在。
您可以使用以下命令启动第一个示例应用程序:
启动HelloWorld1应用程序将在控制台上输出一行代码。通过logback的默认配置策略,当没有找到默认配置文件时,logback将向根日志记录器添加一个ConsoleAppender。
Logback可以使用内置的状态系统报告关于其内部状态的信息。在logback的生命周期中发生的重要事件可以通过一个名为StatusManager的组件访问。目前,让我们通过调用StatusPrinter类的静态print()方法来指示logback打印它的内部状态。
Example: Printing Logger Status
package chapters.introduction;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.core.util.StatusPrinter;public class HelloWorld2 {public static void main(String[] args) {Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2");logger.debug("Hello world.");// print internal stateLoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();StatusPrinter.print(lc);}}
Logback解释说,由于找不到Logback -test.xml和Logback .xml配置文件(稍后讨论),它使用默认策略配置自己,这是一个基本的ConsoleAppender。Appender是一个可以被视为输出目的地的类。追加器存在于许多不同的目的地,包括控制台、文件、Syslog、TCP套接字、JMS等等。用户还可以根据自己的具体情况轻松创建自己的Appenders。
注意,在出现错误的情况下,logback将自动在控制台上打印其内部状态。
前面的例子相当简单。在更大的应用程序中实际的日志记录不会有太大的不同。日志语句的一般模式不会改变。只有配置过程是不同的。但是,您可能希望根据您的需要自定义或配置logback 。后续章节将介绍Logback配置。
注意,在上面的示例中,我们已经指示logback通过调用StatusPrinter.print()方法来打印其内部状态。Logback的内部状态信息在诊断与Logback相关的问题时非常有用。
下面列出了在应用程序中启用日志记录所需的三个步骤。
- 配置logback环境。您可以用几种或多或少复杂的方法来实现这一点。稍后再详细介绍。
- 在您希望执行日志记录的每个类中,通过调用org.slf4j.LoggerFactory的getLogger方法检索Logger实例类,将当前类名或类本身作为参数传递。
- 通过调用该日志记录器实例的打印方法,即debug()、info()、warn()和error()方法,使用该日志记录器实例。这将在配置的appender上产生日志输出。
Building logback
作为其构建工具,logback依赖于Maven,这是一种广泛使用的开源构建工具。
安装Maven之后,构建logback项目(包括它的所有模块)应该非常简单,只需从解压logback发行版的目录中发出mvn install命令即可。Maven将自动下载所需的外部库。
Logback发行版包含完整的源代码,这样您就可以修改部分Logback库并构建自己的版本。只要遵守LGPL许可或EPL许可的条件,您甚至可以重新分发修改后的版本。
要在IDE下构建logback,请参阅类路径设置页面的相关部分。
Chapter 2: Architecture
Logback’s architecture
Logback的基本架构是足够通用的,因此可以在不同的情况下应用。目前,logback分为logback-core、logback-classic和logback-access三个模块。
核心模块为其他两个模块奠定了基础。经典模块扩展了核心。经典模块对应于log4j的一个显著改进的版本。logback -classic本地实现了SLF4J API,这样您就可以在logback和其他日志系统(如JDK 1.4中引入的log4j或java.util.logging (JUL))之间轻松地来回切换。第三个模块称为access,它与Servlet容器集成,以提供http访问日志功能。一个单独的文档包含访问模块文档。
http://logback.qos.ch/access.html
在本文档的其余部分中,我们将编写”logback”来引用logback-classic模块。
Logger, Appenders and Layouts
Logback构建在三个主要类之上: Logger、Appender和 Layout。这三种类型的组件协同工作,使开发人员能够根据消息类型和级别记录消息,并在运行时控制这些消息的格式和报告位置。
Logger类是logback-classic的一部分。另一方面,Appender和Layout接口是logback-core的一部分。作为一个通用模块,logback-core没有loggers的概念。
Logger context
与普通的System.out.println相比,任何日志API的第一个也是最重要的优势在于它能够禁用某些日志语句,同时允许其他语句不受阻碍地打印。该功能假设日志空间(即所有可能的日志语句的空间)是根据开发人员选择的一些标准进行分类的。在经典日志中,这种分类是日志记录器的固有部分。每一个日志记录器都附加到LoggerContext,该context负责制造日志记录器,并将它们排列成树状的层次结构。
日志记录器Loggers是命名实体。它们的名字是区分大小写的,并且遵循分层命名规则:
Named Hierarchy
如果一个日志记录器的名称后跟圆点是后代日志记录器名称的前缀,则该日志记录器被称为另一个日志记录器的祖先。如果一个日志记录器和它的后代日志记录器之间没有祖先,则该日志记录器被称为子日志记录器的父日志记录器。
For example, the logger named “com.foo” is a parent of the logger named “com.foo.Bar”. Similarly, “java” is a parent of “java.util” and an ancestor of “java.util.Vector”. 大多数开发人员应该都熟悉这个命名方案。
根记录器位于记录器层次结构的顶部。它的特殊之处在于,它在一开始就属于每个层次的一部分。与每个日志记录器一样,可以通过其名称检索它,如下所示:
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
All other loggers are also retrieved with the class static getLogger method found in the org.slf4j.LoggerFactory class. 此方法将所需记录器的名称作为参数。下面列出了Logger接口中的一些基本方法。
package org.slf4j;public interface Logger {// Printing methods:public void trace(String message);public void debug(String message);public void info(String message);public void warn(String message);public void error(String message);}
Effective Level aka(又名) Level Inheritance
可以为日志记录器分配级别。可能的级别(TRACE、DEBUG、INFO、WARN和ERROR)的集合在ch.qos.logback.classic.Level类中定义。注意,在logback中,Level类是final类,不能被子类化,因为存在一种更灵活的方法,即Marker对象
如果一个给定的日志记录器没有分配级别,那么它将从其最近的祖先继承一个已分配级别的日志记录器。更正式地:
给定记录器L的有效级别等于其层次结构中的第一个非空级别,从L本身开始,在层次结构中向上一直到根记录器。
为了确保所有日志记录器最终都能继承一个级别,根日志记录器总是有一个指定的级别。默认情况下,该级别为DEBUG。
下面是四个示例,它们具有不同的赋值级别和根据级别继承规则生成的有效(继承)级别。
Example 1
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | none | DEBUG |
| X.Y | none | DEBUG |
| X.Y.Z | none | DEBUG |
在上面的示例1中,只有根日志记录器被分配了一个级别。这个级别值DEBUG由其他记录器X、X. y和X. y . z继承
Example 2
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | ERROR | ERROR |
| X | INFO | INFO |
| X.Y | DEBUG | DEBUG |
| X.Y.Z | WARN | WARN |
在上面的示例2中,所有日志记录器都有一个指定的级别值。级别继承不起作用。
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | INFO | INFO |
| X.Y | none | INFO |
| X.Y.Z | ERROR | ERROR |
在上面的示例3中,日志记录器root、X和X.Y.Z分别被分配为DEBUG、INFO和ERROR级别。Logger X. y从其父X继承其级别值。
Example 4
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | INFO | INFO |
| X.Y | none | INFO |
| X.Y.Z | none | INFO |
在上面的示例4中,日志记录器root和X分别被分配为DEBUG和INFO级别。日志记录器X. y和X. y . z从它们最近的父X继承它们的级别值,X有一个已分配的级别。
Printing methods and the basic selection rule 印刷方法和基本的选择规则
根据定义,打印方法决定日志请求的级别。例如,如果L是一个日志实例,那么语句L. INFO(“..”)是INFO级别的日志语句。
如果日志请求的级别高于或等于日志记录器的有效级别,则称日志请求已启用。否则,该请求将被禁用。如前所述,没有指定级别的日志记录器将从其最近的祖先继承一个级别。这条规则总结如下。
基本的选择规则:如果p >= q,则启用向具有有效级别q的日志记录器发出的级别p的日志请求。
该规则是logback的核心。它假设级别的顺序如下: TRACE < DEBUG < INFO < WARN < ERROR。
以一种更直观的方式,下面是选择规则的工作原理。在下表中,垂直头显示日志请求的级别,由p指定,而水平头显示日志记录器的有效级别,由q指定。行(级别请求)和列(有效级别)的交集是基本选择规则产生的布尔值。
| level of request p |
effective level q | |||||
|---|---|---|---|---|---|---|
| TRACE | DEBUG | INFO | WARN | ERROR | OFF | |
| TRACE | YES | NO | NO | NO | NO | NO |
| DEBUG | YES | YES | NO | NO | NO | NO |
| INFO | YES | YES | YES | NO | NO | NO |
| WARN | YES | YES | YES | YES | NO | NO |
| ERROR | YES | YES | YES | YES | YES | NO |
下面是基本选择规则的一个例子。
import ch.qos.logback.classic.Level;import org.slf4j.Logger;import org.slf4j.LoggerFactory;....// get a logger instance named "com.foo". Let us further assume that the// logger is of type ch.qos.logback.classic.Logger so that we can// set its levelch.qos.logback.classic.Logger logger =(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");//set its Level to INFO. The setLevel() method requires a logback loggerlogger.setLevel(Level. INFO);Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");// This request is enabled, because WARN >= INFOlogger.warn("Low fuel level.");// This request is disabled, because DEBUG < INFO.logger.debug("Starting search for nearest gas station.");// The logger instance barlogger, named "com.foo.Bar",// will inherit its level from the logger named// "com.foo" Thus, the following request is enabled// because INFO >= INFO.barlogger.info("Located nearest gas station.");// This request is disabled, because DEBUG < INFO.barlogger.debug("Exiting gas station search");
Retrieving Loggers
Calling the LoggerFactory.getLogger) method with the same name will always return a reference to the exact same logger object.
Logger x = LoggerFactory.getLogger("wombat");Logger y = LoggerFactory.getLogger("wombat");
X和y指向完全相同的logger对象。
因此,可以配置一个日志记录器,然后在代码的其他地方检索相同的实例,而不需要传递引用。与父母总是在子女之前的亲生父母的基本矛盾是,日志记录程序可以以任何顺序创建和配置。特别是,“父”记录器将找到并链接到其子程序,即使它是在其子程序之后实例化的。
logback环境的配置通常在应用程序初始化时完成。首选的方法是读取配置文件。我们将很快讨论这种方法。
Logback可以很容易地通过软件组件来命名日志记录器。这可以通过在每个类中实例化一个记录器来实现,记录器的名称等于类的完全限定名。这是定义日志记录器的一种有用且简单的方法。由于日志输出带有生成日志记录器的名称,这种命名策略使识别日志消息的来源变得很容易。然而,这只是命名日志记录器的一种可能的(尽管很常见)策略。Logback不限制可能的日志记录器集。作为开发人员,您可以随意命名日志记录器。
然而,根据日志记录器所在的类来命名它们似乎是迄今为止已知的最好的通用策略。
Appenders and Layouts
基于日志记录器有选择地启用或禁用日志记录请求的能力只是问题的一部分。Logback允许将日志请求打印到多个目的地。在logback语言中,输出目的地称为appender。目前,appender存在于控制台、文件、远程套接字服务器、MySQL、PostgreSQL、Oracle和其他数据库、JMS和远程UNIX Syslog守护进程。
记录器可以附加多个追加器。
addAppender方法将一个appender添加到给定的日志记录器。对于给定记录器的每个启用的日志记录请求将被转发到该记录器中的所有追加器以及层次结构中更高的追加器。换句话说,追加器是从记录器层次结构中附加继承的。例如,如果将控制台附加程序添加到根记录器,那么所有启用的日志记录请求将至少打印在控制台上。另外,如果将文件appender添加到日志记录器(例如L)中,那么启用的L和L的子日志请求将打印在文件和控制台上。通过将记录器的可加性标志设置为false,可以覆盖此默认行为,从而使追加器累加不再是可加的。
控制追加器可加性的规则总结如下
Appender Additivity
logger L的日志语句的输出将被输出到所有以L为单位的appender及其祖先。这就是“附加附加性”一词的含义。
但是,如果logger L的祖先(比如P)的可加性标志设置为false,那么L的输出将被定向到L中的所有追加器及其直到并包括P的祖先,而不是P的任何祖先中的追加器。
日志记录器的可加性标志默认设置为true。
下表显示了一个例子:
| Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |
|---|---|---|---|---|
| root | A1 | not applicable | A1 | 因为根日志记录器位于日志记录器层次结构的顶部,所以可加性标志不应用于它。 |
| x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of “x” and of root. |
| x.y | none | true | A1, A-x1, A-x2 | Appenders of “x” and of root. |
| x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders of “x.y.z”, “x” and of root. |
| security | A-sec | false | A-sec | 由于可加性标志被设置为false,所以没有追加器累加。只使用appender A-sec。 |
| security.access | none | true | A-sec | 仅追加“security”,因为“security”中的可加性标志被设置为false。 |
通常情况下,用户不仅希望自定义输出目的地,还希望自定义输出格式。这是通过将布局与appender关联来实现的。布局负责根据用户的意愿格式化日志请求,而appender负责将格式化的输出发送到目的地。作为标准logback架构的一部分,PatternLayout允许用户根据类似于C语言printf函数的转换模式指定输出格式。
For example, the PatternLayout with the conversion pattern “%-4relative [%thread] %-5level %logger{32} - %msg%n” will output something akin to:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一个字段是程序开始后经过的毫秒数。第二个字段是发出日志请求的线程。第三个字段是日志请求的级别。第四个字段是与日志请求相关联的记录器的名称。’-‘后面的文本是请求的消息。
Parameterized logging
假定经典logback中的记录器实现了SLF4J的Logger接口,某些打印方法允许多个参数。这些打印方法变体主要是为了提高性能,同时最小化对代码可读性的影响。
For some Logger logger, writing,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
导致构造消息参数的开销,即将整数i和条目[i]转换为字符串,并连接中间字符串。这与是否记录消息无关。
避免参数构造成本的一种可能的方法是用测试包围日志语句。下面是一个例子
if(logger.isDebugEnabled()) {logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));}
这样,如果日志记录器禁用了调试,就不会产生参数构造的成本。另一方面,如果日志记录器为DEBUG级别启用,您将导致评估日志记录器是否启用的开销,两次: 一次在debugenenabled中,一次在DEBUG中。在实践中,这种开销是不重要的,因为评估日志记录器所花的时间不到实际记录请求所需时间的1%。
Better alternative
存在一种基于消息格式的方便的替代方法。假设entry是一个对象,你可以这样写:
Object entry = new SomeObject();logger.debug("The entry is {}.", entry);
只有在评估是否记录日志之后,并且只有当决策是肯定的,日志记录器实现才会格式化消息并使用条目的字符串值替换“{}”对。换句话说,当日志语句被禁用时,这种形式不会导致参数构造的开销。
下面两行代码将产生完全相同的输出。然而,在禁用日志语句的情况下,第二个变量将比第一个变量的性能高出至少30倍。
logger.debug("The new entry is "+entry+".");logger.debug("The new entry is {}.", entry);
也可以使用两个参数的变体。例如,你可以这样写:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
如果需要传递三个或更多参数,也可以使用Object[]变体。例如,你可以这样写:
Object[] paramArray = {newVal, below, above};logger.debug("Value {} was inserted between {} and {}.", paramArray);
A peek under the hood
在介绍了基本的logback组件之后,现在就可以描述当用户调用记录器的打印方法时logback框架所采取的步骤了。现在让我们分析当用户调用名为com.wombat的日志记录器的info()方法时,logback所采取的步骤。
1. Get the filter chain decision
如果存在,则调用TurboFilter链。Turbo过滤器可以设置上下文范围的阈值,或者根据与每个日志请求相关联的Marker、Level、Logger、消息或Throwable等信息过滤某些事件。如果过滤链的应答为FilterReply.DENY,那么日志请求将被丢弃。如果是FilterReply.中性,然后我们继续下一步,即第二步。如果回复是FilterReply.接受,我们跳过下一个,直接跳到步骤3。
2. Apply the basic selection rule
在这一步,logback将日志记录器的有效级别与请求级别进行比较。如果根据此测试禁用了日志记录请求,那么logback将删除该请求,而不进行进一步处理。否则,将继续执行下一步。
3. Create a LoggingEvent object
如果请求了前面的过滤器,logback将创建一个ch.qos.logback.classic.LoggingEvent对象包含所有请求的相关参数,如日志记录器的请求,请求级别,消息本身,除了可能是传递的请求,当前时间,当前线程,关于发出日志请求的类和MDC的各种数据。注意,其中一些字段是惰性初始化的,这只是在实际需要时才进行初始化。MDC用于用额外的上下文信息修饰日志记录请求。MDC将在随后的一章中讨论。
4. Invoking appenders
在创建LoggingEvent对象之后,logback将调用所有适用的追加器的doAppend()方法,即从记录器上下文继承的追加器。
logback发行版附带的所有追加器都扩展了AppenderBase抽象类,该抽象类在同步块中实现doAppend方法,以确保线程安全。如果存在自定义过滤器,那么AppenderBase的doAppend()方法也会调用附加到该appender的自定义过滤器。自定义过滤器,可以动态地附加到任何附加器,在单独的一章中介绍。
5. Formatting the output
被调用的appender负责格式化日志记录事件。然而,有些(但不是所有)追加器将格式化日志事件的任务委托给布局。布局格式化LoggingEvent实例,并以字符串形式返回结果。注意,一些附加器,如SocketAppender,不会将日志记录事件转换为字符串,而是序列化它。因此,它们没有也不需要布局。
6. Sending out the LoggingEvent
日志记录事件完全格式化后,每个追加器将其发送到目的地。
下面是一个序列UML图,显示了一切是如何工作的。您可能想要单击图像以显示其更大的版本。
Performance
反对日志记录的一个经常被引用的论据是它的计算成本。这是一个合理的担忧,因为即使是中等规模的应用程序也可能生成数千个日志请求。我们的大部分开发工作都花在度量和调整logback的性能上。与这些工作无关,用户仍然应该了解以下性能问题。
1. 日志记录完全关闭时的日志记录性能
通过将根日志记录器的级别设置为level.OFF,可以完全关闭日志记录。当完全关闭日志记录时,日志请求的开销包括方法调用和整数比较。在一台3.2Ghz的奔腾D机器上,这一成本通常在20纳秒左右。
然而,任何方法调用都涉及参数构造的“隐藏”成本。例如,对于一些日志记录器x写入,
x.debug("Entry number: " + i + "is " + entry[i]);
导致构造消息参数的开销,即将整数I和条目[I]转换为字符串,并连接中间字符串,而不管消息是否会被记录。
参数构建的成本可能相当高,这取决于所涉及参数的大小。为了避免参数构造的成本,可以利用SLF4J的参数化日志记录:
x.debug("Entry number: {} is {}", i, entry[i]);
这种变体不会导致参数构造的开销。与之前对debug()方法的调用相比,它会快很多。只有当日志记录请求被发送到附加的附加器时,消息才会被格式化。此外,格式化消息的组件是高度优化的。
尽管如此,将日志语句放置在紧密循环(即非常频繁调用的代码)中是一种双输建议,可能会导致性能下降。即使关闭了日志记录,在紧密循环中日志记录也会减慢应用程序的速度,如果打开日志记录,将生成大量(因此是无用的)输出。
2. 当日志记录打开时,决定是否记录日志的性能。
在logback中,不需要遍历记录器层次结构。日志记录器在创建时知道它的有效级别(也就是说,考虑到级别继承后,它的级别)。如果父记录器的级别发生了更改,那么将联系所有子记录器以注意更改。因此,在基于有效级别接受或拒绝请求之前,记录器可以做出准瞬时决定,而不需要咨询其祖先。
3.实际日志记录(格式化并写入到输出设备)
这是格式化日志输出并将其发送到目标目的地的成本。在这里,我们再次努力使布局(格式化器)尽可能快地执行。对于追加器也是如此。当日志记录到本地机器上的一个文件时,实际日志记录的成本通常是9到12微秒。当登录到远程服务器上的数据库时,它会上升到几毫秒。
尽管logback功能丰富,但其最重要的设计目标之一是执行速度,这一需求仅次于可靠性。为了提高性能,一些logback组件已经被重写了好几次。
Chapter 3: Logback configuration
我们首先介绍配置logback的方法,并使用许多示例配置脚本。Joran, logback所依赖的配置框架将在后面的章节中介绍。
Configuration in logback
将日志请求插入到应用程序代码中需要大量的计划和努力。观察表明,大约4%的代码专门用于日志记录。因此,即使是中等规模的应用程序也会在其代码中包含数千条日志语句。考虑到它们的数量,我们需要工具来管理这些日志语句。
Logback可以通过编程方式配置,也可以使用XML或Groovy格式的配置脚本进行配置。By the way, existing log4j users can convert their log4j.properties files to logback.xml using our PropertiesTranslator web-application.
让我们从logback尝试配置自身的初始化步骤开始讨论:
- Logback试图在类路径中找到一个名为Logback -test.xml的文件。
- 如果没有找到这样的文件,logback将尝试查找名为logback.Groovy的文件类路径中的。
- If no such file is found, it checks for the file logback.xml in the classpath..
- 如果没有找到这样的文件,服务提供者加载工具(在JDK 1.6中引入)将被用来解析com.qos.logback.classic.spi.Configurator接口的实现,方法是通过查找META-INF\services\ch.qos.logback.classic.spi。类路径中的配置器。它的内容应该指定所需的Configurator实现的完全限定类名。
- 如果上述操作都不成功,logback将使用BasicConfigurator自动配置自己,这将导致日志输出被定向到控制台。
最后一步是在没有配置文件的情况下提供默认(但非常基本)日志功能的最后一步。
如果您正在使用Maven,并且将logback-test.xml放在src/test/resources文件夹下,Maven将确保它不会包含在生成的工件中。因此,您可以在测试期间使用一个不同的配置文件,即logback-test.xml,在生产中使用另一个文件,即logback.xml。
Joran解析给定的logback配置文件大约需要100毫秒。为了节省应用程序启动时的毫秒数,您可以使用服务提供者加载工具(上面的第4项)来加载您自己的自定义配置器类,BasicConfigrator可以作为一个很好的起点。
Automatically configuring logback
配置logback的最简单方法是让logback回退到它的默认配置。让我们在一个名为MyApp1的虚拟应用程序中体验一下这是如何实现的。
Example: Simple example of BasicConfigurator usage
package chapters.configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class MyApp1 {final static Logger logger = LoggerFactory.getLogger(MyApp1.class);public static void main(String[] args) {logger.info("Entering application.");Foo foo = new Foo();foo.doIt();logger.info("Exiting application.");}}
该类定义一个静态记录器变量。然后实例化一个Foo对象。Foo类如下所示:
package chapters.configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class Foo {static final Logger logger = LoggerFactory.getLogger(Foo.class);public void doIt() {logger.debug("Did it again!");}}
为了运行本章中的示例,需要确保类路径上存在某些jar文件。详情请参阅安装页面。
假设配置文件logback-test.xml或logback.xml不存在,logback将默认调用BasicConfigurator,这将设置最小配置。这个最小配置包括一个附加到根日志记录器的ConsoleAppender。The output is formatted using a PatternLayoutEncoder set to the pattern %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n。 而且,默认情况下,根日志记录器被分配为DEBUG级别。
因此,命令java chaps .configuration的输出。MyApp1应该类似于:
16:06:09.031 [main] INFO chapters.configuration.MyApp1 - Entering application.16:06:09.046 [main] DEBUG chapters.configuration.Foo - Did it again!16:06:09.046 [main] INFO chapters.configuration.MyApp1 - Exiting application.
MyApp1应用程序通过调用org.slf4j.LoggerFactory 链接到 org.slf4j.Logger(日志记录器类),检索它希望使用的日志记录器,然后继续运行。Note that the only dependencies of the Foo class on logback are through org.slf4j.LoggerFactory and org.slf4j.Logger imports. 。除了配置logback的代码(如果存在这样的代码),客户端代码不需要依赖logback。由于SLF4J允许在其抽象层下使用任何日志记录框架,因此很容易将大量代码从一个日志记录框架迁移到另一个日志记录框架。
使用logback-test.xml或logback.xml进行自动配置
如前所述logback将尝试使用这些文件配置自己。下面的配置文件与我们刚才看到的BasicConfigurator建立的配置文件相当。
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><!-- encoders are assigned the typech.qos.logback.classic.encoder.PatternLayoutEncoder by default --><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="debug"><appender-ref ref="STDOUT" /></root></configuration>
将sample0.xml重命名为logback.xml(或logback-test.xml)后,将其放入可从类路径访问的目录中。运行MyApp1应用程序应该会得到与上一次运行相同的结果。
在警告或错误的情况下自动打印状态消息
如果在解析配置文件期间出现警告或错误,logback将自动在控制台上打印其内部状态数据。请注意,为了避免重复,如果用户显式注册了状态监听器(下面将讨论),则禁用自动状态打印。
在没有警告或错误的情况下,如果您仍然希望检查logback的内部状态,那么您可以通过调用StatusPrinter类的print()来指示logback打印状态数据。下面所示的MyApp2应用程序与MyApp1完全相同,只是增加了两行用于打印内部状态数据的代码。
Example: Print logback’s internal status information
public static void main(String[] args) {// assume SLF4J is bound to logback in the current environmentLoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();// print logback's internal statusStatusPrinter.print(lc);...}
如果一切顺利,您应该会在控制台上看到以下输出
17:44:58,578 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml]17:44:58,671 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set17:44:58,671 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]17:44:58,687 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]17:44:58,812 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Popping appender named [STDOUT] from the object stack17:44:58,812 |-INFO in ch.qos.logback.classic.joran.action.LevelAction - root level set to DEBUG17:44:58,812 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[root]17:44:58.828 [main] INFO chapters.configuration.MyApp2 - Entering application.17:44:58.828 [main] DEBUG chapters.configuration.Foo - Did it again!17:44:58.828 [main] INFO chapters.configuration.MyApp2 - Exiting application.
在输出的末尾,您可以识别前面示例中打印的行。您还应该注意到logback的内部消息,即状态对象,它允许方便地访问logback的内部状态。
Status data
在诊断logback问题时,启用状态数据的输出通常有很长的路要走。注意,错误也可能发生在配置后,例如,当磁盘已满或日志文件无法归档时,由于权限错误。因此,强烈建议您按照下面讨论的方式注册statuslistener。
下一个示例演示了OnConsoleStatusListener的安装。
Example: Registering a status listener
<configuration><-- Recommendation: place status listeners towards the the top of the configuration file --><statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />... the rest of the configuration file</configuration>
StatusListner可以通过配置文件安装,假设:
- the configuration file is found
- the configuration file is well-formed XML.
如果没有满足这两个条件中的任何一个,Joran就不能解释配置文件,特别是
在没有状态消息的情况下,跟踪错误的logback.xml配置文件可能会很困难,特别是在无法轻松修改应用程序源代码的生产环境中。为了帮助识别错误配置文件的位置,可以通过“logback.statusListenerClass的系统属性”设置StatusListener。(将在下面讨论),用于强制输出状态消息。“logback。statusListenerClass的系统属性也可以用于在出现错误时自动生成的输出。”设置StatusListener系统属性(用于强制输出状态消息。“logback。statusListenerClass的系统属性也可以用于在出现错误时自动生成的输出。
Shorthand
作为简写,可以通过将
Example: Basic configuration file using debug mode
<configuration debug="true"><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><!‐- encoders are by default assigned the typech.qos.logback.classic.encoder.PatternLayoutEncoder ‐‐><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="debug"><appender-ref ref="STDOUT" /></root></configuration>
顺便说一下,设置debug=”true”完全等同于前面所示的安装OnConsoleStatusListener。
“logback.statusListenerClass” system property
也可以通过设置“logback.statusListenerClass”Java系统属性来注册状态监听器。
java -Dlogback.statusListenerClass = ch.qos.logback.core.status.OnConsoleStatusListener …
Logback附带了几个状态监听器实现。OnConsoleStatusListener在控制台上打印传入的状态消息,例如在System.out上。OnErrorConsoleStatusListener在System.err上打印传入状态消息。NopStatusListener丢弃传入的状态消息。
注意,如果在配置期间注册了任何状态监听器,特别是如果用户通过“logback.statusListenerClass”指定了一个状态监听器,那么自动状态打印(在出现错误的情况下)将被禁用。因此,通过将NopStatusListener设置为状态侦听器,可以完全停止内部状态打印。
java -Dlogback.statusListenerClass=ch.qos.logback.core.status.NopStatusListener …
Viewing status messages
Logback在StatusManager对象中收集其内部状态数据,通过LoggerContext进行访问。
给定一个StatusManager,您可以访问与logback上下文关联的所有状态数据。为了将内存使用保持在合理的水平,默认的StatusManager实现将状态消息存储在两个独立的部分:头部分和尾部分。头部分存储第一个H状态消息,而尾部分存储最后一个T消息。目前,H=T=150,尽管这些值可能在未来的版本中改变。
Logback-classic附带一个名为ViewStatusMessagesServlet的servlet。这个servlet以HTML表的形式打印与当前LoggerContext关联的StatusManager的内容。下面是示例输出。
要将这个servlet添加到您的web应用程序中,请将以下几行添加到其WEB-INF/web.xml文件中。
<servlet><servlet-name>ViewStatusMessages</servlet-name><servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class></servlet><servlet-mapping><servlet-name>ViewStatusMessages</servlet-name><url-pattern>/lbClassicStatus</url-pattern></servlet-mapping>
ViewStatusMessages servlet将在URL http://host/yourWebapp/lbClassicStatus中可见
Listening to status messages via code
您也可以通过Java代码注册StatusListener。下面是示例代码。
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();StatusManager statusManager = lc.getStatusManager();OnConsoleStatusListener onConsoleListener = new OnConsoleStatusListener();statusManager.add(onConsoleListener);
注意,已注册的状态侦听器将只接收其注册之后的状态事件。它将不会收到以前的消息。因此,把状态监听器注册指令放在配置文件的顶部,放在其他指令之前,通常是一个好主意。
Setting the location of the configuration file via a system property
通过系统属性设置配置文件的位置
您可以使用名为“logback.configurationFile”的系统属性指定默认配置文件的位置。此属性的值可以是URL、类路径上的资源或应用程序外部文件的路径。
java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1
注意,文件扩展名必须是”.xml”或”.groovy”。其他扩展被忽略。显式注册状态监听器可能有助于调试定位配置文件的问题。
鉴于“logback.configurationFile 是一个Java系统属性,它也可以在您的应用程序中设置。但是,必须在创建任何logger实例之前设置system属性。
import ch.qos.logback.classic.util.ContextInitializer;public class ServerMain {public static void main(String args[]) throws IOException, InterruptedException {// must be set before the first call to LoggerFactory.getLogger();// ContextInitializer.CONFIG_FILE_PROPERTY is set to "logback.configurationFile"System.setProperty(ContextInitializer.CONFIG_FILE_PROPERTY, "/path/to/config.xml");...}}
Automatically reloading configuration file upon modification
修改后自动重新加载配置文件
如果指示这样做,logback-classic将扫描配置文件中的更改,并在配置文件更改时自动重新配置自己。为了指示logback-classic在其配置文件中扫描更改并自动重新配置自身,将
Example: Scanning for changes in configuration file and automatic re-configuration
<configuration scan="true">...</configuration>
默认情况下,每分钟扫描一次配置文件的更改。您可以通过设置
<configuration scan="true" scanPeriod="30 seconds" >...</configuration>
如果没有指定时间单位,则假定时间单位为毫秒,这通常是不合适的。当修改默认扫描周期时,请不要忘记指定时间单位。
在幕后,当您将扫描属性设置为true时,ReconfigureOnChangeTask将被安装。此任务在单独的线程中运行,并将检查您的配置文件是否已更改。ReconfigureOnChangeTask也会自动监视任何包含的文件。
由于在编辑配置文件时很容易出错,所以如果配置文件的最新版本有XML语法错误,它将退回到没有XML语法错误的前一个配置文件。
Enabling packaging data in stack traces 在堆栈跟踪中启用打包数据
从版本1.1.4开始,默认情况下禁用打包数据。
如果指示这样做,logback可以包括为它输出的堆栈跟踪行中的每一行打包数据。打包数据由堆栈跟踪行类产生的jar文件的名称和版本组成。打包数据对于识别软件版本问题非常有用。然而,计算它是相当昂贵的,特别是在频繁抛出异常的应用程序中。下面是一个示例输出:
14:28:48.835 [btpool0-7] INFO c.q.l.demo.prime.PrimeAction - 99 is not a valid valuejava.lang.Exception: 99 is invalidat ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
打包数据在默认情况下是禁用的,但可以通过配置启用:
<configuration packagingData="true">...</configuration>
或者,打包数据可以通过在LoggerContext中调用setPackagingDataEnabled(boolean)方法来编程启用/禁用,如下所示:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();lc.setPackagingDataEnabled(true);
Invoking JoranConfigurator directly
Logback依赖于一个名为Joran的配置库,它是Logback -core的一部分。Logback的默认配置机制在它在类路径上找到的默认配置文件上调用JoranConfigurator。如果出于某种原因希望覆盖logback的默认配置机制,可以通过直接调用JoranConfigurator来实现。下一个应用程序MyApp3在一个作为参数传递的配置文件上调用JoranConfigurator。
Example: Invoking JoranConfigurator directly
package chapters.configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.joran.JoranConfigurator;import ch.qos.logback.core.joran.spi.JoranException;import ch.qos.logback.core.util.StatusPrinter;public class MyApp3 {final static Logger logger = LoggerFactory.getLogger(MyApp3.class);public static void main(String[] args) {// assume SLF4J is bound to logback in the current environmentLoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();try {JoranConfigurator configurator = new JoranConfigurator();configurator.setContext(context);// Call context.reset() to clear any previous configuration, e.g. default// configuration. For multi-step configuration, omit calling context.reset().context.reset();configurator.doConfigure(args[0]);} catch (JoranException je) {// StatusPrinter will handle this}StatusPrinter.printInCaseOfErrorsOrWarnings(context);logger.info("Entering application.");Foo foo = new Foo();foo.doIt();logger.info("Exiting application.");}}
这个应用程序获取当前有效的LoggerContext,创建一个新的JoranConfigurator,设置它将要操作的上下文,重置logger上下文,最后要求配置器使用作为参数传递给应用程序的配置文件配置上下文。在出现警告或错误时,将打印内部状态数据。注意,对于多步骤配置,应该忽略context.reset()调用。
Stopping logback-classic
为了释放logback-classic所使用的资源,停止logback上下文总是一个好主意。停止上下文将关闭上下文定义的所有附加到日志记录器的logger,并以有序的方式停止任何活动线程。请同时阅读下面的“关机钩子”部分。
import org.sflf4j.LoggerFactory;import ch.qos.logback.classic.LoggerContext;...// assume SLF4J is bound to logback-classic in the current environmentLoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();loggerContext.stop();
在web应用程序中,以上代码可以从ServletContextListener的contextDestroyed方法中调用,以停止logback-classic并释放资源。从1.1.10版本开始,相应的ServletContextListener会自动为您安装(见下面)。
Stopping logback-classic via a shutdown hook
安装JVM关闭钩子是关闭日志回退和释放相关资源的一种方便的方法。
<configuration debug="true"><!-- in the absence of the class attribute, assumech.qos.logback.core.hook.DefaultShutdownHook --><shutdownHook/>....</configuration>
注意,你可以通过设置class属性来对应你的shutdown钩子的类名来安装你自己制作的shutdown钩子。
默认的关闭钩子,即DefaultShutdownHook,将在指定的延迟(默认为0)之后停止logback上下文。停止上下文将允许在后台运行的任何日志文件压缩任务在30秒内完成。在独立的Java应用程序中,向配置文件中添加
WebShutdownHook or stopping logback-classic in web-applications
Logback-classic会自动要求web服务器安装一个实现ServletContainerInitializer接口的LogbackServletContainerInitializer(可在servlet-api 3中获得)。x和更高版本)。这个初始化器将依次安装和实例化LogbackServletContextListener。当web应用程序停止或重新加载时,这个侦听器将停止当前的logback-classic上下文。
你可以在web应用程序的web.xml文件中设置一个名为logbackDisableServletContainerInitializer的
<web-app><context-param><param-name>logbackDisableServletContainerInitializer</param-name><param-value>true</param-value></context-param>....</web-app>
注意,logbackDisableServletContainerInitializer变量也可以设置为Java系统属性,即OS环境变量。大多数本地设置有优先级,即web-app第一,系统属性第二,操作系统环境最后。
Configuration file syntax
正如您到目前为止已经在手册中看到的(还有大量示例要参考),logback允许您重新定义日志记录行为,而不需要重新编译代码。实际上,您可以很容易地配置logback,以便禁用应用程序某些部分的日志记录,或直接将日志输出到UNIX Syslog守护进程、数据库、日志可视化程序,或将日志记录事件转发到远程logback服务器,该服务器将根据本地服务器策略进行日志记录。例如,通过将日志事件转发到第二台logback服务器。
本节的其余部分介绍配置文件的语法。
正如我们将反复演示的那样,logback配置文件的语法非常灵活。因此,不可能用DTD文件或XML模式指定允许的语法。然而,配置文件的基本结构可以描述为,
Case sensitivity of tag names
由于logback版本0.9.17,与显式规则相关的标记名不区分大小写。例如,
Configuring loggers, or the element
此时,您至少应该对级别继承和基本选择规则有一些了解。否则,除非你是埃及古物学家,logback配置对你来说不会比象形文字更有意义。
使用
Example: Setting the level of a logger
java
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO"/>
<!-- Strictly speaking, the level attribute is not necessary since -->
<!-- the level of the root level is set to DEBUG by default. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
当将上述配置文件作为参数提供给MyApp3应用程序时,它将产生以下输出:
java
17:34:07.578 [main] INFO chapters.configuration.MyApp3 - Entering application.
17:34:07.578 [main] INFO chapters.configuration.MyApp3 - Exiting application.
Note that the message of level DEBUG generated by the “chapters.configuration.Foo” logger has been suppressed 禁止.
您可以配置任意多个日志记录器的级别。在下一个配置文件中,我们设置“chapters.configuration.Foo” 为INFO,但同时将chapters.configuration. foo logger的级别设置为DEBUG。
Example: Setting the level of multiple loggers
java
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO" />
<logger name="chapters.configuration.Foo" level="DEBUG" />
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
使用此配置文件运行MyApp3将在控制台上输出如下:
java
17:39:27.593 [main] INFO chapters.configuration.MyApp3 - Entering application.
17:39:27.593 [main] DEBUG chapters.configuration.Foo - Did it again!
17:39:27.593 [main] INFO chapters.configuration.MyApp3 - Exiting application.
在JoranConfigurator使用sample3.xml配置文件配置了logback之后,下表列出了日志记录器及其级别。
| Logger name | Assigned Level | Effective Level |
| —- | —- | —- |
| root | DEBUG | DEBUG |
| chapters.configuration | INFO | INFO |
| chapters.configuration.MyApp3 | null | INFO |
| chapters.configuration.Foo | DEBUG | DEBUG |
这样,MyApp3类中INFO级别的两个日志语句以及Foo.doIt()中的DEBUG消息都被启用了。注意,根日志记录器的级别总是设置为一个非空值,默认为DEBUG。
请注意,基本选择规则取决于被调用的记录器的有效级别,而不是附加appender的记录器级别。Logback将首先确定日志语句是否启用,如果启用,它将调用日志记录器层次结构中找到的appender,而不管它们的级别。配置文件sample4.xml就是一个很好的例子:
Example: Logger level sample
java
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO" />
<!-- turn OFF all logging (children can override) -->
<root level="OFF">
<appender-ref ref="STDOUT" />
</root>
</configuration>
下表列出了应用sample4.xml配置文件后的记录器及其级别。
| Logger name | Assigned Level | Effective Level |
| —- | —- | —- |
| root | OFF | OFF |
| chapters.configuration | INFO | INFO |
| chapters.configuration.MyApp3 | null | INFO |
| chapters.configuration.Foo | null | INFO |
名为STDOUT的ConsoleAppender是sample4.xml中唯一配置的appender,它被附加到级别设置为OFF的根日志记录器。然而,使用配置脚本sample4.xml运行MyApp3将产生:
java
17:52:23.609 [main] INFO chapters.configuration.MyApp3 - Entering application.
17:52:23.609 [main] INFO chapters.configuration.MyApp3 - Exiting application.
Thus, the level of the root logger has no apparent effect because the loggers in chapters.configuration.MyApp3 and chapters.configuration.Foo classes are all enabled for the INFO level. As a side note, the chapters.configuration logger exists by virtue of its declaration in the configuration file - even if the Java source code does not directly refer to it.
#### Configuring Appenders
appender使用
java
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
这些配置脚本定义了两个名为FILE和STDOUT的追加器。FILE appender记录到一个名为myApp.log的文件。这个appender的编码器是一个PatternLayoutEncoder,输出日期、级别、线程名、记录器名、文件名和日志请求所在的行号、消息和行分隔符。第二个追加器称为STDOUT输出到控制台。此追加器的编码器只输出后跟行分隔符的消息字符串。
通过在appender-ref元素中通过名称引用追加器,追加器被附加到根日志记录器。注意,每个追加器都有自己的编码器。编码器通常不是被设计成由多个追加器共享的。布局也是如此。因此,logback配置文件不提供任何用于共享编码器或布局的语法方法。
#### Appenders accumulate积累
默认情况下,追加器是累积的: logger将记录附加到它自己(如果有的话)的追加器以及附加到它的祖先的所有追加器。因此,将相同的appender附加到多个日志记录器将导致日志输出重复。
Example: Duplicate appender
```java
使用duplicate.xml运行MyApp3将产生以下输出:java
14:25:36.343 [main] INFO chapters.configuration.MyApp3 - Entering application.
14:25:36.343 [main] INFO chapters.configuration.MyApp3 - Entering application.
14:25:36.359 [main] DEBUG chapters.configuration.Foo - Did it again!
14:25:36.359 [main] DEBUG chapters.configuration.Foo - Did it again!
14:25:36.359 [main] INFO chapters.configuration.MyApp3 - Exiting application.
14:25:36.359 [main] INFO chapters.configuration.MyApp3 - Exiting application.
注意重复的输出。名为STDOUT的appender被附加到两个日志记录器上,分别附加到root和chapter .configuration。因为根日志记录器是所有日志记录器和章节的祖先。
Since the root logger is the ancestor of all loggers and _chapters.configuration_ is the parent of both _chapters.configuration.MyApp3_ and _chapters.configuration.Foo_, each logging request made with these two loggers will be output twice, once because _STDOUT_ is attached to _chapters.configuration_ and once because it is attached to _root_.
Appender的可加性不是为新用户设计的陷阱。这是一个非常方便的logback特性。例如,您可以配置日志记录,使日志消息显示在控制台上(对于系统中的所有日志记录器),而只有来自某些特定日志记录器集的消息流到特定的appender。
_Example: Multiple appender_java
In this example, the console appender will log all the messages (for all loggers in the system) whereas only logging requests originating from the _chapters.configuration_ logger and its children will go into the _myApp.log_ file.
<a name="bGPpN"></a>
#### Overriding the default cumulative behaviour
如果发现默认的累积行为不适合您的需要,您可以通过将可加性标志设置为false来覆盖它。因此,日志记录器树中的一个分支可以将输出直接指向一组不同于树中其他部分的追加器。<br />_**Example: Additivity flag**_java
This example, the appender named _FILE_ is attached to the _chapters.configuration.Foo_ logger. Moreover, the _chapters.configuration.Foo_ logger has its additivity flag set to false such that its logging output will be sent to the appender named _FILE_ but not to any appender attached higher in the hierarchy. Other loggers remain oblivious to the additivity setting of the _chapters.configuration.Foo_ logger. Running the MyApp3 application with the _additivityFlag.xml_ configuration file will output results on the console from the _chapters.configuration.MyApp3_ logger. However, output from the _chapters.configuration.Foo_ logger will appear in the _foo.log_ file and only in that file.
<a name="MJZPZ"></a>
### Setting the context name
如前一章所述,每个记录器都附加到记录器上下文。默认情况下,记录器上下文被称为“default”。但是,您可以在<contextName>配置指令的帮助下设置一个不同的名称。注意,一旦设置,记录器上下文名称就不能更改。设置上下文名称是一种简单而直观的方法,可以区分记录到同一目标的多个应用程序。
_**Example: Set the context name and display it**_java
最后一个示例说明了记录器上下文的命名。在布局模式中添加contextName转换词将输出该名称。
<a name="AZogw"></a>
### Variable substitution
本文档的早期版本使用术语“属性替换”而不是术语“变量”。请认为这两个术语是可以互换的,尽管后者的意思更清楚一些。
与许多脚本语言一样,logback配置文件支持变量的定义和替换。变量有一个作用域(见下面)。此外,变量可以在配置文件本身、外部文件、外部资源中定义,甚至可以动态地计算和定义。
变量替换可以发生在配置文件中可以指定值的任何位置。变量替换的语法类似于Unix shell。开始${和结束}之间的字符串被解释为对属性值的引用。对于属性aName,字符串“${aName}”将被aName属性持有的值替换。
由于**HOSTNAME**和**CONTEXT_NAME**变量经常会派上用场,因此它们是自动定义的,并具有上下文作用域。考虑到在某些环境中可能需要一些时间来计算主机名,它的值是延迟计算的(仅在需要时)。而且,可以直接在配置中设置HOSTNAME。
<a name="n0ZIO"></a>
#### Defining variables
变量可以在配置文件本身中一次定义一个,也可以从外部属性文件或外部资源批量加载。由于历史原因,用于定义变量的XML元素是<property>,尽管在logback 1.0.7和以后的版本中,元素<variable>可以互换使用。
下一个示例显示了在配置文件开头声明的变量。然后在文件的下方使用它来指定输出文件的位置。
_Example: Simple Variable substitution _java
java -DUSER_HOME=”/home/sebastien” MyApp2 Example: System Variable substitution
java
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
当需要多个变量时,创建包含所有变量的单独文件可能更方便。下面是如何进行这样的设置。
Example: Variable substitution using a separate file
java
<configuration>
<property file="src/main/java/chapters/configuration/variables1.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
这个配置文件包含一个对名为variables .properties文件的引用。该文件中包含的变量将被读取,然后在局部作用域中定义。这是变量。属性文件可能如下所示。
Example: Variable fileUSER_HOME=/home/sebastien 您也可以引用类路径上的资源而不是文件。
java
<configuration>
<property resource="resource1.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
#### Scopes
属性可以定义为在局部作用域、上下文作用域或系统作用域中插入。本地作用域是默认的。尽管可以从操作系统环境中读取变量,但不可能写入操作系统环境。
一个具有局部作用域的属性从它在配置文件中的定义开始就存在,直到该配置文件的解释/执行结束。因此,每次解析和执行配置文件时,都会重新定义局部作用域中的变量。
具有上下文作用域的属性被插入到上下文中,并一直持续到上下文被清除为止。定义之后,上下文范围内的属性就是上下文的一部分。因此,它在所有日志事件中都可用,包括那些通过序列化发送到远程主机的事件。
一个具有系统作用域的属性被插入到JVM的系统属性中,并持续到JVM被清除为止。
在替换过程中,属性首先在本地作用域中查找,其次在上下文作用域中查找,第三在系统属性作用域中查找,最后在操作系统环境中查找。
java
<configuration>
<property scope="context" name="nodeId" value="firstNode" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/opt/${nodeId}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
在上面的示例中,既然nodeId属性是在上下文中定义的,那么它将在每个日志事件中可用,即使是那些通过序列化发送到远程主机的日志事件。
### Default values for variables
在某些情况下,如果变量没有被声明或者它的值是空的,那么它有一个默认值是可取的。在Bash shell中,可以使用“:-”操作符指定默认值。例如,假设没有定义名为aName的变量,“${aName:-golden}”将被解释为“golden”。
### Nested variables
完全支持变量嵌套。变量的名称、默认值和值定义都可以引用其他变量。
#### value nesting
变量的值定义可以包含对其他变量的引用。假设您希望使用变量不仅指定目标目录,还指定文件名,并将这两个变量合并到名为“destination”的第三个变量中。下面显示的属性文件给出了一个示例。
Example: Nested variable references
java
USER_HOME=/home/sebastien
fileName=myApp.log
destination=${USER_HOME}/${fileName}
注意,在上面的属性文件中,“destination”是由另外两个变量组成的,即“USER_HOME”和“fileName”。Example: Variable substitution using a separate file
java
<configuration>
<property file="variables2.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${destination}</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
#### name nesting
当引用一个变量时,变量名可以包含对另一个变量的引用。例如,如果变量”userid”被赋值为”alice”,则为”${${userid}.password}“引用名为”alice.password”的变量。
#### default value nesting
一个变量的默认值可以引用另一个变量。例如,假设变量’id’未赋值,变量’userid’赋值为”alice”,那么表达式”${id:-${userid}}“将返回”alice”。
### HOSTNAME property
由于HOSTNAME属性通常很方便,所以在配置上下文作用域时自动定义它。
### CONTEXT_NAME property
正如其名称所示,CONTEXT_NAME属性对应于当前日志记录上下文的名称。
### Setting a timestamp
timestamp元素可以根据当前日期和时间定义一个属性。时间戳元素将在后续章节中解释。
### Defining properties on the fly
可以使用java
<configuration>
<define name="rootLevel" class="a.class.implementing.PropertyDefiner">
<shape>round</shape>
<color>brown</color>
<size>24</size>
</define>
<root level="${rootLevel}"/>
</configuration>
在上面的例子中,shape, color和size都是“a.class. implementing.com propertydefiner”的属性。只要在PropertyDefiner实例的实现中存在给定属性的setter, logback就会注入配置文件中指定的适当值。
目前,logback提供了两个相当简单的PropertyDefiner实现。
### Conditional processing of configuration files
开发人员经常需要在针对不同环境(如开发、测试和生产)的多个logback配置文件之间来回切换。这些配置文件有很多共同之处,只是在少数地方有所不同。为了避免重复,logback支持在java
<!-- if-then form -->
<if condition="some conditional expression">
<then>
...
</then>
</if>
<!-- if-then-else form -->
<if condition="some conditional expression">
<then>
...
</then>
<else>
...
</else>
</if>
条件是一个Java表达式,其中只有上下文属性或系统属性可以访问。对于作为参数传递的键,property()或其更短的等效p()方法返回该属性的String值。例如,要访问键为“k”的属性的值,您可以写入property(“k”)或等价的p(“k”)。如果带有“k”键的属性未定义,property方法将返回一个不为空的空字符串。这避免了检查空值的需要。
isDefined()方法可用于检查属性是否已定义。例如,要检查属性“k”是否被定义,你可以写isDefined(“k”)类似地,如果你需要检查一个属性是否为空,isNull()方法被提供。例如:isNull(“k”)。
java
<configuration debug="true">
<if condition='property("HOSTNAME").contains("torino")'>
<then>
<appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="CON" />
</root>
</then>
</if>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${randomOutputDir}/conditional.log</file>
<encoder>
<pattern>%d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="FILE" />
</root>
</configuration>
在java
<configuration>
<insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" />
<contextName>${appName}</contextName>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d ${CONTEXT_NAME} %level %msg %logger{50}%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
在最后一个例子中,”java:comp/env/appName” env-entry作为appName属性插入。注意,Example: File include ```java
目标文件必须将其元素嵌套在<included>元素中。例如,可以将ConsoleAppender声明为:<br />_**Example: File include**_```java<included><appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>"%d - %m%n"</pattern></encoder></appender></included>
同样,请注意必须包含的
要包含的内容可以作为文件、资源或URL引用。
As a file:
要包含文件,请使用file属性。可以使用相对路径,但请注意,当前目录是由应用程序定义的,不一定与配置文件的路径相关。
As a resource:
要包含资源,即在类路径上找到的文件,请使用resource属性。
As a URL:
要包含URL的内容,请使用URL属性。
如果找不到要包含的文件,logback将打印状态消息来报错。如果所包含的文件是可选的,则可以通过在
Adding a context listener
LoggerContextListener接口的实例监听与记录器上下文生命周期相关的事件。
JMXConfigurator是LoggerContextListener接口的一个实现。这在以后的一章中有描述。
LevelChangePropagator
从0.9.25版本开始,经典日志带有LevelChangePropagator,这是LoggerContextListener的一个实现,它可以将对任何经典日志记录器级别的更改传播到java.util.logging框架上。这种传播消除了禁用日志语句对性能的影响。LogRecord实例只会被发送到logback(通过SLF4J)用于启用的日志语句。这使得实际应用程序使用七月到slf4j桥是合理的。
contextListener元素可用于安装如下所示的LevelChangePropagator。
<configuration debug="true"><contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>....</configuration>
设置LevelChangePropagator的resetJUL属性将重置所有j.u.l loggers的所有先前级别配置。但是,以前安装的处理程序将保持不变。
<configuration debug="true"><contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"><resetJUL>true</resetJUL></contextListener>....</configuration>
