Java的日志框架之Logback

前言

在Java的日志系统里面,有两个概念,一个叫做日志框架,如我们熟悉的Logback, Log4j, Log4j2, JDK自带的java.util.logging等;一个叫做日志门面,如Slf4j(Simple Logging Facade For Java)。

门面可以理解为一个统一的记录日志的接口,但后面具体产生日志写到文件等等的工作是由不同的日志框架来实现,有的需要在slf4j提供的api到日志框架中增加一个适配器,有的像logback直接实现了slf4j,不用另外适配,如下图:

Java的日志框架之Logback

在代码中记录日志非常方便,如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld{
  private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
  public static void main(String[] args) {
    logger.info("hello rachel !");
  }
}

如果项目中使用了lombok的 @Slf4j,使用变成如下:

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HelloWorld{
  public static void main(String[] args) {
    log.info("hello rachel !");
  }
}

@Slf4j会自动生成如下代码:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HelloWorld.class);

logback执行记录日志的过程图:

Java的日志框架之Logback

logback构建在三个主要的类上:Logger,Appender和Layouts,接下来将一一介绍。

Logger

Logger有两个非常重要的属性,一个是命名(name),一个是层级(level)。

命名

Logger的命名是大小写敏感的,且通过 .来构建继承关系, 例如名为 com.foo的logger是名为 com.foo.bar的logger的父级。

root logger是最高层级的logger。

名字相同则为同一个logger实例,使用类的全限定名对logger进行命名是最好的方式。

层级

logger的层级有以下几个等级,从高级别到低级别依次是
ERROR

WARN
INFO
DEBUG
TRACE

可以给每个logger指定一个等级,如果没有指定等级,那么logger会继承最近的父级的等级,一个logger的有效等级是从自身一直回溯到root logger。即,若父级,父级的父级…都没有指定等级,该logger最后会使用root logger的等级作为自己的等级。root logger 默认的层级是DEBUG。

基本规则

logger的等级和我们代码中指定的日志等级共同作用,决定该日志是否会被打印,这点是非常基础和重要的。

即日志的打印级别为 p,Logger 实例的级别为 q,如果 p >= q,则该条日志可以打印出来。

我们代码中可以通过 log.info(), log.debug()等等指定日志等级,但如果该logger的有效层级大于了指定的等级,则日志不会打印出来。

配置

我们可以在logback.xml中配置logger, 例如:


name和level就是我们上述的命名和层级,挂载的Appender,将在下面介绍。

Appender

Appender可以理解为一个日志输出的目的地。一个logger可以有多个appender,即这条日志将会被输出到不同文件(假如目的地是文件)。

叠加性规则

appender有一个属性叫做additivity,默认是true。

即, 由logger L 打印的日志会遍历L和它父级中的所有appender, 都会从这些appender输出。
如果 L 的某个父级P, 设置了additivity = fasle, 那么logger L 打印的日志只会在L到P之间(L

类型

ConsoleAppender

ConsoleAppender是将日志输出到控制台,通过System.out或者System.err来进行输出。

属性名 类型 描述 encoder Encoder 见 OutputStreamAppender 属性 target String System.out 或 System.err。默认为 System.out withJansi boolean withJansi 的默认值为 false。设置 withJansi 为 true 可以激活 Jansi 在 windows 使用 ANSI 彩色代码。在 windows 上如果设置为 true,你应该将 org.fusesource.jansi:jansi:1.9 这个 jar 包放到 classpath 下。基于 Unix 实现的操作系统,像 Linux、Max OS X 都默认支持 ANSI 才彩色代码。

FileAppender

FileAppender是将日志输出到文件,通过 file来指定目标文件。

属性名 类型 描述 append boolean 如果为 true,日志事件会被追加到文件中,否则的话,文件会被截断。默认为 true encoder Encoder 参见 OutputStreamAppender 的属性 file String 要写入文件的名称。如果文件不存在,则新建。在 windows 平台上,用户经常忘记对反斜杠进行转义。例如,c:\temp\test.log 不会被正确解析,因为 ‘\t’ 是一个转义字符,会被解析为一个 tab 字符 (\u0009)。正确的值应该像:c:/temp/test.log 或者 c:\temp\test.log。没有默认值。 prudent boolean 在严格模式下,FileAppender 会将日志安全的写入指定文件。即使在不同的 JVM 或者不同的主机上运行 FileAppender 实例。默认的值为 false。严格模式可以与 RollingFileAppender 结合使用。严格模式也意味着 append 属性被自动设置为 true。严格模式依赖排他文件锁。实验证明,文件锁大概是写入日志事件成本的 3 倍,应该避免使用严格模式。

RollingFileAppender

RollingFileAppender继承自FileAppender, 是输出到日志文件且具有轮转功能。与轮转相关的有两个子属性,一个是 RollingPolicy一个是 TriggeringPolicy。这两个必须同时设置,如果 RollingPolicy也实现了 TriggeringPolicy接口,那只需要设置 RollingPolicy

属性名 类型 描述 file String 参见 FileAppender append boolean 参见 FileAppender encoder Encoder 参见 OutputStreamAppender rollingPolicy RollingPolicy 当轮转发生时,指定 RollingFileAppender 的行为。下面将会详细说明 triggeringPolicy TriggeringPolicy 告诉 RollingFileAppender 什么时候发生轮转行为。下面将会详细说明 prudent boolean /

TimeBasedRollingPolicy

TimeBasedRollingPolicy即基于时间的轮转策略,它实现了RollingPolicy和TriggeringPolicy的接口,且必须配置一个强制属性 fileNamePattern,TimeBasedRollingPolicy配置的属性如下:

属性名 类型 描述 fileNamePattern String 该属性定义了轮转时的属性名。它的值应该由文件名加上一个 %d 的占位符。%d 应该包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略掉这个日期格式,那么就默认为 yyyy-MM-dd。轮转周期是通过 fileNamePattern 推断出来的。

注意:可以选择对 RollingFileAppender(TimeBasedRollingPolicy 的父类)中的 file 属性进行设置,也可以忽略。通过设置 FileAppender 的 file 属性,你可以将当前活动日志的路径与归档日志的路径分隔开来。当前日志永远会是通过 file 指定的文件。它的名字不会随着时间的推移而发生变化。但是,如果你选择忽略 file 属性,当前活动日志在每个周期内将会根据 fileNamePattern 的值变化。稍后的例子将会说明这一点。

%d{} 中的日期格式将会遵循 java.text.SimpleDateFormat 中的约定。斜杆 ‘/’ 或者反斜杠 ” 都会被解析成目录分隔符。

foo.%d.log/foo.%d{yyyy-MM-dd}.log – 每天轮转

foo.%d{yyyy/MM}.log – 每月轮转

foo.%d.gz – 每天轮转归档,0点压缩 maxHistory int 这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。比如,你指定按月轮转,指定 maxHistory = 6,那么 6 个月内的归档文件将会保留在文件夹内,大于 6 个月的将会被删除。注意:当旧的归档文件被移除时,当初用来保存这些日志归档文件的文件夹也会在适当的时候被移除。 totalSizeCap int 这个可选属性用来控制所有归档文件总的大小。例如磁盘100G,可以设置一个小于100G的日志大小限制。当达到这个大小后,旧的归档文件将会被异步的删除。使用这个属性时还需要设置 maxHistory 属性。而且,maxHistory 将会被作为第一条件,该属性作为第二条件。 cleanHistoryOnStart boolean 如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。

归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作。

SizeAndTimeBasedRollingPolicy

这个策略是在时间轮转的策略上多了一个轮转大小限制,除了%d以外还需要%i,这两个占位符都是强制要求的。在当前时间还没有达到轮转周期前,但日志文件达到了maxFileSize指定的大小,会进行归档,索引递增从0开始

属性名 类型 描述 maxFileSize int 日志文件达到size将会进行归档

AsyncAppender

AsyncAppender异步地打印ILoggingEvent,仅为一个事件调度器,必须调用其他的appender来完成操作。

属性名 类型 描述 queueSize int 队列的最大容量,默认为 256 discardingThreshold int 默认,当队列还剩余 20% 的容量时,会丢弃级别为 TRACE, DEBUG 与 INFO 的日志,仅仅只保留 WARN 与 ERROR 级别的日志。想要保留所有的事件,可以设置为 0 includeCallerData boolean 获取调用者的数据相对来说比较昂贵。为了提高性能,默认情况下不会获取调用者的信息。默认情况下,只有像线程名或者 MDC 这种”便宜”的数据会被复制。设置为 true 时,appender 会包含调用者的信息 maxFlushTime int 根据所引用 appender 队列的深度以及延迟, AsyncAppender 可能会耗费长时间去刷新队列。当 LoggerContext 被停止时, AsyncAppender stop 方法会等待工作线程指定的时间来完成。使用 maxFlushTime 来指定最大的刷新时间,单位为毫秒。在指定时间内没有被处理完的事件将会被丢弃。这个属性的值的含义与 Thread.join(long) 相同 neverBlock boolean 默认为 false,在队列满的时候 appender 会阻塞而不是丢弃信息。设置为 true,appender 不会阻塞你的应用而会将消息丢弃

AsyncAppender & FileAppender

系统设计中常常会有cache和buffer的设计,cache是因为设备的速度差,高速设备访问低速设备造成的高速设备的等待,两者直接加入cache; buffer是化零为整,少量多次变成多量少次。

FileAppender属于buffer级方案,AsyncAppender属于cache级方案。

FileAppender内部有缓存buffer, buffer读写都加锁,logger写buffer, buffer写盘。AsyncAppender把日志写盘变为日志写内存,减少日志的RT(Response Time)。

Java的日志框架之Logback

配置:


    mylog.txt

      mylog-%d{yyyy-MM-dd}.%i.txt
      100MB
      60
      20GB

      %msg%n

    0
    512

Encoder & Layout

Encoder

encoder是appender的一个属性,它将日志事件转换为字节数组,同时将字节数据写入到一个OutputStream中。

Layout

layout是将日志事件转化为字符串。

PatternLayout

常见的转换字符

转换字符 效果 %logger{length} 生成logger名称,假设logger的名字为com.hello.rachel.service.SayService 则:

%logger{0} —> SayService

%logger{10} —> c.h.r.s.SayService

%logger{25} —> c.h.rachel.service.SayService %d{pattern}

%date{pattern} 生成日期:

%d —> 2022-07-05 19:00:00,000 %caller{depth} 生成日志调用者的位置信息,依赖JVM实现

%caller{2} —>

0 [main] DEBUG – logging statement

Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)

Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17) %line/%L 生成日志请求所在的行号 %m/%msg/%message 生成日志具体信息 %n 行分隔符 %p/%level 生成日志级别 %t/%thread 生成日志事件的线程名 %X{key:-defaultVal}

/%mdc{key:-defaultVal} 生成日志事件线程的MDC内容 %replace(p){r,t} 在子模式p产生的字符中,将所有出现正则表达式r的地方替换成t

格式修改器

可以使用格式修改器对数据字段进行对齐;第一个可选的为减号(-)为左对齐;然后是最小字段宽度,如果字段包含很好数据会padding(默认填充左边,右对齐)
例如%-30logger(%date %level) [%thread] 将会出现thread之后的日志信息左对齐的样式

着色

可以使用圆括号分组对子模块进行着色,如%blue(…), %cyan(…), %magenta(…)等等;
注,windows上appender需要添加 <withjansi>true</withjansi>打开颜色设置

MDC

在分布式系统中通常需要用不同的线程处理不同的客户端,为了给每个客户端请求打上唯一的标记,用户可以将上下文信息放到MDC(Mapped Diagnostic Context, 诊断上下文)中。MDC中只包含静态方法,让开发人员可以在MDC中放置信息,然后通过特定的logback组件获取。注意,子线程不会自动继承父线程的MDC。

package org.slf4j;

public class MDC {
  // 将上下文的值作为 MDC 的 key 放到线程上下的 map 中
  public static void put(String key, String val);

  // 通过 key 获取上下文标识
  public static String get(String key);

  // 通过 key 移除上下文标识
  public static void remove(String key);

  // 清除 MDC 中所有的 entry
  public static void clear();
}

PatternLayout中的 %X{key}就可以获取MDC中的值。

参考资料
logback 中文 tutorial
日志采坑总结
log4j2 英文 tutorial
log4j2 中文 tutorial
logback性能优化
动态修改log等级

Original: https://www.cnblogs.com/rachel-aoao/p/java_logback.html
Author: rachel_aoao
Title: Java的日志框架之Logback

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/601299/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Centos7.9编译OpenSSH的rpm安装包并升级OpenSSH

    本文介绍如何通过openssh-9.0p1.tar.gz制作openssh的rpm安装包,并升级openssh到9.0。 下载openssh-9.0p1.tar.gz 编译成rpm…

    数据库 2023年6月14日
    097
  • 折腾 2 年多!我们终于见面了!

    2018年3月,我与张老师就这么在微信上聊了起来,起初我并没有写书的打算,我们之间只是通过讨论、交流的形式聊聊关于出书的方方面面。最终,敌不过张老师超强的专业能力、细致的解说与盛情…

    数据库 2023年6月9日
    0172
  • 一张图弄懂sql的连接查询

    无意中看到一张图,非常直观的表现出了sql连接查询的结果集,对连接查询的理解十分有帮助,所以收藏了下来。 其中红色部分为可以查询出的数据,白色部分为不能查询出的数据 Origina…

    数据库 2023年6月14日
    0107
  • Java正则表达式Pattern和Matcher类详解

    概述 Pattern类的作用在于编译正则表达式后创建一个匹配模式. Matcher类使用Pattern实例提供的模式信息对正则表达式进行匹配 Pattern类 常用方法及介绍 Pa…

    数据库 2023年6月11日
    0102
  • IDEA中如何查看接口的所有实现类呢?

    接口是我们日常开发中常用的操作,那么如何查看一个接口有哪些实现类呢?下文笔者将讲述IDEA编辑器中 查看实现类的快捷方法,如下所示 在spring源码阅读中,每一个接口都有很多实现…

    数据库 2023年6月11日
    084
  • Linux–>开关机+用户管理指令

    shutdown关机 语法: shutdown -h &#x5173;&#x673A;&#x65F6;&#x95F4; now 立刻1 1分种后 s…

    数据库 2023年6月14日
    091
  • firewall 命令简单操作

    Firewalld 是维护防火墙策略的守护程序的名称。使用 firewall-cmd 命令与防火墙配置进行交互, 使用区域概念对与系统交互的流量进行分段。网络接口分配给一个或多个区…

    数据库 2023年6月14日
    084
  • MySQL45讲-2-一条SQL更新语句是如何执行的?

    前面我们了解了SQL查询语句是如何执行的,一条SQL查询语句的过程需要经过连接器、分析器、优化器、执行器等功能模块,最终到达存储引擎。 在MySQL中,可以恢复到半个月内的任何一个…

    数据库 2023年5月24日
    095
  • JavaScript 获取 Url 上的参数(QueryString)值

    获取URL里面传的参数,在Js中不能像后台一样使用Request.QueryString来获取URL里面参数,下面介绍两种方式用来获取参数 方式一:使用split分隔来获取,这种方…

    数据库 2023年6月9日
    082
  • 给小白的 PostgreSQL 容器化部署教程(上)

    作者:王志斌编辑:钟华龙 本文来自社区小伙伴 王志斌 的投稿。从小白的角度,带你一步步实现将 RadonDB PostgreSQL 集群部署到 Kubernetes 上。文章分为上…

    数据库 2023年5月24日
    0115
  • postman结合newman生成测试报告

    1. cmd窗口安装newman npm install -g newman 2. cmd窗口安装newman-html报告 nnpm install -g newman-repo…

    数据库 2023年5月24日
    078
  • 5、枚举Enum

    枚举类会 隐式的继承Enum类,无法再继承其它类( 单继承机制) 一、无实参枚举类型: 1、定义: /** * 1、无实参枚举类型 */ public enum NoParamTy…

    数据库 2023年6月6日
    0106
  • SQL的约束

    概念:约束是作用于表中字段上的规则,用于限制存储表中的数据; 常见的约束分类:约束 描述 关键字 非空约束 限制该字段的数据不能为null not null 唯一约束 保证该字段的…

    数据库 2023年6月16日
    095
  • 分享一个有意思的错误

    subList方法拆分集合问题 JAVA技术交流群:737698533 分享一个有意思的错误,先看代码 public static void main(String[] args)…

    数据库 2023年6月16日
    0108
  • Java基础四—泛型、注解、异常、反射

    泛型 泛型的本质是为了参数化类型( 在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类…

    数据库 2023年6月6日
    092
  • 记一次部署系列:prometheus通过pushgateway配置自定义监控项

    1、部署pushgateway并配置prometheus能够拿到pushgateway的数据,类似下图。部署过程参考:https://www.cnblogs.com/sunnyto…

    数据库 2023年6月9日
    0235
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球