用SLF4j/Logback打印日志-1
在 浅谈后端日志系统 中已经写了很多日志方面的零散的非技术的东西。本篇更像一份入门说明,讲解一下SLF4j/Logback。SLF4J是一套抽象的日志API接口,logback它是的底层实现,所以在这里的大部分代码中,看到的API都是 SLF4J 提供的,但是日志系统的配置还是按照 logback的文档 配置的。
在后端logger系统中,有三个最基础的概念需要先熟悉:
- Logger 日志记录器 - 日志记录器就是一个普通的Java类而已
logger.debug("Hello world.Debug");
这里面的logger就是日志记录器,每个日志记录器都可以独立的配置属性。
- Appender 输出源 - 输出源是日志最终输出的地方,比如控制台或者文件
- Layout(Encoder) 布局 - 布局决定了日志的打印格式,比如使用 %r [%t] %-5p %c %x - %m%n可以打印出 467 [main] INFO org.apache.log4j.examples.Sort - Exiting main method.这样的日志。
这在后面会有更详细的介绍,接下来先看一个HelloWorld - 感受一下logback的魅力。
HelloWorld
入门很重要,这里还是采用IntelJ+Gradle的编程环境,按步骤来:
- 创建目录,这是一个非常简单的Java工程,仅有一个App.java 类
➜ logback-test tree .
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── hello
│ └── App.java
- 配置build.gradle文件,这里仅添加了对 logback 的依赖,logback还会依赖 slf4j, Gradle会帮我们解决这些间接依赖的问题。
group 'com.hello'
version '1.0-SNAPSHOT' apply plugin: 'java' repositories {
mavenCentral()
} dependencies {
compile 'ch.qos.logback:logback-core:1.1.+'
compile 'ch.qos.logback:logback-classic:1.1.+'
}
- 实现App.java并右键运行
// App.java:
package com.hello; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) {
logger.debug("Hello world.");
}
}
// 控制台打印的结果
Console:10:35:22.504 [main] DEBUG com.hello.App - Hello world.
这个例子没有太多值得关注的地方,我们仅仅调用了一个方法 logger.debug("Hello world."); 打印了一条日志。但是注意到打印出来的日志格式是这样的:
Console:10:35:22.504 [main] DEBUG com.hello.App - Hello world.
除了我们要打印的信息“Hello world”之外,多了很多信息,比如 Console、时间戳、DEBUG符号,类名等。这些信息是在日志打印的时候logback默认加上去的,可以通过配置文件来修改这些信息。
默认情况下logback会在classpath下寻找名为logback.xml的配置文件(还有另外4种查找配置文件的方式,见文档)如果没有找到任何配置文件,会使用 BasicConfigurator 初始化一个默认配置,它会把结果打印到控制台,这就是上面“HelloWorld”示例的情况。为了让logback可以加载到配置文件,需要在 src/main/resources/ 目录下创建文件 logback.xml(Gradle会自动把 resources 目录下的文件放到classpath目录)。
logback.xml文件的内容为
<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>
这是和默认配置等效的一条配置。
日志记录器 - logger
在上面的 “HelloWorld” 示例中,我们通过 Logger logger = LoggerFactory.getLogger(App.class); 工厂方法获得了一个日志记录器。并通过 logger.debug("Hello world."); 方法打印了一条信息,但是如果我们通过 logger.info 方法是打印不出来的,原因是什么呢?
在logback中日志记录器是继承的,继承的规则是 com.hello.foo 会继承 com.hello 的日志配置,父子关系通过 . 来分割,所以 com 是 com.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") 来得到这个日志记录器。
下面的三种写法是等效的:
Logger logger = LoggerFactory.getLogger(App.class)
Logger logger = LoggerFactory.getLogger(App.class.getName())
Logger logger = LoggerFactory.getLogger("com.hello.App")
日志输出源 - Appenders
在上面的配置文件中其实已经见识到了日志输出源的配置:
<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>
这个输出源配置把日志打印到控制台,格式为 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n,输出源的 name 和 class 属性是必须配置的选项。比如还可以配置把日志打印到文件:
<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文件都打印了相同的日志。在上面的“HelloWorld”工程中,我们这样配置logback.xml文件:
<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> <appender name="file" class="ch.qos.logback.core.FileAppender">
<file>log/demo.file.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender> <logger name="com.hello" level="INFO"></logger> <root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="file" />
</root>
</configuration>
再次操作会在工程根目录的 log/demo.file.log 文件中打印
2016-03-31 11:38:06,068 INFO [main] c.h.App [App.java:11] Hello world.Info
在控制台会打印:
11:38:06.068 [main] INFO com.hello.App - Hello world.Info
在这里创建了两个输出源,一个基于文件的输出源 <appender name="file" class="ch.qos.logback.core.FileAppender"> 并配置了文件路径和日志输出格式。另一个是基于控制台的输出源 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
然后在 root-logger 下面添加了这个输出源,所以这条日志会在文件和控制台各打印一份(注意因为配置的格式不同,打印出来的结果也略有不同)。
在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
<logger name="com.hello" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
additivity 属性告诉logback不要继承来自父类的设置。
布局 - Layout 和 编码 - Encoder
Layouts are logback components responsible for transforming an incoming event into a String.
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/>标签。一般使用最多的是 PatternLayout 或 PatternLayouEncoder ,他们的特点是提供了很多约定的标记,这些标记都以 % 开头,比如logger名称、日志等级日期、线程名等。比如:%level [%thread]: %message%n可以打印出日志等级,线程名字和日志本身。
Layouts, as discussed in detail in the next chapter, are only able to transform an event into a String. Moreover, given that a layout has no
control over when events get written out, layouts cannot aggregate events into batches. Contrast this with encoders which not only have total
control over the format of the bytes written out, but also control when (and if) those bytes get written out.
事实上我们在使用logback的时候很少见到直接使用layout,这是因为layout只能把一条log事件转化成String,layout不能控制log什么时候被写入文件,也不能做到批量处理。Encoder是被发明出来解决这些问题的。目前为止Encoders只有一个实现 - PatternLayoutEncoder,它内部包含了一个PatternLayout,所以可以像使用PatternLayout一样使用
Encoder。
至此已经粗略介绍完Logback的三个最重要的部分,下面会记录一些常用且比较重要的配置属性
常用配置
有一个最重要的配置 - TimeBasedRollingPolicy 策略配置限于限于篇幅只能转移到下一篇了。
1. 变量定义和替换
这种功能在Maven里面也经常用到,用法很简答:
<configuration>
<property name="USER_HOME" value="/home/sebastien" />
<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>
这里定义了一个变量 <property name="USER_HOME" value="/home/sebastien" /> 并通过 ${USER_HOME} 引用。变量定义的意义在于可以对一些全局性的属性/变量做统一的配置。如果有很多变量的时候,可以把变量定义在单独的文件中,通过 <property file="src/main/java/chapters/configuration/variables1.properties" /> 来引用。
// variables1.properties
USER_HOME=/home/sebastien
fileName=myApp.log
destination=${USER_HOME}/${fileName}
2. Scope配置
通过配置scope可以设置变量的作用空间,用法
<property scope="context" name="nodeId" value="firstNode" />
有三种Scope是可以配置的:
1. LOCAL SCOPE
2. CONTEXT SCOPE
3. SYSTEM SCOPE
** 3. if表达式**
在xml中写代码的既视感:
<!-- if-then-else form -->
<if condition="some conditional expression">
<then>
...
</then>
<else>
...
</else>
</if>
** 4. 常用条件表达式函数**
1. property('key')
2. isDefined('key')
3. isNull("key")
** 5. 文件引用 **
// 引用方式
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://some.host.com/includedConfig.xml"/>
// 被引用文件
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
6. 使用时间戳属性
这种属性在哪些很短命的需要经常重启的App中用的比较多,每次重启会产生新的log文件(下面还有更牛逼的)
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<file>log-${bySecond}.txt</file>
7. 常用的转移标记:
- %c{length} /%lo{length} /logger{length} 打印logger名称,并控制长度,比如;%logger{5} mainPackage.sub.sample.Bar m.s.s.Bar
- %C{length} /class{length} 打印出产生这条log的调用方的完整名字,并控制长度。这个方法效率不高,慎用。(目测要调用堆栈才能查到调用方的类名或者方法)
- %contextName / %cn 打印出logger的上下文信息。在Configuration一节中我们曾经看到过这个的使用
- %d{pattern} / %date{pattern} /%d{pattern, timezone} /%date{pattern, timezone} 打印出当前时间,通过pattern来控制日期格式,比如:%date{HH:mm:ss.SSS} 14:06:49.812
- %F / %file 打印出产生这条log的java源文件,这个方法效率不高,慎用。
- 打印出产生这条log的堆栈信息,并控制长度,效率不高%L / %line 打印出产生这条log的源码行号
- %caller{depth}
- %caller{depthStart..depthEnd}
- %caller{depth, evaluator-1, ... evaluator-n}
- %caller{depthStart..depthEnd, evaluator-1, ... evaluator-n}
- %m / %msg / %message 打印loggger方法传入的消息,比如.loggger.info("hello"),指代这里的“hello”
- %M / %method 打印出产生这条log的方法名,效率不高
- %n 换行
- %p / %le /%level 打印出日志等级
- %r / %relative 打印出自动App启动至今流逝的时间(单位:毫秒)
- %t / %thread 打印出产生这条log的线程名字
- %X{key:-defaultVal} / mdc{key:-defaultVal} 打印出MDC中定义的上下文
- 打印出异常的堆栈信息,并控制深度,默认情况下会打印所有的堆栈信息
- %ex{depth}
- %exception{depth}
- %throwable{depth}
- %ex{depth, evaluator-1, ..., evaluator-n}
- %exception{depth, evaluator-1, ..., evaluator-n}
- %throwable{depth, evaluator-1, ..., evaluator-n}
- 和 %ex一样,但是会给出jar包信息%nopex /%nopexception 忽略异常信息
- %xEx{depth}
- %xException{depth}
- %xThrowable{depth}
- %xEx{depth, evaluator-1, ..., evaluator-n}
- %xException{depth, evaluator-1, ..., evaluator-n}
- %xThrowable{depth, evaluator-1, ..., evaluator-n}
- %marker 打印marker信息,格式:parentName [ child1, child2 ]
- %property{key} 引用之前定义的变量(或属性)
- %replace(p){r, t} 举例说明:%replace(%msg){'\s', ''},会把消息中的所有空格移除
Refer:
https://www.jianshu.com/p/d1a565f192b9
用SLF4j/Logback打印日志-1的更多相关文章
- 用SLF4j/Logback打印日志-3
在 用SLF4j/Logback打印日志-1 和 用SLF4j/Logback打印日志-2 中分别介绍了Logback记录日志的基本原理并重点介绍了输出源配置.本篇介绍一些性能和技巧性的东西. 性能 ...
- 用SLF4j/Logback打印日志-2
本篇主要介绍logback的输出源配置,logback默认提供了很多输出源,但是用的最多的是这几种: OutputStreamAppender 日志输出到一个二进制流,可以通过 <encoder ...
- 日志框架之2 slf4j+logback实现日志架构 · 远观钱途
如何从缤纷复杂的日志系统世界筛选出适合自己的日志框架以及slf4j+logback的组合美妙之处?此文可能有帮助 logback介绍 Logback是由log4j创始人设计的另一个开源日志组件,官方网 ...
- slf4j/logback: logging日志的配置
slf4j/logback: logging日志的配置 import依赖: import org.slf4j.Logger;import org.slf4j.LoggerFactory;private ...
- 使用 SLF4J + LogBack 构建日志系统(转)
转载自:http://www.cnblogs.com/mailingfeng/p/3499436.html 上次我们讨论了如何选择一个好的开源日志系统方案,其中的结论是:使用 SLF4J + LogB ...
- Spring Boot(三):logback打印日志
springboot对logback的支持是非常好的,不需要任何配置,只需要在resource下加logback.xml就可以实现功能直接贴代码: <?xml version="1.0 ...
- springboot中logback打印日志(转)
springboot对logback的支持是非常好的,不需要任何配置,只需要在resource下加logback.xml就可以实现功能 直接贴代码: <?xml version="1. ...
- java IDE 中安装 lombok plugin 插件,并使用 @Slf4j 注解打印日志初体验
lombok 插件介绍: IntelliJ IDEA官方插件页面:https://plugins.jetbrains.com/plugin/6317-lombok-plugin 使用lombok之后, ...
- logback打印日志时添加上下文
尝试上述特性, 配置如下: 效果:
随机推荐
- C++ map.insert: pair和make_pair区别
C++ map.insert: pair和make_pair区别 \*********************************\ map<uint32_t, string> tem ...
- org.apache.maven.archiver.MavenArchiver.getManifest错误
eclipse导入新的maven项目时,pom.xml第一行报错: org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.mav ...
- 【*】深入理解redis主从复制原理
1.复制过程 从节点执行 slaveof 命令. 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制. 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点 ...
- 使用Synchronized块同步变量
我们可以通过synchronized块来同步特定的静态或非静态方法.要想实现这种需求必须为这些特定的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传 ...
- 2,EasyNetQ-日志记录
EasyNetQ提供了一个IEasyNetQLogger接口: public interface IEasyNetQLogger { void DebugWrite(string format, pa ...
- [python]接口签名
一个主机中的数据要通过外网发送数据给另外一个主机,为了保证接口安全,需要对接口进行签名,由于重放攻击貌似对这种接口无效,所以没有加入时间戳 直接放代码: #!/usr/bin/env python # ...
- 数据包发包工具bittwist
数据包发包工具bittwist 渗透测试中,通过发送特定格式的包,可以实施网络嗅探和攻击.Kali Linux提供一款发包工具bittwist.该工具可以通过指定的网络接口发送数据.该工具不仅可以 ...
- dsu on tree题表
dsu on tree,又名树上启发式合并.重链剖分,是一类十分实用的trick,它常常可以作为一些正解的替代算法: 1.DFS序+线段树/主席树/线段树合并 2.对DFS序分块的树上莫队 3.长链剖 ...
- Prim算法和Kruskal算法
Prim算法和Kruskal算法都能从连通图找出最小生成树.区别在于Prim算法是以某个顶点出发挨个找,而Kruskal是先排序边,每次选出最短距离的边再找. 一.Prim(普里姆算法)算法: ...
- 20172330 2017-2018-1 《Java程序设计》第六周学习总结
学号 2017-2018-2 <程序设计与数据结构>第六周学习总结 教材学习内容总结 这一章主要是对数组的学习: 数组是一种简单而功能强大的编程语言结构,用于分组和组织数据.在java中, ...