Logback 架构与输出流程

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Logback 主要由三个 jar 一起组成• slf4j-api• logback-core• logback-classic

网络异常,图片无法展示
|


Logback 主要由三个 jar 一起组成

  • slf4j-api
  • logback-core
  • logback-classic
<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
</dependencies>
复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main.class);
        logger.debug("hello world");
    }
}
复制代码


当不存在默认配置时、Logback 会在 root logger 中新增一个 ConsoleAppender

Logback 可以打印其内部的状态信息、通过 StatusPrinter 和 StatusManager

private static void test2(){
    LoggerContext loggerContext  = (LoggerContext) LoggerFactory.getILoggerFactory();
    StatusManager statusManager = loggerContext.getStatusManager();
    StatusPrinter.print(statusManager);
}
复制代码


打印如下

21:20:30,270 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
21:20:30,270 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
21:20:30,271 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
21:20:30,274 |-INFO in ch.qos.logback.classic.BasicConfigurator@6d8a00e3 - Setting up default configuration.
复制代码


没有找到三个默认的配置文件

  • logback-test.xml
  • logback.groovy
  • logback.xml


只能使用默认的配置、将日志输出到控制台。

Appender 作为输出的目的地

  • console
  • file
  • db
  • MQ


如果发生了错误、Logback 会在控制台打印自己内部的状态


三大组件


  • Logger
  • Appender
  • Layous

Logger 作为 logback-classic 模块的一部分、Appender 与 Layouts 接口作为 logback-core 的一部分、作为一个通用的模块、logback-core 没有 Logger 的概念


LoggerContext

网络异常,图片无法展示
|


实现了 slf4j 的 ILoggerFactory 接口、用于产生 Logger

网络异常,图片无法展示
|


LoggerContext 主要的成员变量

网络异常,图片无法展示
|

  • 第一个是 root logger
  • 第二个就是一个缓存、所以对于 Logger 的名字来说、它大小写敏感
  • TurboFilter 作为一个全局的过滤器


层级结构

Logback 的 Logger 以 name 的 . 作为层级结构的划分

名为 com.demo.Main 的 logger 它的父 logger 是 com.demo logger、再往上就是 com 在网上就是 root

public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
    private static final long serialVersionUID = 5454405123156820674L; // 8745934908040027998L;
    public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();
    private String name;
    transient private Level level;
    transient private int effectiveLevelInt;
    transient private Logger parent;
    transient private List<Logger> childrenList;
复制代码

字段 parent 就是用来存放父级 logger 的、而 childrenList 则是存放子 logger 的、当父 logger 的等级发生改变的时候就是依靠该字段去通知子 logger 。


有效等级

我们知道如果我们 logger 设置的等级大于调用的方法、那么该日志不会被输出、显然这种等值的比较、可以依靠数值来实现。而这个值在 Logger 类中就是 effectiveLevelInt 这个字段

public static final int OFF_INT = Integer.MAX_VALUE;
public static final int ERROR_INT = 40000;
public static final int WARN_INT = 30000;
public static final int INFO_INT = 20000;
public static final int DEBUG_INT = 10000;
public static final int TRACE_INT = 5000;
public static final int ALL_INT = Integer.MIN_VALUE;
    public static final Level OFF = new Level(OFF_INT, "OFF");
    public static final Level ERROR = new Level(ERROR_INT, "ERROR");
    public static final Level WARN = new Level(WARN_INT, "WARN");
    public static final Level INFO = new Level(INFO_INT, "INFO");
    public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG");
    public static final Level TRACE = new Level(TRACE_INT, "TRACE");
    public static final Level ALL = new Level(ALL_INT, "ALL");
复制代码


我们可以看到

public LoggerContext() {
    super();
    this.loggerCache = new ConcurrentHashMap<String, Logger>();
    this.loggerContextRemoteView = new LoggerContextVO(this);
    this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
    this.root.setLevel(Level.DEBUG);
    loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
    initEvaluatorMap();
    size = 1;
    this.frameworkPackages = new ArrayList<String>();
}
复制代码


默认情况下 root logger 的 level 为 debug、effectiveLevelInt 的值为 10000

默认情况下、如果我们创建使用的 logger 没有指定 level 的话、它会继承它的父类的 effectiveLevelInt

Logger createChildByName(final String childName) {
    ........
    Logger childLogger;
    childLogger = new Logger(childName, this, this.loggerContext);
    childrenList.add(childLogger);
    childLogger.effectiveLevelInt = this.effectiveLevelInt;
    return childLogger;
} 
复制代码

this 就是父 logger


当 父 logger level 发生变化时、会调用方法通知子 logger

Logger createChildByName(final String childName) {
    int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
    if (i_index != -1) {
        throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
                        + " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
    }
    if (childrenList == null) {
        childrenList = new CopyOnWriteArrayList<Logger>();
    }
    Logger childLogger;
    childLogger = new Logger(childName, this, this.loggerContext);
    childrenList.add(childLogger);
    childLogger.effectiveLevelInt = this.effectiveLevelInt;
    return childLogger;
}
复制代码


demo 如下

private static void test3(){
    ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("x.y");
    logger.setLevel(Level.INFO);
    logger.info("info");
    logger.debug("debug");
    ch.qos.logback.classic.Logger childLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("x.y.z");
    childLogger.info("info");
    childLogger.debug("debug");
    logger.setLevel(Level.TRACE);
    logger.trace("trace");
    logger.debug("debug");
    childLogger.trace("trace");
    childLogger.debug("debug");
}
复制代码

输出的话、你自己想想


Appender

一个 Logger 可以有多个 Appender、比如说日志既可以输出到控制台也可以到日志文件

// Logger 类中使用 AppenderAttachableImpl<ILoggingEvent> aai 保存、内部使用 COWList 保存
public synchronized void addAppender(Appender<ILoggingEvent> newAppender) {
    if (aai == null) {
        aai = new AppenderAttachableImpl<ILoggingEvent>();
    }
    aai.addAppender(newAppender);
}
复制代码


Appender 默认也是具有继承的特性、比如说 root 的 Appender 是 Console、而子 logger 的 Appender 是 file、那么使用子 logger 打印日志、日志即会输出到日志文件也会输出到 console 中

// Logger
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);
    }
}
复制代码
// AppenderAttachableImpl
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;
}
复制代码

先调用自己的 Appender 然后调用父类的。

可以将 additive 设置为 false、那么子 logger 不再对父 logger 有继承 Appender


流程


过滤链

public void info(String msg) {
    filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
}
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                                    final Throwable t) {
  final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
  if (decision == FilterReply.NEUTRAL) {
    if (effectiveLevelInt > level.levelInt) {
      return;
    }
  } else if (decision == FilterReply.DENY) {
    return;
  }
  buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}
复制代码


从 LoggerContext 中获取 TurboFilterList

final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
                final Object[] params, final Throwable t) {
    if (turboFilterList.size() == 0) {
        return FilterReply.NEUTRAL;
    }
    return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
}
复制代码


默认情况是没有 turboFilter 、所以会返回 NEUTRAL

final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
                final Object[] params, final Throwable t) {
    if (turboFilterList.size() == 0) {
        return FilterReply.NEUTRAL;
    }
    return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
}
复制代码
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) {
            return r;
        }
    }
    return FilterReply.NEUTRAL;
}
复制代码

如果存在多个、如果明确是 DENY 或者 ACCPET 的话、直接返回、如果不确定、那么遍历到最后返回 NEUTRAL

如果返回到是 NEUTRAL 则判断调用的方法的级别是否大于 logger 设置的 level 如果小于则不打印直接返回。

同理、返回 DENY 也是直接返回、只有 ACCEPT 才能继续往下走


LoggingEvent 对象

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.setMarker(marker);
    callAppenders(le);
}
复制代码

LoggingEvent 对象,这个对象包含了日志请求所有相关的参数,请求的 logger,日志请求的级别,日志信息,与日志一同传递的异常信息,当前时间,当前线程,以及当前类的各种信息和 MDC。


调用 Appender

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);
    }
}
复制代码


这个在上面谈及过

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;
}
复制代码
public synchronized void doAppend(E eventObject) {
    // WARNING: The guard check MUST be the first statement in the
    // doAppend() method.
    // prevent re-entry.
    if (guard) {
        return;
    }
    try {
        guard = true;
        if (!this.started) {
            if (statusRepeatCount++ < ALLOWED_REPEATS) {
                addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
            }
            return;
        }
        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 = false;
    }
}
复制代码

这里会涉及到自定义的过滤器 getFilterChainDecision


格式化输出

// SyslogAppenderBase
@Override
protected void append(E eventObject) {
    if (!isStarted()) {
        return;
    }
    try {
        String msg = layout.doLayout(eventObject);
        if (msg == null) {
            return;
        }
        if (msg.length() > maxMessageSize) {
            msg = msg.substring(0, maxMessageSize);
        }
        sos.write(msg.getBytes(charset));
        sos.flush();
        postProcess(eventObject, sos);
    } catch (IOException ioe) {
        addError("Failed to send diagram to " + syslogHost, ioe);
    }
}
复制代码


这个会借助 Layout 对象、将 EventObject 转换成一个字符串。

但是也存在一些 Appender 不需要对其进行格式化、比如说输出到MQ、Socket 之类的

private void dispatchEvents(ObjectWriter objectWriter) throws InterruptedException, IOException {
    while (true) {
        E event = deque.takeFirst();
        postProcessEvent(event);
        Serializable serializableEvent = getPST().transform(event);
        try {
            objectWriter.write(serializableEvent);
        } catch (IOException e) {
            tryReAddingEventToFrontOfQueue(event);
            throw e;
        }
    }
}
复制代码


它们将其序列化如何写入输出流中

public Serializable transform(ILoggingEvent event) {
        if (event == null) {
            return null;
        }
        if (event instanceof LoggingEvent) {
            return LoggingEventVO.build(event);
        } else if (event instanceof LoggingEventVO) {
            return (LoggingEventVO) event;
        } else {
            throw new IllegalArgumentException("Unsupported type " + event.getClass().getName());
        }
    }
}
复制代码


发送 LoggingEvent

当日志事件被完全格式化之后将会通过每个 appender 发送到具体的目的地。



目录
相关文章
|
6月前
|
SQL 开发框架 安全
Linux系统中ARMv8架构u-boot启动流程分析
Linux系统中ARMv8架构u-boot启动流程分析
364 1
|
11月前
|
XML JSON 监控
浅谈logback日志架构
浅谈logback日志架构
133 0
|
存储 关系型数据库 数据库
【北亚企安数据恢复】Ceph分布式存储基本架构&Ceph数据恢复流程
Ceph存储可分为块存储,对象存储和文件存储。Ceph基于对象存储,对外提供三种存储接口,故称为统一存储。 Ceph的底层是RADOS(分布式对象存储系统),RADOS由两部分组成:OSD和MON。 MON负责监控整个集群,维护集群的健康状态,维护展示集群状态的各种图表,如OSDMap、MonitorMap、PGMap和CRUSHMap。 OSD负责存储数据、复制数据、平衡数据、恢复数据,与其它OSD间进行心跳检查等。通常情况下一块硬盘对应一个OSD。
|
12天前
|
JSON JavaScript 前端开发
Vue3源码架构简析及Monorepo流程构建
【10月更文挑战第12天】Vue3源码架构简析及Monorepo流程构建
Vue3源码架构简析及Monorepo流程构建
|
22天前
|
消息中间件 分布式计算 Kafka
大数据-98 Spark 集群 Spark Streaming 基础概述 架构概念 执行流程 优缺点
大数据-98 Spark 集群 Spark Streaming 基础概述 架构概念 执行流程 优缺点
30 0
|
4月前
|
NoSQL Redis UED
业务架构问题之在流程建模中,“定职责”的重要性是什么,流程建模中的交互设计原则是什么
业务架构问题之在流程建模中,“定职责”的重要性是什么,流程建模中的交互设计原则是什么
|
3月前
|
缓存 Java Maven
SpringCloud基于Eureka的服务治理架构搭建与测试:从服务提供者到消费者的完整流程
Spring Cloud微服务框架中的Eureka是一个用于服务发现和注册的基础组件,它基于RESTful风格,为微服务架构提供了关键的服务注册与发现功能。以下是对Eureka的详细解析和搭建举例。
|
6月前
|
存储 监控 供应链
一款数字化管理平台源码:云MES系统(附架构图、流程、)
制造生产企业打造数字化生产管控的系统,从原材料、生产报工、生产过程、质检、设备、仓库等整个业务流程的管理和控制,合理安排生产计划、实时监控生产、优化生产工艺、降低不良产出和运营成本;
126 8
一款数字化管理平台源码:云MES系统(附架构图、流程、)
|
5月前
|
监控 Java 持续交付
构建Java微服务架构的CI/CD流程
构建Java微服务架构的CI/CD流程
|
6月前
|
算法 安全
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
346 1
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)

热门文章

最新文章