Log4J2源碼系列(十) - 內部日誌StatusLogger的實現原理

StatusLogger是用來打印Log4J2的信息的,包括加載插件的過程,信息,解析自定義配置的信息,譬如當系統未檢測到自定義的配置時:

No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' o show Log4j2 internal initialization logging.

配置屬性

  • StatusLogger相關的屬性可以在log4j2.StatusLogger.properties中配置,Log4J2會通過PropertiesUtil工具類把相關屬性讀取出來:
# StatusLogger的隊列中保存的最大日誌條數,默認200條
log4j2.StatusLogger.entries
# status listeners的日誌級別默認是warn
log4j2.StatusLogger.level
# 布爾值,如果配置爲true,則會顯示所有的status日誌
log4j2.debug
  • 在自定義配置文件,如xml中,配置status屬性, 例子:
<Configuration status="info" monitorInterval="60">
<!--中間部分省略-->
<Configuration/>

status的合法屬性在Level類中有定義, 默認level是ERROR,不區分大小寫

	static {
        OFF = new Level("OFF", StandardLevel.OFF.intLevel());
        FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel());
        ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel());
        WARN = new Level("WARN", StandardLevel.WARN.intLevel());
        INFO = new Level("INFO", StandardLevel.INFO.intLevel());
        DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel());
        TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel());
        ALL = new Level("ALL", StandardLevel.ALL.intLevel());
    }

實現

  • 構造方法:
    private StatusLogger(final String name, final MessageFactory messageFactory) {
    	//調用AbstractLogger的構造方法
        super(name, messageFactory);
        //真正的底層是依託於SimpleLLogger
        this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY,
                messageFactory, PROPS, System.err);
        //設置listener的日誌等級, 默認是WARN
        this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();

        //如果系統屬性log4j2.debug配置爲true,則把日誌等級設置爲TRACE
        if (isDebugPropertyEnabled()) {
            logger.setLevel(Level.TRACE);
        }
    }

* 打印日誌logMessage
```java
 	public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
            final Throwable t) {
        StackTraceElement element = null;
        //如果函數名不爲null,則打印調用棧信息
        if (fqcn != null) {
            element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
        }
        //StatusData是要打印的日誌以及相關的調用信息
        final StatusData data = new StatusData(element, level, msg, t, null);
        msgLock.lock();
        try {
            messages.add(data);
        } finally {
            msgLock.unlock();
        }
        // 如果打開了debug,則直接打印所有信息
        if (isDebugPropertyEnabled()) {
            logger.logMessage(fqcn, level, marker, msg, t);
        } else {
        	//如果有status listener,則status listener通知listener去處理
            if (listeners.size() > 0) {
                for (final StatusListener listener : listeners) {
                    if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
                        listener.log(data);
                    }
                }
            } else {
            	//調用SimpleLogger的方法打印日誌
                logger.logMessage(fqcn, level, marker, msg, t);
            }
        }
    }

注意:
- Thread.currentThread().getStackTrace()目前已爲不推薦的獲取stack trace的方法了,此前在log4j2(四) - 日誌位置是怎麼獲取到的?有什麼影響?中有說明過, 目前比較推薦的方案是採用new Throwable().getStackTrace()去獲取stack trace,有性能上的優勢。
- StatusData是JMX做監控時需要用到的 ,messages是用來存儲StatusData的有界隊列,隊列的大小受log4j2.StatusLogger.entries的控制,有關jmx的代碼這裏不作展開。

  • messages的代碼如下
	private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);

	private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {

        private static final long serialVersionUID = -3945953719763255337L;

        private final int size;

        BoundedQueue(final int size) {
            this.size = size;
        }

        @Override
        public boolean add(final E object) {
            super.add(object);
            while (messages.size() > size) {
                messages.poll();
            }
            return size > 0;
        }
    }
  • SimpleLogger的logMessage方法比較長,其實主要內容就是怎麼做消息的格式化,過程就不詳細的解釋了,大家參考一下
	public void logMessage(final String fqcn, final Level mgsLevel, final Marker marker, final Message msg,
            final Throwable throwable) {
        final StringBuilder sb = new StringBuilder();
        //格式化時間日誌
        if (showDateTime) {
            final Date now = new Date();
            String dateText;
            synchronized (dateFormatter) {
                dateText = dateFormatter.format(now);
            }
            sb.append(dateText);
            sb.append(SPACE);
        }
		//打印日誌等級
        sb.append(mgsLevel.toString());
        sb.append(SPACE);
        if (Strings.isNotEmpty(logName)) {
            sb.append(logName);
            sb.append(SPACE);
        }
        sb.append(msg.getFormattedMessage());
        //格式化mdc
        if (showContextMap) {
            final Map<String, String> mdc = ThreadContext.getImmutableContext();
            if (mdc.size() > 0) {
                sb.append(SPACE);
                sb.append(mdc.toString());
                sb.append(SPACE);
            }
        }
        //處理異常
        final Object[] params = msg.getParameters();
        Throwable t;
        if (throwable == null && params != null && params.length > 0
                && params[params.length - 1] instanceof Throwable) {
            t = (Throwable) params[params.length - 1];
        } else {
            t = throwable;
        }
        //這裏的stream,如果是statusLogger的話,這裏默認是System.err, 也就是說打印到錯誤輸出流
        stream.println(sb.toString());
        if (t != null) {
            stream.print(SPACE);
            t.printStackTrace(stream);
        }
    }

小結

本文簡單的介紹了一下StatusLogger的參數配置,用途,以及其底層的實現原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章