再看 Logback 源码

news2024/11/19 12:35:40

三大组件

Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。

Logger 类作为 logback-classic 模块的一部分。AppenderLayouts 接口作为 logback-core 的一部分。作为一个通用的模块,logback-core 没有 logger 的概念。

Appender 与 Layout

有选择的启用或者禁用日志的输出只是 logger 的一部分功能。logback 允许日志在多个地方进行输出。站在 logback 的角度来说,输出目的地叫做 appender。appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中。

一个 logger 可以有多个 appender。

logger 通过 addAppender 方法来新增一个 appender。对于给定的 logger,每一个允许输出的日志都会被转发到该 logger 的所有 appender 中去。换句话说,appender 从 logger 的层级结构中去继承叠加性。例如:如果 root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来。如果再给一个叫做 L 的 logger 添加了一个 file appender,那么 L 以及 L 的子级 logger 都可以在文件和控制台打印日志。可以通过设置 additivity = false 来改写默认的设置,这样 appender 将不再具有叠加性。

通常,用户既想自定义日志的输出地,也想自定义日志的输出格式。通过给 appender 添加一个 layout 可以做到。layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地。PatternLayout 能够根据用户指定的格式来格式化日志,类似于 C 语言的 printf 函数。

例:PatternLayout 通过格式化串 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 会将日志格式化成如下结果:

176  [main] DEBUG manual.architecture.HelloWorld2 - Hello world.

底层流程

在介绍了基本的 logback 组件之后,我们准备介绍一下,当用户调用日志的打印方法时,logback 所执行的步骤。现在我们来分析一下当用户通过一个名为 com.wombat 的 logger 调用了 info() 方法时,logback 执行了哪些步骤。

第一步:获取过滤器链

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

public class Main1 {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main1.class);
        logger.info("test:{}"111);
    }
}

ch.qos.logback.classic.Logger

public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();

public void info(String format, Object arg) {
    filterAndLog_1(FQCN, null, Level.INFO, format, arg, null);
}

private void filterAndLog_1(final String localFQCN, final Marker marker, final Level level, final String msg,final Object param, final Throwable t) {

  //获取 TurboFilter 过滤链
  final FilterReply decision = loggerContext.getTurboFilterChainDecision_1(marker, this, level, msg, param, t);

  // 如果结果是中立的、则判断日志级别
  if (decision == FilterReply.NEUTRAL) {
    if (effectiveLevelInt > level.levelInt) {
      return;
    }
  } else if (decision == FilterReply.DENY) {
    // 如果是拒绝、直接返回不需要打印了
    return;
  }

  // 这个后面分析
  buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);
}

ch.qos.logback.classic.spi.TurboFilterList 这个位于 classic 包

public FilterReply getTurboFilterChainDecision(final Marker marker, final Logger logger, final Level level,
        final String format, final Object[] params, final Throwable t)
 
{

    final int size = size();
    // if (size == 0) {
    // return FilterReply.NEUTRAL;
    // }
    if (size == 1) {
        try {
            TurboFilter tf = get(0);
            return tf.decide(marker, logger, level, format, params, t);
        } catch (IndexOutOfBoundsException iobe) {
            return FilterReply.NEUTRAL;
        }
    }

    Object[] tfa = toArray();
    final int len = tfa.length;
    for (int i = 0; i < len; i++) {
        // for (TurboFilter tf : this) {
        final TurboFilter tf = (TurboFilter) tfa[i];
        final FilterReply r = tf.decide(marker, logger, level, format, params, t);
        if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
          // 如果是 deny 或者 accept 直接返回
            return r;
        }
    }
    return FilterReply.NEUTRAL;
}
public enum FilterReply {
    DENY, NEUTRAL, ACCEPT;
}
微信公众号:CoderLi
微信公众号:CoderLi

第二步:Logger 级别过滤

如果返回的 Reply 是 neutral 则会根据级别进行过滤。如果是 deny 则直接返回

微信公众号:CoderLi
微信公众号:CoderLi

第三步:创建 LoggingEvent 对象

buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);

private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level,
        final String msg, final Object[] params, final Throwable t)
 
{
    LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
    le.addMarker(marker);
    callAppenders(le);
}
微信公众号:CoderLi
微信公众号:CoderLi

从参数数组中提取异常

image-20221022221235596
image-20221022221235596
public static final Throwable extractThrowable(Object[] argArray) {
    if (argArray == null || argArray.length == 0) {
        return null;
    }

    final Object lastEntry = argArray[argArray.length - 1];
    if (lastEntry instanceof Throwable) {
        return (Throwable) lastEntry;
    }
    return null;
}

提取最后一个参数

public static Object[] trimmedCopy(Object[] argArray) {
    if (argArray == null || argArray.length == 0) {
        throw new IllegalStateException("non-sensical empty or null argument array");
    }
    final int trimemdLen = argArray.length - 1;
    Object[] trimmed = new Object[trimemdLen];
    System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
    return trimmed;
}

重新赋值给这个参数数组

第四步:调用 appender

/**
 * Invoke all the appenders of this logger.
 * 
 * @param event The event to log
 */

public void callAppenders(ILoggingEvent event) {
    int writes = 0;
    for (Logger l = this; l != null; l = l.parent) {
        writes += l.appendLoopOnAppenders(event);
        if (!l.additive) {
            break;
        }
    }
    // No appenders in hierarchy
    if (writes == 0) {
        loggerContext.noAppenderDefinedWarning(this);
    }
}

private int appendLoopOnAppenders(ILoggingEvent event) {
  if (aai != null) {
    return aai.appendLoopOnAppenders(event);
  } else {
    return 0;
  }
}

public int appendLoopOnAppenders(E e) {
  int size = 0;
  final Appender<E>[] appenderArray = appenderList.asTypedArray();
  final int len = appenderArray.length;
  for (int i = 0; i < len; i++) {
    appenderArray[i].doAppend(e);
    size++;
  }
  return size;
}
微信公众号:CoderLi
微信公众号:CoderLi

这里有两个实现累、一个是同步的、这个正常是不会使用这个的了

微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi

我们直接看另一个的实现

/**
     * The guard prevents an appender from repeatedly calling its own doAppend
     * method.
     */

private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();

public void doAppend(E eventObject) {

    if (Boolean.TRUE.equals(guard.get())) {
        return;
    }

    try {
        guard.set(Boolean.TRUE);
    ............
        // 只要不是 deny 都可以继续
        if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
            return;
        }

        // ok, we now invoke derived class' implementation of append
        this.append(eventObject);

    } catch (Exception e) {
        if (exceptionCount++ < ALLOWED_REPEATS) {
            addError("Appender [" + name + "] failed to append.", e);
        }
    } finally {
        guard.set(Boolean.FALSE);
    }
}

getFilterChainDecision 这里也出现了一个 Filter 的接口

public FilterReply getFilterChainDecision(E event) {
    return fai.getFilterChainDecision(event);
}

// ch.qos.logback.core.spi.FilterAttachableImpl
public FilterReply getFilterChainDecision(E event) {

  final Filter<E>[] filterArrray = filterList.asTypedArray();
  final int len = filterArrray.length;

  for (int i = 0; i < len; i++) {
    final FilterReply r = filterArrray[i].decide(event);
    if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
      return r;
    }
  }

  // no decision
  return FilterReply.NEUTRAL;
}
微信公众号:CoderLi
微信公众号:CoderLi

这个 Filter 是位于 core 模块中的、每一个 Appender 都有自己的 FilterList

@Override
protected void append(E eventObject) {
    if (!isStarted()) {
        return;
    }

    subAppend(eventObject);
}

// ch.qos.logback.core.OutputStreamAppender
// RollingFileAppender 只是多了一个滚动文件、也就是新建新的日志文件
protected void subAppend(E event) {
  if (!isStarted()) {
    return;
  }
  try {
    // this step avoids LBCLASSIC-139
    if (event instanceof DeferredProcessingAware) {
      ((DeferredProcessingAware) event).prepareForDeferredProcessing();
    }
    writeOut(event);

  } catch (IOException ioe) {
    // as soon as an exception occurs, move to non-started state
    // and add a single ErrorStatus to the SM.
    this.started = false;
    addStatus(new ErrorStatus("IO failure in appender"this, ioe));
  }
}

第五步:格式化输出

protected void writeOut(E event) throws IOException {
    byte[] byteArray = this.encoder.encode(event);
    writeBytes(byteArray);
}
微信公众号:CoderLi
微信公众号:CoderLi

ch.qos.logback.core.encoder.LayoutWrappingEncoder

public byte[] encode(E event) {
    String txt = layout.doLayout(event);
    return convertToBytes(txt);
}

微信公众号:CoderLi
微信公众号:CoderLi

ch.qos.logback.classic.PatternLayout

public String doLayout(ILoggingEvent event) {
    if (!isStarted()) {
        return CoreConstants.EMPTY_STRING;
    }
    return writeLoopOnConverters(event);
}

protected String writeLoopOnConverters(E event) {
  StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
  Converter<E> c = head;
  while (c != null) {
    c.write(strBuilder, event);
    c = c.getNext();
  }
  return strBuilder.toString();
}

head 是如何赋值的

微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi
微信公众号:CoderLi

解释 pattern 表达式

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
微信公众号:CoderLi
微信公众号:CoderLi

Converter 主要主要的作用就是解释表达式对应的字符串。可以借助这个 converter 做日志脱敏

微信公众号:CoderLi
微信公众号:CoderLi

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VapSR

1024刚过还得搬砖 注意机制在设计高级超分辨率&#xff08;SR&#xff09;网络中起着关键作用。在这项工作中&#xff0c;作者们通过改进注意机制设计了一个高效的SR网络。VapSR以更少的参数优于当前的轻量级网络。董超团队开源超大感受野注意力超分方案 paper&#xff1a;ht…

【数据挖掘 | 可视化】 WordCloud 词云(附详细代码案例)

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 开发环境 编辑器&#xff1a; jupyter notebook 解释器&#xff1a; python 3.7在七夕节中&#xff0c;博主写了一篇为女友收集…

自学Vue之路——Vue介绍及基本语法

今日内容概要 前端发展介绍Vue的快速使用插值语法指令系统之文本指令指令系统之事件指令指令系统之属性指令 前端发展介绍 HTML(5)、CSS(3)、JavaScript(ES5、ES6、ES13)&#xff1a;编写一个个的页面 -> 给后端(PHP、Python、Go、Java) -> 后端嵌入模板语法 -> 后端…

Arduino基础知识

目录&#xff1a; 第1章 概述 1.1 Arduino简介 1.2 Arduino内部结构 第2章 Arduino编程 2.1 Arduino开发环境 2.2 Arduino语言概述 2.3 Arduino基本函数 第3章 Arduino通信教程 3.1 SPI通信 3.2 红外通信 3.3 WiFi通信 3.4 蓝牙通信 第4章 Arduino实验 1、接收串…

【学生管理系统】权限管理之用户管理—查询所有用户并关联相关角色

目录 一、查询所有用户&#xff08;关联角色&#xff09; 1&#xff09;后端 2&#xff09;前端 &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、查询所有用户&#xff08;关联角色&#xff09; 1&#xff09;后端 修改javaB…

牛客小题练手 | 二叉树(三)

&#x1f308;刷题&#xff0c;面试&#xff0c;求职&#xff0c;快来牛客网一起成为offer收割机&#xff01; 点击注册收割offer 一、BM32 合并二叉树 描述 已知两颗二叉树&#xff0c;将它们合并成一颗二叉树。合并规则是&#xff1a;都存在的结点&#xff0c;就将结点值加…

前端无法渲染CSS文件

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

Air780E模块PPP应用开发指南

目录PPP拨号windows下PPP拨号配置标准调制解调器设置拨号连接验证拨号结果linux下PPP拨号1.安装 PPP 拨号软件2.创建拨号脚本文件/etc/ppp/peers/air-ppp/etc/ppp/peers/air-chat-connect/etc/ppp/peers/air-chat-disconnect3.创建好以上三个文件后就可以通过pppd拨号了4.拨号成…

机械转码日记【21】list使用及list的模拟实现

目录 前言 1.list的使用 1.2sort和unique 2.list的模拟 2.1构造函数 2.2push_back() 2.3迭代器 2.3.1简洁版 2.3.2升级版&#xff08;重要&#xff09; 2.4insert和erase与迭代器失效 2.4.1list的迭代器失效 2.5析构函数 2.6深拷贝构造 前言 list是我们数据结构…

Web前端 | JavaScript(DOM编程)

✅作者简介&#xff1a;一位材料转码农的选手&#xff0c;希望一起努力&#xff0c;一起进步&#xff01; &#x1f4c3;个人主页&#xff1a;每天都要敲代码的个人主页 &#x1f525;系列专栏&#xff1a;Web前端 &#x1f4ac;推荐一款模拟面试、刷题神器&#xff0c;从基础到…

Qt文档阅读笔记-Hello Speak Example

官方的这个例子比较有意思&#xff0c;在此记录下&#xff0c;方便以后查阅。 Hello Speak Example 这个例子主要是使用QTextToSpeech类将用户自定义输入的文本转换为口语&#xff0c;包括高低音、声音大小、读速。并且能够选择语言和声音。 包含的文件如下&#xff1a; 本篇博…

一文搞懂【知识蒸馏】【Knowledge Distillation】算法原理

知识蒸馏算法原理精讲 文章目录知识蒸馏算法原理精讲1. 什么是知识蒸馏&#xff1f;2. 轻量化网络的方式有哪些&#xff1f;3. 为什么要进行知识蒸馏&#xff1f;3.1 提升模型精度3.2 降低模型时延&#xff0c;压缩网络参数3.3 标签之间的域迁移4. 知识蒸馏的理论依据&#xff…

【JavaWeb】初识HTTP学习

文章目录JavaWeb之HTTP学习1、HTTP相关基本概念2、数据格式介绍2.1 请求的数据格式2.2 响应的数据格式案例JavaWeb之HTTP学习 1、HTTP相关基本概念 什么是HTTP&#xff1f; HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个简单的请…

Spring Cloud基本介绍

✨ Spring Cloud基本介绍1.微服务中的相关概念1.1服务的注册与实现1.2负载均衡1.3熔断1.4链路追踪1.5API网关2.Spring Cloud的介绍2.1基本认识2.2Spring Cloud的架构2.2.1Spring Cloud的核心组件2.2.2Spring Cloud体系结构2.3Spring Boot和Spring Cloud的区别与联系2.3.1 Sprin…

【Spring】简单的登录案例和配套知识

本篇文章接着介绍 Spring 的相关知识&#xff0c;主要通过一个非常非常简单用户登录案例来介绍&#xff0c;各位大佬们路过记得赏小的一颗赞&#x1f929; 文章目录1. 演示一下 Spring 管理类的模式2. 用户登录案例2.1 准备的对象和其功能2.1.1 User2.1.2 UserController2.1.3 …

05、JavaWeb启程——JDBC详解

1、JDBC概述 1、持久化概述 【简介】&#xff1a; 持久化指的是把内存中的数据存储到可掉电存储设备中以供之后使用。 2、JDBC概述 【简介】&#xff1a; JDBC是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系型数据库提供统一的访问。 【JDBC本质】&#xff1…

【Java】泛型

当你觉得这条路很难走的时候&#xff0c;一定是上坡路 目录 1.初识泛型 1.1 什么是泛型 1.2泛型类语法 1.2.1泛型类定义 1.2.2泛型类使用语法 1.2.3泛型类的使用 1.2.4裸类型 2.泛型如何编译 2.1擦除机制 3.泛型的上界 3.1语法 3.2示范 4.泛型方法 4.1 语法 …

i.MX 6ULL 驱动开发 十九:RGBLCD

一、RGBLCD 硬件原理 【正点原子MP157连载】第十八章 RGB LCD彩条显示实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_正点原子的博客-CSDN博客 ATK7016 时序参数&#xff1a; 二、eLCDIF 接口 eLCDIF 是 I.MX6U 自带的液晶屏幕接口&#xff0c;用于连接 RGB …

【Linux】没有GDB,何谈Linux C

一、简单的开始 1、有C代码如下 #include <stdio.h>void main() {printf("Hello World!"); }2、通过gcc编译 生成带有调试信息的可运行程序&#xff0c;编译参数-g gcc -g hello.c -o hello3、运行GDB -q表示不打印gdb版本信息&#xff0c;界面较为干净 …

linux内核调试工具之kprobe

目录 一、内核调试的痛点 二、kprobe的优点 三、kprobe探测点的要点 四、探测点的开销与优化 五、内核配置 六、API 七、程序架构 八、实例 一、内核调试的痛点 内核调试&#xff0c;添加打印信息。在运行过程中想看某个函数的变量&#xff0c;需要重新编译内核。这样破…