一. 异常

 

二. 断言

 

█ 日志

1. 概述

1.1. 常用日志门面

Commons Logging和Slf4j是日志门面(门面模式是软件工程中常用的一种软件设计模式,也被称为正面模式、外观模式。它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)。Log4j和Logback则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用者只需要关注接口而无需关注具体的实现,做到解耦。

1.2. 常用日志实现

Jul (Java Util Logging),自Java1.4以来的官方日志实现。

Log4j Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。

Log4j 2 Apache Log4j 2是apache开发的一款Log4j的升级产品。

Logback 一套日志组件的实现(Slf4j阵营)。

1.3. 常用组合

比较常用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用。

slf4j + logback

commons logging + log4j

2. Slf4j

https://www.slf4j.org/images/concrete-bindings.png

 

可能项目中用到的某些组件, 依赖于其他的日志框架, 为了统一管理日志, slf4j 提供了不同的桥接模式, 允许将不同日志框架的日志转发到 slf4j, 由 slf4j 的实现类来统一完成日志记录

 

https://www.slf4j.org/images/legacy.png

jcl -> slf4j

1562079453777

jul -> slf4j

1562079488398

log4j -> slf4j

1562079539819

https://cnblogpic.oss-cn-qingdao.aliyuncs.com/blogpic/java_log/slf4j-bind.png

 

 

3. commons-logging

2. jul 日志

1.1. 记录器

记录日志信息的工具类, 可以通过 Logger 类的静态方法获取需要的日志记录器. 日志记录器相当于是日志的中转机构, 会将日志信息发送到对应的处理器进行处理.

与包名类似, 日志记录器名具有层级结构, 且在未显式设置的情况下, 子记录器会继承父记录器中的一些设置, 如子记录器会继承父记录器的日志级别.

1.2. 处理器

每个日志记录器, 都可以配置多个不同的日志处理器, 可以将日志输出到指定位置, 如 ConsoleHandler 能将日志输出到控制台, FileHandler 能将日志保存到文件.

默认情况下, 日志记录器不仅将日志发送给自己的所有处理器, 还会发送到父级记录器的处理器, 而最终的记录器(命名为""), 拥有一个 ConsoleHandler, 它将日志信息输出到 System.err 流中.

我们可以为日志记录器添加一个或多个自己的处理器, 以便我们对日志进行更多个性化的处理.

添加新的处理器后, 日志记录器就能处理 FINEST 级别的日志信息了. 但是, 由于日志记录器默认会将日志继续发送给父级记录器的处理器, 而顶级记录器的处理器, 会将所有等于或高于 INFO 级别的记录发送到控制台, 这就导致日志信息会同时被两个处理器处理, 即相同的日志在控制台中被记录了两次. 为了避免重复记录, 我们可以为当前的日志记录器禁用父级处理器

如果觉得 jdk 中提供的处理器不能满足需要, 可以通过扩展 Handler 类或 StreamHandler 类自定义处理器.

1.3. 日志级别

日志有七个级别, 默认只开启前面三个级别. 如果需要记录更低级别的日志, 需要同时修改记录器和处理器的级别. 通过调用记录器不同方法, 记录不同级别的日志.

修改记录器级别

修改处理器级别

具体示例

1.4. 流程跟踪方法

logger.logp(…)

logger.entering(…)

logger.exiting(…)

logger.throwing(…)

1.5. 过滤器

记录器和处理器都可以设置过滤器, 默认根据日志级别进行过滤

可以自定义需要的过滤器, 实现 Filter 接口并重写 isLogable 方法即可

1.6. 格式化器

2. logback

2.1. 组件结构

2.1.1. Logger

位于 classic 包, 根据名字组成继承树, 顶级的 logger 名字为 root

所有的 logger 够能根据名字重复获取

日志级别有5个, trace, debug, info, warn, error, 定义在 Level 类中, 该类不可被继承

如果需要更灵活的方式, 可以使用 Marker

可以为 logger 指定日志级别, 未指定级别的 logger, 其日志级别继承自最近的指定级别的上层 logger

root logger 默认级别为 debug

如果一条日志的级别, 高于或等于 logger 的日志级别, 那么这条日志是有效日志, 会被处理, 否则会被忽略

 

2.1.2. Appender

位于 core 包

logback 允许将同一条日志发送到多个目的地, 这些目的地被称为 Appender, 可以是控制台, 文件, 远程 socket接口, 数据库等等

每个 logger 可以添加多个 appender, 通过 addAppender 方法实现

每条有效的日志请求, 都会被转发到该 logger 以及所有父级logger 的 appender .

允许禁用 appender 的继承

 

2.1.3 Layout

位于 core 包

2.2. 配置

logback 提供的配置方式有以下几种:

logback 在启动时,根据以下步骤寻找配置文件:

  1. 在 classpath 中寻找 logback-test.xml文件
  2. 如果找不到 logback-test.xml,则在 classpath 中寻找 logback.groovy 文件
  3. 如果找不到 logback.groovy,则在 classpath 中寻找 logback.xml文件
  4. 如果上述的文件都找不到,则 logback 会使用 JDK 的 SPI 机制查找 META-INF/services/ch.qos.logback.classic.spi.Configurator 中的 logback 配置实现类,这个实现类必须实现 Configuration 接口,使用它的实现来进行配置, 即编程式配置?
  5. 如果上述操作都不成功,logback 就会使用它自带的 BasicConfigurator 来配置,并将日志输出到 console

如果使用配置文件的形式, xml 或者 groovy, 默认情况下需要将文件命名为 logback.xml, 并放置在 classpath 根目录下, 不能放在下级目录.

 

最简单的办法就是让 logback 自动配置, 会进行最基础的默认配置, 提供一个控制台处理器, 且日志级别为 DEBUG

2.2.1. 编程式配置

2.2.2. xml 配置

logback 会自动从 classpath 根目录下查找 logback.xml 配置文件对logback 进行初始化

如果在 xml 解析过程中出现问题, logback 会自动输出警告和错误信息到控制台.

为了避免信息重复输出, 如果 logback 配置了状态监听器, status listener, 警告信息的自动输出将被关闭, 由状态监听器处理.

在没有错误信息的情况下, 如需要获取 logback 状态信息, 可以用 StatusPrinter.print(loggerContext)方法输出

除了通过 StatusPrinter 在程序代码中输出日志信息, 还可以在配置文件中要求输出状态信息. 只需要在 configuration 元素中将 debug 属性设为 true

注意这里的 debug 属性, 与日志的级别无关.

如果xml 配置文件有问题, logback 无法解析, 则 debug 属性也无法生效. 这种情况下, 配置文件中的问题可能不会输出到日志中, 这种情况下想要定位问题是比较困难的. 为此, 可以在启动参数中通过 logback.statusListenerClass 属性指定状态监听器, 强制要求输出状态信息.

在 configuration 元素上设置 debug="true", 与手动安装状态监听器 OnConsoleStatusListener 是完全等价的

如果需要指定配置文件的路径, 需要在jvm的启动参数中通过logback.configurationFile来指定. 所指定的文件名必须以 .xml 或者 .groovy 作为文件后缀,否则 logback 会忽略这些文件。

如果有需要, logback 可以持续扫描配置文件, 并在修改后重新初始化, 为此, 可以在 configuration 元素中设置 scan 属性

logback 默认每分钟检查一次, 如果需要自定义扫描时间, 通过 scanPeriod 属性设置, 单位可以为 milliseconds, seconds, minutes 或 hours, 若不指定单位, 默认单位为milliseconds

当设置了 scan 属性, 等价于安装了 ReconfigureOnChangeTask, 它会在一个独立的线程中运行, 扫描所有 logback 相关文件的变化.

由于xml 文件的修改容易出错, 如果扫描发现修改后的配置文件引入了错误, 则 logback 会忽略此次修改, 按最近一次准确无误的配置文件对 logback 进行配置.

打包信息默认被禁用, 如果开启, logback 可以在输出异常的栈轨迹时同时输出该行代码所在的包和行数, 这有助于定位异常以及发现版本冲突等问题, 但是这需要耗费大量的资源, 默认关闭, 可以通过 packagingData 属性开启

也可以在程序中设置

通过 LoggerContext 可以获取到 StatusManager 对象, 该对象存储了 logback 的内部状态信息. 为了保证内存占用在一个合理范围内, 默认只保存头尾各150条信息

可以通过 ViewStatusMessagesServlet 访问状态信息, 需要添加 servlet 到 web 应用中

还可以添加一个StatusListenerStatusManager , 以便及时响应状态变化.

logback 附带一个状态 StatusListener 的实现 OnConsoleStatusListener, 它会将所有状态信息输出到控制台

可以通过 java 编码方式添加监听器

状态监听器只能监听它装载后发生的状态信息, 因此通常将其首先装配

可以在配置文件中配置状态监听器

可以在启动的系统参数中配置状态监听器

配置状态监听器后, 就会禁用 logback 默认的自动输出错误信息功能, 因此, 可以通过配置 NopStatusListener 来关闭 logback 的所有状态信息

 

 

 

 

 

xml结构

根节点是 configuration,可包含0个或多个 appender,0个或多个 logger,最多一个 root。

大小写都行, 但开启标签和结束标签要一致, 建议使用小驼峰命名法

1. logger

1.1. 属性

1.2. appender-ref

允许任意多个, 将 appender 关联到 logger

2. root

用来配置 rootlogger, name 固定为 ROOT, 只支持 level 属性, 且level 不能使用 INHERITED 或 NULL

<root> 标签和 <logger> 标签的配置类似,只不过 <root> 标签只允许一个属性,那就是 level 属性,并且它的取值范围只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF <root> 标签下允许有0个或者多个 <appender-ref>

3. appender

https://logback.qos.ch/manual/images/chapters/configuration/appenderSyntax.png

3.1. 属性

3.2. layout

0个或1个,

必填属性 class, 指定具体类型, 若不填, 则默认为PatternLayout

每个 appender 有其自己的 layout, 通常是不可被多个 appender 共享的

3.3. encoder

0个或多个

必填属性 class, 指定具体类型, 若不填, 则默认为 PatternLayoutEncoder

每个 appender 有其自己的 encoder, 通常是不可被多个 appender 共享的

3.4. filter

0个或多个

 

 

 

<appender> 标签有两个必须填的属性,分别是 name 和 class,class 用来指定具体的实现类。<appender> 标签下可以包含至多一个 <layout>,0个或多个 <encoder>,0个或多个 <filter>,除了这些标签外,<appender> 下可以包含一些类似于 JavaBean 的配置标签。

<layout> 包含了一个必须填写的属性 class,用来指定具体的实现类,不过,如果该实现类的类型是 PatternLayout 时,那么可以不用填写。<layout> 也和 <appender> 一样,可以包含类似于 JavaBean 的配置标签。 <encoder> 标签包含一个必须填写的属性 class,用来指定具体的实现类,如果该类的类型是 PatternLayoutEncoder ,那么 class 属性可以不填。 如果想要往一个 logger 上绑定 appender,则使用以下方式:

3.5. 累积

默认情况下, appender 是可累积的, 如果一个 logger 和他的祖先都关联的同样的 appender , 那么日志信息将会被重复记录

如果默认的appender 累积策略不符合需求, 可以将 logger 的 additivity 设为 false 来关闭向上累积

4. logger context

所有的logger 都关联到一个 logger Context 中, 默认名为 default

可以通过 contextName 元素进行修改, 以便在都多个应用输出到同一目的地时区分来源

在输出时, 可以在格式字符串中带上 contextName

5. 变量

logback 允许定义变量, 在后续解析配置文件时进行替换, 且变量具有作用域的限制

调用时, 使用 ${xxx} 获取变量的值

变量可以在配置文件本身内定义, 也可以在外部文件定义, 甚至通过特定的表达式进行计算

定义变量时, 可以通过 <property><variable> 标签定义, 两者等价, 可互换

5.1. 配置文件内部定义

5.2. 系统参数定义

5.3. 外部文件定义

如果变量很多, 可以通过一个外部的 properties 文件进行定义

在 logback 配置文件中, 通过 property 元素的 file 属性引入 properties 文件

也可以通过 property 元素的 resource 属性从 classpath 中引入文件

5.4. 作用域

作用域有 local, context, system 三种, 默认是 local

虽然可以从系统的环境变量中获取变量, 但不能将变量添加到系统环境变量中

查找顺序 local > context > system > environment

scope属性, 可以用在 property, define, insertFromJNDI 三个元素中, 有效值为上述三个, 默认为 local

5.5. 默认值

如果某个变量可能未定义, 则调用者需要为此变量设置一个默认值, 调用时, 通过 :- 表示默认值

变量嵌套

logback 支持变量嵌套, 各种地方各种嵌套

值嵌套

变量名嵌套

${${userid}.password}</tag>

默认值嵌套, 设置默认值为另外一个变量

${id:-${userid}}

常量

logback 预先定义了一些常用的变量,

HOSTNAME, 自动获取当前主机名

CONTEXT_NAME, 与 contextName 元素定义的值一致

时间戳

可以通过 timestamp 元素定义时间戳, 当成变量使用

动态变量

可以通过 define 元素, 在程序运行时动态定义变量, 该元素有两个必要属性:

name 指定变量名, class 指定具体的 PropertyDefiner 的实现类, 其中getPropertyValue() 方法的返回值就是变量的值.

define 元素中也可以使用 scope 属性指定作用域

logback 提供了三个 PropertyDefiner 的实现类:

条件配置

为了满足不同条件, 如开发/生产环境需要采用不同内容的配置, 可以使用 if 元素根据条件进行配置. 此功能依赖于 janino 包, 需要另外引入

在配置文件中使用条件配置, 如下所示

其中的条件表达式, 是对 context 或 system 属性进行判断, 语法同 java 方法调用

property("k")p("k") 获取变量 k 的值, String 类型. 如果变量未定义, 则返回空字符串.

isDefined("k") 检查 k 是否已定义, isNull("k") 检查 k 是否为null

if 条件块可用在 configuration 元素内的任务地方, 并允许嵌套使用. 但由于 xml 天然的复杂性, 过多使用 if 条件块会导致配置文件的可读性下降, 慎用.

从 JNDI 获取变量

某些情况下你可能需要从 jndi 获取变量, <insertFromJNDI> 可以从 JNDI 中获取变量, 并通过 as 属性创建一个变量, scope 属性可以指定作用域

文件导入

可以在配置文件内导入另一文件的配置内容. 在主文件中, 使用 include 元素指定要导入的文件, 而被导入的文件, 则要求其根节点为 included

被导入的文件, 可以通过以下三种方式指定

file 指定文件路径, 绝对路径或相对路径均可, 注意相对路径是相对于应用, 而不是相对于该配置文件

resource 指定资源路径

URL 指定网络路径

默认情况下, logback 找不到指定文件后会输出状态信息以报告错误, 若该文件是可选的, 可以通过optional="true" 属性抑制该错误信息.

6. appender

https://logback.qos.ch/manual/images/chapters/appenders/appenderClassDiagram.jpg

logback 将记录日志的任务委托给一些称为 appender 的组件. 这些组件都实现了 Appender 接口.

6.1. ConsoleAppender

它在用户指定的 encoder 的帮助下, 将日志格式化, 然后输出到控制台, 更精确地说, 是输出到 System.outSystem.err. 有以下三个子元素

6.2. FileAppender

将日志记录到文件中, 有以下子元素

有时候可能需要每次运行应用都创建一个全新的日志文件, 如一些短时间就能完成的批处理, 这可以借助 timestamp 变量实现

 

6.3. RollingFileAppender

滚动日志记录, 当日志文件满足某个指定条件后, 会自动切换到新的日志文件, 避免日志文件过于庞大

有两个子组件

RollingFileAppender 使用时需要配合上述两个组件. 如果提供的 RollingPolicy 实现类同时也实现了TriggeringPolicy 接口, 那么只需要显式指定RollingPolicy, 触发策略可以省略.

有以下子元素

6.3.1 rollingPolicy

RollingPolicy 接口规定了日志滚动策略的相关方法, rollover() 的实现负责主要的日志滚动切换操作. getActiveFileName() 获取当前使用的日志文件

1) TimeBasedRollingPolicy

可能是最常用的滚动策略. 定义了基于时间的滚动策略. 它同时实现了 RollingPolicyTriggeringPolicy 接口

第二个例子, 启用了 prudent 模式

2) SizeAndTimeBasedRollingPolicy

如果需要按日期归档日志, 但又要限制单个日志文件的大小, 可以采用此策略, 通过 maxFileSize 属性设置单个文件大小.

TimeBasedRollingPolicy类似, 需要指定一个 fileNamePattern, 但区别在于此策略必须包含流水号占位符 %i, 流水号从0开始, 递增

3) FixedWindowRollingPolicy

固定滚动策略, 在符合条件时, 将日志文件归档. 要求归档文件名必须包含流水号占位符%i

归档时会将所有日志文件重命名, 流水号越大, 表示日志文件越旧, 最小号的日志文件为最新的日志文件. 如果用户指定的总日志文件数量(maxIndex-minIndex)过大, 会造成大量的日志文件需要重命名, 影响性能. 因此, 若用户设置了过大的数值, 会自动将窗口数减少到20

6.3.2 triggeringPolicy

1) SizeBasedTriggeringPolicy

检测当前日志文件的大小, 当达到指定大小时, 触发归档操作

只有一个 maxFileSize 属性, 指定日志文件的大小, 默认 10MB. 支持 B, KB, MB, GB 等单位, 若只写数值不写单位, 则采用 B 为单位.

7. Encoder

Encoder, 编码器, 将日志事件编码成 byte 数组, 并通过 OutputStream 输出. 在旧版本的 logback 中, appender 通过 Layout 将日志事件格式化成 String, 并通过 java.io.Writer 输出. 旧版本 的用户通常需要在 FileAppender 中添加 PatternLayout, 而在现在的新版本 logback, FileAppender 使用encoder代替layout

与 layout 相比, encoder 显得更加灵活

到目前为止, PatternLayoutEncoder 是唯一真正有用的 encoder, 它实际上就是PatternLayout的包装类.

7.1. LayoutWrappingEncoder

由于旧版本中, appender 依赖于 layout 完成格式化工作, 而新版中采用了 encoder. 为了兼容旧应用中的 layout, 提供了这个 layout -> encoder 的包装类. 它会将相关的工作, 委托给已经存在的 layout 来完成.

7.2. PatternLayoutEncoder

由于 PatternLayout 是最通用的, logback 基于它扩展了 LayoutWrappingEncoder 得到了 PatternLayoutEncoder.

8. Layout

layout 是个泛型接口, 允许处理任意类型的具体事例, 将其格式化成需要的格式. Logback-classic 只处理了 ch.qos.logback.classic.spi.ILoggingEvent, 如果需要处理其他类型, 用户可以自行提供实现类.

自定义layout 时, 可以继承 LayoutBase , 这样就只需关注具体格式化的 doLayout 方法. 自定义layout 后需要包装成 encoder 才能给 appender 使用.

8.1. PatternLayout

logback 提供了一个灵活的格式化 layout, 能根据指定的格式字符串, 将日志事件转换成 String

与 c 语言的 printf() 方法类似, 模板字符串由字面文本和格式控制表达式组成, 其中格式控制表达式由 % 开头, 跟着一些格式修饰字符, 以及用{} 括起来的可选参数, 如日期格式字符串 %d{yyyy-MM-dd}

可以用括号对模板字符串进行分组, 因此括号() 具有特殊含义, 如果需要作为字面文本输出, 需要进行转义

格式修饰字符详见官方文档

一般情况下, 我们要求格式修饰字符与字面文本之间有分隔符, 以便正确识别修饰字符. 但如果需要在修饰符后面直接跟字面文本, 可以在格式字符后面加上空的{}

格式修饰

为了对齐多条日志记录中的同一部分, 可以对格式进行修饰, 指定某一部分的宽度和对齐方式

格式修饰符放置在% 和具体的格式字符之间

如果想要只用一个字母表示日志级别, 可以按最大宽度+去尾的方式指定日志级别, 即 %.-1level

括号分组

小括号()可以将模板字符串进行分组, 可以对整组进行统一的修饰, 如指定整组的字符宽度, 或是指定颜色

9. Filter

Filter 接口规定了对日志事件进行筛选的方法

decide() 方法返回 FilterReply 中定义的三个枚举常量, 决定如何处理该条日志

使用 filter 时, 在 appender 元素中添加 filter 元素, 通过 class 属性指定具体的 filter 实现类

LevelFilter

按日志级别是否匹配进行过滤, 根据匹配结果, 返回 onMatchonMismatch 指定的结果

ThresholdFilter

按照指定的日志级别阈值进行过滤, 高于或等于指定级别的日志, 返回 NEUTRAL, 低于指定级别的返回 DENY