Log4J2源码系列(十) - 内部日志StatusLogger的实现原理

x33g5p2x  于2021-12-25 转载在 其他  
字(4.3k)|赞(0)|评价(0)|浏览(434)

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的参数配置,用途,以及其底层的实现原理。

相关文章