ログバックアーキテクチャと出力プロセス

WeChat public account_CoderLiログバックは主に3つのjarファイルで構成されています

  • slf4j-api
  • ログバックコア
  • logback-クラシック
<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は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.
复制代码

3つのデフォルト構成ファイルが見つかりません

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

デフォルト構成のみを使用して、ログをコンソールに出力できます。

出力先としてのアペンダー

  • コンソール
  • ファイル
  • db
  • SQM

エラーが発生した場合、Logbackは自身の内部状態をコンソールに出力します

3つのコンポーネント

  • ロガー
  • アペンダー
  • Layous

logback-classicモジュールの一部としてのLogger、logback-coreの一部としてのAppenderおよびLayoutsインターフェース、一般的なモジュールとして、logback-coreにはLoggerの概念がありません。

LoggerContext

WeChatパブリックアカウント:CoderLi

ロガーを生成するためのslf4jのILoggerFactoryインターフェイスを実装します

WeChatパブリックアカウント:CoderLi

LoggerContextのメインメンバー変数

画像-20220322213444464

  • 1つ目はルートロガーです
  • 2つ目はキャッシュであるため、ロガーの名前では大文字と小文字が区別されます
  • グローバルフィルターとしてのTurboFilter

階層

Logbackのロガーは階層構造.としてます

親ロガーがロガーであり、あるcom.demo.Mainという名前のロガーはcom.democomroot

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

リターンがニュートラルの場合、呼び出されたメソッドのレベルがロガーによって設定されたレベルよりも大きいかどうかが判断され、それよりも小さい場合、直接印刷およびリターンされません。

同様に、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オブジェクト。このオブジェクトには、ログリクエスト、リクエストされたロガー、ログリクエストのレベル、ログ情報、ログとともに渡される例外情報、現在の時刻、現在のスレッド、およびのさまざまな情報に関連するすべてのパラメータが含まれます。現在のクラスとMDC。

アペンダーに電話する

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が文字列に変換されます。

ただし、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を送信します

ログイベントが完全にフォーマットされると、各アペンダーを介して特定の宛先に送信されます。

おすすめ

転載: juejin.im/post/7078482522815856654