Logback配置使用

Log Java日志:(slf4j、log4j、logback、common-logging )

  • slf4j 是规范/接口
  • 日志实现:log4j、logback、common-logging

简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。

Logback 主要由三个模块组成:

  • logback-core
  • logback-classic
  • logback-access

logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。
logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J。
logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与 HTTP 访问相关的功能。


根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

  • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
  • Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
  • Log4j2:log4j2-spring.xml, log4j2.xml
  • JDK (Java Util Logging):logging.properties

Logback 与 Log4J

实际上,这两个日志框架都出自同一个开发者之手,Logback 相对于 Log4J 有更多的优点:

  • 同样的代码路径,Logback 执行更快
  • 更充分的测试
  • 原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)
  • 内容更丰富的文档
  • 支持 XML 或者 Groovy 方式配置
  • 配置文件自动热加载
  • 从 IO 错误中优雅恢复
  • 自动删除日志归档
  • 自动压缩日志成为归档文件
  • 支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件
  • 支持配置文件中加入条件判断来适应不同的环境
  • 更强大的过滤器
  • 支持 SiftingAppender(可筛选 Appender)
  • 异常栈信息带有包信息
添加依赖
<!-- slf4j + logback: -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.12</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- 实现lsf4j接口并整合 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.1</version>
</dependency>
添加配置

Automatic configuration with logback-test.xml or logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

这是和默认配置等效的一条配置。

使用logback
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.");
  }
}
16:06:09.031 [main] INFO  chapters.configuration.MyApp1 - Entering application.

注意到这里,代码里并没有引用任何一个跟 Logback 相关的类,而是引用了 SLF4J 相关的类,这就是使用 SLF4J 的好处,在需要将日志框架切换为其它日志框架时,无需改动已有的代码。

LoggerFactorygetLogger()方法接收一个参数,以这个参数决定logger的名字,这里传入了 MyApp1这个类的 Class 实例,那么 logger 的名字便是 MyApp1 这个类的全限定类名:chapters.configuration.MyApp1

下面三种方法是等效的:

1. Logger logger = LoggerFactory.getLogger(MyApp1.class)
2. Logger logger = LoggerFactory.getLogger(MyApp1.class.getName())
3. Logger logger = LoggerFactory.getLogger("chapters.configuration.MyApp1")

让 Logback 打印出一些它自身的内部消息

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);

日志打印级别

TRACE < DEBUG < INFO < WARN < ERROR
如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。

Logger,Appenders 与 Layouts

在后端logger系统中,有三个最基础的概念需要先熟悉:

  • Logger 日志记录器 - 日志记录器就是一个普通的Java类而已。
  • Appender 输出源 - 输出源是日志最终输出的地方,比如控制台或者文件
  • Layout(Encoder) 布局 - 布局决定了日志的打印格式,比如使用 %r [%t] %-5p %c %x - %m%n可以打印出 467 [main] INFO org.apache.log4j.examples.Sort - Exiting main method.这样的日志。

Logger 类位于 logback-classic 模块中, 而 Appender 和 Layout 位于 logback-core 中,

这意味着, Appender 和 Layout 并不关心 Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,日志信息才能被正常打印出来。

日志记录器 - logger

在logback中日志记录器是继承的,继承的规则是 com.hello.foo 会继承 com.hello 的日志配置,父子关系通过.来分割,所以 comcom.hello的父节点。在logback中默认会有一个root-logger(根 - 日志记录器)的存在,所有的其他日志记录器都会默认继承它的配置。
在配置文件中看到的:

<root level="debug">
  <appender-ref ref="STDOUT" />
</root>

就是“根”。所以当我们调用Logger logger = LoggerFactory.getLogger(App.class);的时候,默认是从root-logger那里继承了日志输出配置,而root-logger默认的log打印级别是debug,所以用logger.info打印不出日志。

那么如何配置一个普通的日志记录器呢?这是非常简单的:

<logger name="com.hello" level="INFO"/>

这样便配置了一个名为com.hello 的日志记录器(一般会用包名作为日志记录器的名字),name是必须配置的属性。
然后可以通过 Logger logger = LoggerFactory.getLogger("com.hello")来得到这个日志记录器。

为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了一个 **分层 **概念。每个 logger 都有一个 name,这个 name 的格式与 Java 语言中的包名格式相同。

日志输出源 - Appenders

输出源配置把日志打印到控制台,输出源的 nameclass 属性是必须配置的选项。

    <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>

配置把日志打印到文件:

<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>

有了输出源之后,就可以给logger配置输出源,一个logger可以配置多个输出源:

<root level="debug">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
</root>

这里给 root-logger配置了两个输出源,此时如果我们调用logger.debug方法会发现控制台和根目录下的myApp.log文件都打印了相同的日志。


在logback中,level的配置会被继承,但是appender的配置会被子logger保留。这么说有点抽象,看下面的例子:

<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="com.hello">
    <appender-ref ref="STDOUT" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

这个配置会导致控制台打印两次:

11:38:06.068 [main] INFO  com.hello.App - Hello world.Info
11:38:06.068 [main] INFO  com.hello.App - Hello world.Info

当调用LoggerFactory.getLogger("com.hello.App") 的时候,它继承了来自 <logger name="com.hello">root-logger 的输出源,而他们的输出源都是控制台,所以导致在控制台输出了两次。

解决办法是,要么在有继承关系的logger中配置不同的输出源(从业务上避免),要么在子logger中覆盖父节点中的配置。可以通过additivity="false" 配置实现:

<logger name="com.hello" additivity="false">
    <appender-ref ref="STDOUT" />
</logger>

additivity属性告诉logback不要继承来自父类的设置。

布局 - Layout 和 编码 - Encoder

Layout主要用来把log事件转换成String。一般布局都是配置在 Appender 里面的:

<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n" />
    </layout>
</appender>

注意,上面的示例使用的Appender是org.apache.log4j.ConsoleAppender,这是log4j的配置而不是这里讲的logback,这是因为在过去日志系统确实都是使用Layout来做日志转换的,但是由于一些 固有的问题 ,logback在Layout上面又封装了一层 - Encoder,表现在配置上就是这样的(这才是logback的配置):

<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>

但是Encoder的出现并不影响我们的配置,只是在形式上多了一个<encoder/>标签。一般使用最多的是 PatternLayoutPatternLayouEncoder,他们的特点是提供了很多约定的标记,这些标记都以%开头,比如logger名称、日志等级日期、线程名等。

  • %d{HH: mm:ss.SSS}——日志输出时间
  • %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
  • %-5level——日志级别,并且使用5个字符靠左对齐
  • %logger{36}——日志输出者的名字
  • %msg——日志消息
  • %n——平台的换行符

事实上我们在使用logback的时候很少见到直接使用layout,这是因为layout只能把一条log事件转化成String,layout不能控制log什么时候被写入文件,也不能做到批量处理。Encoder是被发明出来解决这些问题的。
目前为止Encoders只有一个实现 - PatternLayoutEncoder,它内部包含了一个PatternLayout,所以可以像使用PatternLayout一样使用Encoder。

常见配置

根节点<configuration>包含的属性
  • scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  • scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  • debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

<configuration>下面一共有2个属性,3个子节点,分别是:

属性一:设置上下文名称<contextName>

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。

<contextName>logback</contextName>
属性二:设置变量<property>

用来定义变量值的标签,<property>有两个属性,namevalue;其中name的值是变量的名称,value的值时变量定义的值。通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使${}来使用变量。

<property name="log.path" value="E:\\logback.log" />

变量有三个作用域:

  • local
  • context
  • system

local 作用域在配置文件内有效,context 作用域的有效范围延伸至 logger context,system 作用域的范围最广,整个 JVM 内都有效。

logback 在替换变量时,首先搜索 local 变量,然后搜索 context,然后搜索 system。

<property scope="context" name="nodeId" value="firstNode" />

也可以通过外部文件来定义:

<property file="src/main/java/chapters/configuration/variables1.properties" />
子节点一:<appender>

appender用来格式化日志输出节点,有俩个属性nameclass,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。

1、控制台输出ConsoleAppender:

<!--输出到控制台-->
 <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
     <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <level>ERROR</level>
     </filter>
     <encoder>
         <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
     </encoder>
 </appender>

ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~

2、输出到文件RollingFileAppender

另一种常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。

RollingFileAppender用于切分文件日志:

<!--输出到文件-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
        <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>
  • <fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>定义了日志的切分方式——把每一天的日志归档到一个文件中。
  • <maxHistory>30</maxHistory>表示只保留最近30天的日志,以防止日志填满整个磁盘空间。同理,可以使用%d{yyyy-MM-dd_HH-mm}来定义精确到分的日志切分方式。
  • <totalSizeCap>1GB</totalSizeCap>用来指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志。
子节点二:<root>

root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。

<root level="debug">
  <appender-ref ref="console" />
  <appender-ref ref="file" />
</root>
子节点三: <loger>

<loger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>

<loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。

if表达式(条件化处理配置文件)

logback 允许在配置文件中定义条件语句,以决定配置的不同行为,

   <!-- 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>
常用条件表达式函数
1. property('key')
2. isDefined('key')
3. isNull("key")
文件包含

可以使用 ≶include> 标签在一个配置文件中包含另外一个配置文件。

<configuration>
  <include file="src/main/java/chapters/configuration/includedConfig.xml"/>

  <root level="DEBUG">
    <appender-ref ref="includedConsole" />
  </root>

</configuration>

被包含的文件必须有以下格式:

<included>
  <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>"%d - %m%n"</pattern>
    </encoder>
  </appender>
</included>

支持从多种源头包含

  1. 从文件中包含
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
  1. 从 classpath 中包含
<include resource="includedConfig.xml"/>
  1. 从 URL 中包含
<include url="http://some.host.com/includedConfig.xml"/>

如果包含不成功,那么 logback 会打印出一条警告信息,如果不希望 logback 抱错,只需这样做:

<include optional="true" ..../>

添加一个 Context Listener

LoggerContextListener接口的实例能监听 logger context 上发生的事件,比如说日志级别的变化,添加的方式如下所示:

<configuration debug="true">
  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
  .... 
</configuration>

多环境日志输出

据不同环境(prod:生产环境,test:测试环境,dev:开发环境)来定义不同的日志输出。

文件名称不是logback.xml,想使用spring扩展profile支持,要以logback-spring.xml命名

<!-- 测试环境+开发环境. 多个使用逗号隔开. -->
<springProfile name="test,dev">
    <logger name="com.dudu.controller" level="info" />
</springProfile>
<!-- 生产环境. -->
<springProfile name="prod">
    <logger name="com.dudu.controller" level="ERROR" />
</springProfile>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,817评论 1 13
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,907评论 0 6
  • 一、Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layo...
    默默守护阅读 1,863评论 2 8
  • from:https://www.cnblogs.com/ITtangtang/p/3926665.html一、L...
    enshunyan阅读 3,172评论 0 0
  • 如果可以 我愿是一滴水 在河流中奔腾 在沙漠里干枯 如果可以 我愿是一朵云 在天空中畅游 在暮霭中散尽 如果可以 ...
    张小莫白阅读 119评论 0 6