打日志那些事儿
1 SLF4J
1.1 门面(外观)模式
门面模式(Facade Pattern),隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
1.2 SLF4J介绍
SLF4J(Simple Logging Facade for Java)是Java的一个日志门面,提供了一组通用的接口,但未提供实现。常用的实现有Logback、log4j等。使用门面模式的好处是,我们不需要关注日志底层是怎么实现的,只需要会调用接口就行。另外,在系统更换底层日志实现类时,无须修改项目代码。例如,从Logback切换到Log4j。
2 Logback
Logback is intended as a successor to the popular log4j project, picking up where log4j 1.x leaves off.(Logback 旨在作为流行的 log4j 项目的继承者,在 log4j 1.x 停止的地方接手。)这是Logback官网的第一句话,目的很明显。作为SpringBoot自带的日志框架,与Log4j相比肯定有其过人之处,比如性能更好、内存占用更小、流畅地实现了slf4j等,如果没有更好的选择,那么用Logback就行了。
2.1 引入
如果是SpringBoot项目,无需单独引入,spring-boot-starter
下的spring-boot-starter-logging
已经引入Logback相关jar包。
2.2 配置
2.2.1 Logback启动步骤
- Logback 尝试在 classpath中找到一个名为
logback-test.xml
的文件。 - 如果没有找到这样的文件,它会检查 类路径中的文件
logback.xml
。 - 如果没有找到这样的文件,则 使用服务提供者加载工具(在 JDK 1.6 中引入) 通过查找文件 META-INF\services\ch.qos.logback.classic.spi.Configurator来解析接口 的实现类路径。其内容应指定所需实现的完全限定类名。 com.qos.logback.classic.spi.ConfiguratorConfigurator。
- 如果以上都没有成功,logback 会自动配置自己,BasicConfigurator 这将导致日志输出被定向到控制台。
2.2.2 使用自动配置
如果只引入而不加任何配置,则默认规则是:按%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
模板输出debug
级别的日志到控制台。
18:08:10.016 [main] DEBUG com.loyoi.LogTest - 输出一些debug日志
与自动配置对应配置规则如下:
<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>
2.2.3 使用.xml配置文件
如果使用的是maven,将logback.xml
和logback-test.xml
分别放在src/main/resources和src/test/resources目录下,Logback将会加载对应的配置文件用于生产和测试。也可以使用系统属性-Dlogback.configurationFile=/path/to/config.xml
来指定配置文件位置。
├─src
│ ├─main
│ │ ├─java
│ │ ├─resources
│ │ │ └─logback.xml
│ └─test
│ │ ├─java
│ │ ├─resources
│ │ │ └─logback-test.xml
配置文件的基本结构可以描述为<configuration>
元素,包含零个或多个<appender>
元素,后跟零个或多个<logger>
元素,后跟最多一个<root>
元素,如下图所示:
其中,<logger>
负责定义要记录什么内容,日志的级别。<appender>
定义日志输出到哪里,如控制台、文件、关系型数据库、非关系型数据库、其他日志系统等。
2.2.3.1 配置logger
<logger>
配置参数
参数 | 描述 | 可选性 | 可选值 |
---|---|---|---|
name | 记录器名称,指定记录日志的某个包或者类的全类名来控制其日志输出 | 必选 | 例子:org.xxx、org.xxx.AClass,下面所说的层级可以依据此来判断 |
level | 日志级别 | 可选 | 不区分大小写的TRACE、DEBUG、INFO、WARN、ERROR、ALL 或 OFF 之一。如果指定为不区分大小写的INHERITED或者NULL,那么记录器的级别会从层次结构中的更高层继承,默认值就是上层的级别。根据“精确原则”来取类或者包下的日志级别。 |
additivity | 可加性 | 可选 | true或false,默认是true,控制当前层级的日志是否传递到上层级别进行输出 |
<root> 只支持level属性,默认DEBUG级别,不支持INHERITED或者NULL,作为最顶层的记录器。 |
<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>
<!-- 未指定level,使用父级root的DEBUG -->
<logger name="chapters" additivity="false"/>
<!-- additivity设置为false,避免输出多次 -->
<logger name="chapters.configuration" level="INFO" additivity="false"/>
<!-- 虽然父层的包指定了INFO级别,但是Foo类指定的更精确,因此Foo类会使用DEBUG级别 -->
<logger name="chapters.configuration.Foo" level="DEBUG" additivity="false"/>
<root level="DEBUG">
<!-- 可包含一个或者多个append-ref,指定输出 -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
2.2.3.2 配置appender
使用<appender>
标签来配置附加器,它有两个必要的参数:name为附加器的名称,被logger引用时使用;class属性指定要实例化的 appender 类的全类名,该类负责输出相关的工作。<appender>
元素可以包含零个或一个<layout>
元素、零个或多个 <encoder>
元素和零个或多个 <filter>
元素。除了这三个公共元素之外,<appender>
元素还可以包含任意数量的与 appender 类的 JavaBean 属性相对应的元素。下图说明了appender的通用结构。
下图为附加器相关类图
下面是一个定义附加器并引用的例子,例子中定义了两个附加器,一个输出到文件,一个输出到控制台,对应不同的处理类。对每一个附加器,<encoder>
是不共享的,必须单独定义。
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>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
附加器class种类:
名称 | 作用 |
---|---|
ConsoleAppender | 输出到控制台 |
FileAppender | 输出到文件 |
RollingFileAppender | 扩展FileAppender,拥有翻转日志文件的能力 |
SocketAppender | 明文传输日志到远程日志服务器 |
SSLSocketAppender | 安全通道传输日志到远程日志服务器 |
ServerSocketAppender | 等待来自远程客户端的传入连接,日志被分发到连接客户端,无客户端时日志将被丢弃 |
SSLServerSocketAppender | ServerSocketAppender的安全版,使用安全通道 |
SMTPAppender | 将日志累积在一个或多个固定大小的缓冲区中,并在用户指定的事件发生后将相应缓冲区的内容发送到电子邮件中。 |
DBAppender | 将日志写入关系型数据库 |
SyslogAppender | 将日志发送到远程 syslog 守护进程 |
SiftingAppender | 根据给定的运行时属性分离(或筛选)日志记录 |
AsyncAppender | 异步记录日志,它仅充当事件调度程序,因此必须引用另一个附加程序才能执行任何有用的操作。 |
详细使用参考官方文档 |
2.2.3.3定义变量
变量使用<property>
标签定义,除了在logback.xml
中定义以外,变量可以来自系统变量,如java -DUSER_HOME="/home/my" XXX
定义的变量,可以来自文件,如var.properties,引用变量使用${}
。变量首先在本地范围中查找属性,然后在上下文范围中查找,然后在系统属性范围中查找,最后在 OS 环境中查找。
<configuration>
<!-- 自定义变量 -->
<property name="USER_HOME" value="/home/my" />
<!-- 引用外部配置文件 -->
<property file="/home/my/var.properties" />
<!-- 引用类路径下的资源 -->
<property resource="application.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>
2.2.4 状态监听器
注册状态监听器用于排查Logback出现的问题,简单配置如下:
<configuration debug="true">
...
</configuration>
通常会将内容输出到控制台,但Logback-classic 附带一个名为 ViewStatusMessagesServlet 的 servlet。将其注册到web程序中,访问注册路径可在网页端看到状态信息。下面是一个示例输出:
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 set
17: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 stack
17:44:58,812 |-INFO in ch.qos.logback.classic.joran.action.LevelAction - root level set to DEBUG
17:44:58,812 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[root]
2.2.5 自动更新配置文件
开启此项,修改配置文件后会周期性地检查配置文件是否有更新,如果不指定scanPeriod,默认1分钟检查一次,如果不指定单位,默认为毫秒。另外,如果修改的配置文件有错误,会自动回退到上个没错的版本。
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
2.2.6 释放logback-classic使用的资源
在程序退出时,可以配置释放logback-classic使用的资源,简单配置如下:
<configuration debug="true">
<!-- in the absence of the class attribute, assume
ch.qos.logback.core.hook.DefaultShutdownHook -->
<shutdownHook/>
....
</configuration>
2.3 使用
在代码中可以看到如下两种使用方式,静态与非静态,官方推荐静态使用方式。那么这两种有什么区别呢?下面是一个验证二者区别的例子:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogControllerTest {
static final Logger loggerStatic = LoggerFactory.getLogger(LogControllerTest.class);
final Logger loggerEntity = LoggerFactory.getLogger(LogControllerTest.class);
public static void main(String[] args) {
Logger loggerEntity1 = new LogControllerTest().loggerEntity;
Logger loggerEntity2 = new LogControllerTest().loggerEntity;
System.out.println("静态与实例: " + (loggerStatic == loggerEntity1));
System.out.println("实例与实例: " + (loggerEntity1 == loggerEntity2));
}
}
输出:
静态与实例: true
实例与实例: true
也就是说,静态和非静态获取到的是同一个对象,但是,静态只需要初始化一次。
2 Log4j2
由于Log4j 1.x版本已经过时,所以推出了Log4j2的版本,该版本并不是对1.x版的升级,相当于是重写。学会Logback的使用,对于Log4j2来说,使用上基本类似,具体细节可在使用时参考官方文档。Log4j2和Logback都出自Ceki Gülcü之手,在Logback官网上,可以很明显地看到 _log4j.properties to logback.xml Translator_和 Canonical form for logback.xml files (requires logback 1.3) 两个导航栏,为Log4j迁移到Logback提供了方便的转换。
3 ELK
“ELK”是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch、Logstash 和 Kibana。在分布式云上,可以使用ELK来采集容器的日志,方便管理和查看日志。
- Elasticsearch 是一个搜索和分析引擎。
- Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。
- Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。