log4j源码浅析

一. 前言

1. 相关文档

2. 说明

  • log4j可以使用log4j.xml 和 log4j.properties​​​ 两种配置文件, 以下源码解析皆基于log4j.properties​​​ 来展开
  • 本文只讲核心代码, 不是主线的代码不展示出来

3. 代码准备

二. 常用类图

1. Logger的类图

在这里插入图片描述
说明:

  • 在log4j.properties中, 可以配置根logger 和 自定义logger. 根logger的实现类为RootLogger, 自定义logger的实现类为Logger
  • Logger的属性都在父类Category中, 主要为 logger的名字, 输出日志等级, 父节点logger, AppenderAttachableImpl(本logger绑定的appender列表, appdender为日志输出目的地)
  • logger的绑定关系: 类似链表结构, 自定义logger的最终父节点为rootLogger
  • logger的执行顺序: 先遍历执行该logger的所有appender(appdender的日志等级>=当前输出日志等级才执行), 若当前logger的additive=false, 结束. 再执行其父类logger, 直至根logger, 在这之间遇到logger的additive=false就会提前结束

2. LogManager类图

在这里插入图片描述
说明:

  • 日志框架初始化在LogManager中的static代码块中
  • LogManager有一个重要属性RepositorySelector, 意为选择器, 作用是产生一个LoggerRepository
  • LoggerRepository的业务实现类为Hierarchy, Hierarchy里面有一个重要属性Logger root, 就是根logger

3. Appender类图

在这里插入图片描述

说明:

  • 常用Appender有 ConsoleAppender, RollingFileAppender, DailyRollingFileAppender, 如图红色框选部分
  • Appender译为输出目的地, 它存储了日志要输出的等级, 格式, 位置等信息, 作为一个日志输出器

4. Layout类图

在这里插入图片描述
说明: 日志的输出格式. 实现类有
TTCCLayout (输出 耗时 + 线程名 + 日志级别 + logger名 + ndc + 日志内容)
EnhancedPatternLayout (PatternLayout 的增强版本, 不展开讲, 感兴趣的自行看下源码)
HTMLLayout (html的格式, 输出 耗时 + 线程名 + 日志内容 +ogger名 + 日志内容等 )
PatternLayout (通过设置模式, 个性化指定要输出的内容)
SimpleLayout (简易模式, 只输出 日志级别 + 日志内容)
XMLLayout (xml的格式, 输出 logger名 + 时间 +日志级别 + 线程名 + ndc + 日志内容等 )

5. WriterAppender结构图

在这里插入图片描述

三. 获取Logger对象(初始化)

初始化流程

在这里插入图片描述

说明:

  1. 通过Logger.getLogger(Class clazz) 或 Logger.getLogger(String name)进入
  2. 加载LogManager进jvm, 执行静态代码块执行初始化, 创建出RepositorySelector实例及LoggerRepository实例(Hierarchy)
  3. 调用OptionConverter.selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy)方法执行配置. (传递Hierarchy)
  4. 在OptionConverter中根据存在的配置文件类型创建Configurator实例(这里假设配置文件为log4j.properties), 执行Configurator的doConfigure(URL url, LoggerRepository repository) (传递Hierarchy)
  5. 在PropertyConfigurator完成所有组件配置.
  6. configureRootCategory(Properties props, LoggerRepository hierarchy) 配置根logger及其appdender等属性
  7. parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) 配置非根的logger及其appdender等属性, 渲染器等
  8. LogManager完成静态代码块后, 通过Hierarchy.getLogger(String name)方法获取出logger对象

入口:

	入口: Logger.getLogger(Class clazz) 或 Logger.getLogger(String name)
public class LogTest {
    public static void main(String[] args) {
    	// 获取logger对象
    	//Logger logger = Logger.getLogger(LogTest.class.getName());
        Logger logger = Logger.getLogger(LogTest.class);
        System.out.println(logger);
    }
}

进入Logger的Logger getLogger(Class clazz)方法内:

public class Logger extends Category {
  static public Logger getLogger(Class clazz) {
    // 通过LogManager获取logger对象, 此时jvm加载LogManager
    return LogManager.getLogger(clazz.getName());
  }
}

jvm加载LogManager

执行LogManager的static代码块

public class Logger extends Category {
    static {
        // 核心逻辑: 初始化repositorySelector, 并设置选择器的LoggerRepository为Hierarchy, 创建根logger
        Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);

        // 重写(过时)
        String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);
        if (override == null || "false".equalsIgnoreCase(override)) {
            String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
            URL url = null;

            // 核心逻辑: 先查找log4j.xml, 没有再查找log4j.properties文件
            if (configurationOptionStr == null) {
                url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
                if (url == null) {
                    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
                }
            } 

            if (url != null) {
                try {
                    // 核心逻辑: 读取配置, 完成logger初始化
                    OptionConverter.selectAndConfigure(url, configuratorClassName,
                            LogManager1.getLoggerRepository());
                } catch (NoClassDefFoundError e) {}
            } 
        }
    }
}

说明:

  1. 初始化repositorySelector, 并设置选择器的LoggerRepository为Hierarchy, 创建根logger
  2. 先查找log4j.xml, 没有再查找log4j.properties文件
  3. 读取配置, 完成logger初始化 OptionConverter.selectAndConfigure(…);

进入OptionConverter的selectAndConfigure(…)方法中

public class OptionConverter {
    static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
        Configurator configurator = null;
        String filename = url.getFile();

        // 若配置文件为log4j.xml, 则创建DOMConfigurator来解析
        if (clazz == null && filename != null && filename.endsWith(".xml")) {
            clazz = "org.apache.log4j.xml.DOMConfigurator";
        }

        if (clazz != null) {
            LogLog.debug("Preferred configurator class: " + clazz);
            configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null);
            if (configurator == null) {
                LogLog.error("Could not instantiate configurator [" + clazz + "].");
                return;
            }
        } else {
            // 否则, 则创建PropertyConfigurator来解析
            configurator = new PropertyConfigurator();
        }

        // 进行配置
        configurator.doConfigure(url, hierarchy);
    }
}

说明:
1.配置文件为log4j.xml, 则使用 DOMConfigurator来解析
2.配置文件为log4j.properties, 则使用PropertyConfigurator来解析

进入PropertyConfigurator的doConfigure(URL configURL, LoggerRepository hierarchy)方法

public class PropertyConfigurator implements Configurator {
	// 几个属性解释
	// registry注册器, 用于初始化过程中缓存 已完成初始化的appender, 初始化完成后会重置
    protected Hashtable registry = new Hashtable(11);  
    // logger仓库, 存储hierarchy对象
    private LoggerRepository repository;
    // logger工厂, 使用来创建自定义的logger对象
    protected LoggerFactory loggerFactory = new DefaultCategoryFactory();

    public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
        // 读取配置文件内容, 写入props中
        Properties props = new Properties();
        InputStream istream = null;
        URLConnection uConn = null;
        try {
            uConn = configURL.openConnection();
            uConn.setUseCaches(false);
            istream = uConn.getInputStream();
            props.load(istream);
        } catch (Exception e) {} finally {}
        
        // 继续进行配置
        doConfigure(props, hierarchy);
    }
}

进入PropertyConfigurator的doConfigure(Properties properties, LoggerRepository hierarchy)方法

    public void doConfigure(Properties properties, LoggerRepository hierarchy) {
        // 缓存hierarchy, 方便后续使用
        repository = hierarchy;

        // 判断配置中log4j.debug是否开启, 开启则后续会打印log4j项目内的debug级别的日志
        String value = properties.getProperty(LogLog.DEBUG_KEY);
        if (value == null) {
            value = properties.getProperty("log4j.configDebug");
        }
        if (value != null) {
            LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
        }

        // 判断配置中log4j.reset是否开启, 是则重置hierarchy
        String reset = properties.getProperty(RESET_KEY);
        if (reset != null && OptionConverter.toBoolean(reset, false)) {
            hierarchy.resetConfiguration();
        }

        // 获取配置中的log4j.threshold, 即全局的日志级别, 设置后低于该级别的日志都不能输出, 不设置则默认为ALL
        String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
        if (thresholdStr != null) {
            hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
        }

        // 核心代码: 配置根logger及其appender
        configureRootCategory(properties, hierarchy);
        // 配置LoggerFactory
        configureLoggerFactory(properties);
        // 核心代码: 配置自定义的logger及其appender, 渲染器等
        parseCatsAndRenderers(properties, hierarchy);

        LogLog.debug("Finished configuring.");
        // 去除registry缓存(该缓存作用: 缓存初始化过程中的appender), 以便不影响垃圾回收
        registry.clear();
    }

说明:
1. 对一些全局的属性进行配置, 如是否开启debug(log4j.debug), 是否重置(log4j.reset), 日志级别(log4j.threshold)
2. 执行核心代码: 配置根logger及其appender
3. 执行核心代码: 配置自定义的logger及其appender, 以及一些异常处理器等

进入PropertyConfigurator的configureRootCategory(…)方法中

    void configureRootCategory(Properties props, LoggerRepository hierarchy) {
        // 获取log4j.rootLogger的配置信息. 
        String effectiveFrefix = ROOT_LOGGER_PREFIX;
        // OptionConverter.findAndSubst(..)方法是获取指定key前缀的配置, 赋值到props中, 后续经常使用
        String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);

        if (value == null) {
            // 没配置log4j.rootLogger, 则获取log4j.rootCategory
            value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
            effectiveFrefix = ROOT_CATEGORY_PREFIX;
        }

        if (value == null){
            LogLog.debug("Could not find root logger information. Is this OK?");
        } else {
            // 获取出hierarchy中的根logger
            Logger root = hierarchy.getRootLogger();
            synchronized (root) {
                // 核心代码: 解析根logger 及绑定它的appender (后续自定义的loggger也是走这个逻辑)
                parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
            }
        }
    }

说明:
1. 获取log4j.rootLogger的配置信息, 进入parseCategory(…)方法中, 解析根logger 及绑定它的appender
2. 后续自定义的loggger也是走PropertyConfigurator的parseCategory(…)方法

进入PropertyConfigurator的parseCategory(…)方法

    void parseCategory(Properties props, Logger logger, String optionKey,
                       String loggerName, String value) {

        // 将logger的配置信息按照逗号分隔, 示例: log4j.rootLogger=info,console,rollingFile, 分隔后得到info 和 console及rollingFile
        StringTokenizer st = new StringTokenizer(value, ",");

        if (!(value.startsWith(",") || value.equals(""))) {
            if (!st.hasMoreTokens()){
                return;
            }

            // 获取当前logger的日志级别level. 第一个参数
            String levelStr = st.nextToken();

            // 非法的level值判断
            if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
                if (loggerName.equals(INTERNAL_ROOT_NAME)) {
                    LogLog.warn("The root logger cannot be set to null.");
                } else {
                    logger.setLevel(null);
                }
            } else {
                // 设置logger的level值, 不填则设置为DEBUG
                // (不填level这时第一个appenderName就被无效掉了, 示例log4j.rootLogger=console,rollingFile, 其中console就被丢弃了)
                logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
            }
            LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
        }

        // 移除所有的appender
        logger.removeAllAppenders();

        Appender appender;
        String appenderName;
        // 遍历log4j.rootLogger中的appenderName
        while (st.hasMoreTokens()) {
            appenderName = st.nextToken().trim();
            if (appenderName == null || appenderName.equals(",")){
                continue;
            }
            // 核心代码: 根据appenderName解析出appender
            appender = parseAppender(props, appenderName);
            if (appender != null) {
                // 将appender加入到 logger的AppenderAttachableImpl的appenderList中
                logger.addAppender(appender);
            }
        }
    }

说明:
1. 获取logger的配置, 设置日志级别level. (注: level值省略会导致第一个appdend丢失)
2. 执行核心代码: 根据appenderName解析出appender
3. 将appender加入到 logger的AppenderAttachableImpl的appenderList中

下面简单看一下logger.addAppender(appender);方法

// Category类的方法
  AppenderAttachableImpl aai;	
  synchronized public void addAppender(Appender newAppender) {
    if(aai == null) {
      aai = new AppenderAttachableImpl();
    }
    aai.addAppender(newAppender);
    repository.fireAddAppenderEvent(this, newAppender);
  }

// AppenderAttachableImpl 类的addAppender
public class AppenderAttachableImpl implements AppenderAttachable {
  protected Vector  appenderList;
  public void addAppender(Appender newAppender) {
    if(newAppender == null){
    	return;
    }
    if(appenderList == null) {
      appenderList = new Vector(1);
    }
    if(!appenderList.contains(newAppender))
      appenderList.addElement(newAppender);
  }
 }

进入PropertyConfigurator的parseAppender(…)方法中

    Appender parseAppender(Properties props, String appenderName) {
        // 根据appenderName获取registry缓存中的appender, 若存在则直接放回
        Appender appender = registryGet(appenderName);
        if ((appender != null)) {
            LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
            return appender;
        }

        // 注. 这里就写死了配置文件中layout只能是小写的.
        String prefix = "log4j.appender." + appenderName;
        String layoutPrefix = prefix + ".layout";

        // 通过配置文件中类的全限定名实例化appender对象. OptionConverter.instantiateByKey(....)该方法会经常使用到
        appender = (Appender) OptionConverter.instantiateByKey(props, prefix, Appender.class, null);
        if (appender == null) {
            LogLog.error("Could not instantiate appender named \"" + appenderName + "\".");
            return null;
        }
        // 设置appender的name
        appender.setName(appenderName);

        if (appender instanceof OptionHandler) {
            if (appender.requiresLayout()) {
                // 通过配置文件中类的全限定名实例化layout对象
                Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null);
                if (layout != null) {
                    // 设置appender的layout
                    appender.setLayout(layout);
                    LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
                    // 反射设置layout属性(例如PatternLayout的conversionPattern属性), 初始化解析器(代码位置: org.apache.log4j.helpers.PatternParser.parse). 详情见[补充: PatternLayout的setConversionPattern及模式初始化]
                    PropertySetter.setProperties(layout, props, layoutPrefix + ".");
                    LogLog.debug("End of parsing for \"" + appenderName + "\".");
                }
            }

            // 初始化异常处理器
            final String errorHandlerPrefix = prefix + ".errorhandler";
            String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
            if (errorHandlerClass != null) {
                ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
                        errorHandlerPrefix,
                        ErrorHandler.class,
                        null);
                if (eh != null) {
                    // 设置appender的errorHandler
                    appender.setErrorHandler(eh);
                    LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
                    parseErrorHandler(eh, errorHandlerPrefix, props, repository);
                    final Properties edited = new Properties();
                    final String[] keys = new String[]{
                            errorHandlerPrefix + "." + ROOT_REF,
                            errorHandlerPrefix + "." + LOGGER_REF,
                            errorHandlerPrefix + "." + APPENDER_REF_TAG
                    };
                    for (Iterator iter = props.entrySet().iterator(); iter.hasNext(); ) {
                        Map.Entry entry = (Map.Entry) iter.next();
                        int i = 0;
                        for (; i < keys.length; i++) {
                            if (keys[i].equals(entry.getKey())) {
                                break;
                            }
                        }
                        if (i == keys.length) {
                            edited.put(entry.getKey(), entry.getValue());
                        }
                    }
                    PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
                    LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
                }

            }
            // 反射设置appender的其他属性(如encoding、threshold、maxFileSize、datePattern). PropertySetter.setProperties(...)该方法会经常使用到
            PropertySetter.setProperties(appender, props, prefix + ".");
            LogLog.debug("Parsed \"" + appenderName + "\" options.");
        }
        // 解析appender的Filter. 使用较少, 这里不展开讲
        parseAppenderFilters(props, appenderName, appender);
        // 加入registry缓存中
        registryPut(appender);
        return appender;
    }

说明:
1.根据appenderName获取registry缓存中的appender, 若存在则直接放回(appender可能被其他logger就完成初始化了, )
2.初始化日志输出器appender对象
3.初始化布局layout对象, 并设置到appender中(配置文件的key中layout必须为小写)
4.初始化异常处理器errorHandler, 并设置到appender中
5.初始化过滤器filter, 并设置到appender中

补充: PatternLayout的setConversionPattern及模式初始化

public class PatternLayout extends Layout {
	// 输出日志内容的buffer
	private StringBuffer sbuf = new StringBuffer(BUF_SIZE);
	// 设置的模式, 默认值%m%n
    private String pattern;
	// 模式转换器, 链式结构
    private PatternConverter head;

   	// 实例化时
   	public PatternLayout() {
     	this("%m%n");
   	}

   	public PatternLayout(String pattern) {
     	this.pattern = pattern;
     	// 核心代码: parse()方法
     	head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse();
   	}
    
	// 如果有layout.conversionPattern配置, 初始化时, 反射调用该方法, 完成模式转换器链初始化
    public void setConversionPattern(String conversionPattern) {
        pattern = conversionPattern;
        // 核心代码: parse()方法
        head = createPatternParser(conversionPattern).parse();
    }

	// 创建PatternParser实例, 该类左右是用来初始化 模式转换器链PatternConverter head
	protected PatternParser createPatternParser(String pattern) {
        return new PatternParser(pattern);
    }
}

public class PatternParser {
	// 临时模式转换器头结点
  	PatternConverter head;
  	// 临时模式转换器尾结点
  	PatternConverter tail;
  	// 设置的模式值
  	protected String pattern;
  	// 模式解析. 使用状态机模式逐字符解析pattern属性. 这里代码非常多, 不展开讲, 直接说逻辑
    public PatternConverter parse() {
    	// 状态机模式, 需要进一步了解的请直接看源码, 下面贴出状态机的状态流转图
        char c;
        i = 0;
        while (i < patternLength) {
            c = pattern.charAt(i++);
            switch (state) {
                case LITERAL_STATE: ... break;
                case CONVERTER_STATE: ... break;
                case MIN_STATE: ... break;
                case DOT_STATE: ... break;
                case MAX_STATE: ... break;
            }
        }
		...
        return head;
    }
	
	protected void finalizeConverter(char c) {
		// 解析%后的字母, 如c, C, d, F, l, L等等
        PatternConverter pc = null;
        switch (c) {
            case 'c':
            	// 创建出PatternConverter的实现类CategoryPatternConverter
            	// extractPrecisionOption()方法用户获取pattern中{}内的值
                pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption());
                currentLiteral.setLength(0);
                break;
            //........
            case 'X':
                String xOpt = extractOption();
                // 创建出PatternConverter的实现类MDCPatternConverter. mdc可以用来做日志链路追踪
                // 链路追踪拓展: https://blog.csdn.net/Erica_java/article/details/128616137
                pc = new MDCPatternConverter(formattingInfo, xOpt);
                currentLiteral.setLength(0);
                break;
            default: ....;
        }
		// 将PatternConverter按照链式结果连接起来. 这里使用head和tail节点维护节点, 最终返回head到PatternLayout中
        addConverter(pc);
	}
}

状态机状态流转图
在这里插入图片描述

进入PropertyConfigurator的configureLoggerFactory(Properties props)方法中

    protected void configureLoggerFactory(Properties props) {
        // 获取配置文件loggerFactory全限定名, 创建loggerFactory实例
        String factoryClassName = OptionConverter.findAndSubst("log4j.loggerFactory", props);
        if (factoryClassName != null) {
            LogLog.debug("Setting category factory to [" + factoryClassName + "].");
            loggerFactory = (LoggerFactory) 
                    OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory);
            // 反射设置loggerFactory的属性
            PropertySetter.setProperties(loggerFactory, props, "log4j.factory" + ".");
        }
    }

说明:
1.获取配置文件loggerFactory全限定名, 创建loggerFactory实例
2.反射设置loggerFactory的属性
注: 不配置时, loggerFactory使用默认值, 为DefaultCategoryFactory实例对象. loggerFactory用于创建自定义的logger对象

进入PropertyConfigurator的parseCatsAndRenderers(…)方法中

    protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
        // 遍历Properties中所有的key
        Enumeration enumeration = props.propertyNames();
        while (enumeration.hasMoreElements()) {
            String key = (String) enumeration.nextElement();
            if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
                // 解析所有log4j.category.开头和log4j.logger.开头的key
                String loggerName = null;
                if (key.startsWith(CATEGORY_PREFIX)) {
                    loggerName = key.substring(CATEGORY_PREFIX.length());
                } else if (key.startsWith(LOGGER_PREFIX)) {
                    loggerName = key.substring(LOGGER_PREFIX.length());
                }
                String value = OptionConverter.findAndSubst(key, props);
                // 核心代码: 创建出自定义logger对象, 绑定其与其他logger的关系
                Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
                synchronized (logger) {
                    // 核心代码: 解析非根logger 及绑定它的appender(这里和根logger解析是同一个方法)
                    parseCategory(props, logger, key, loggerName, value);
                    // 解析additivity属性, 设置到logger中
                    parseAdditivityForLogger(props, logger, loggerName);
                }
            } else if (key.startsWith(RENDERER_PREFIX)) {
                // 解析所有log4j.renderer.开头的key, 初始化和绑定渲染器. 较少使用, 不展开讲
                String renderedClass = key.substring(RENDERER_PREFIX.length());
                String renderingClass = OptionConverter.findAndSubst(key, props);
                if (hierarchy instanceof RendererSupport) {
                    RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
                            renderingClass);
                }
            } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
                // 解析所有log4j.throwableRenderer开头的异常渲染器, 初始化和绑定异常渲染器. 较少使用, 不展开讲
                if (hierarchy instanceof ThrowableRendererSupport) {
                    ThrowableRenderer tr = (ThrowableRenderer)
                            OptionConverter.instantiateByKey(props, THROWABLE_RENDERER_PREFIX, ThrowableRenderer.class, null);
                    if (tr == null) {
                        LogLog.error("Could not instantiate throwableRenderer.");
                    } else {
                        PropertySetter setter = new PropertySetter(tr);
                        setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
                        ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
                    }
                }
            }
        }
    }

进入Hierarchy的getLogger(String name, LoggerFactory factory)方法中

    public Logger getLogger(String name, LoggerFactory factory) {
        CategoryKey key = new CategoryKey(name);
        Logger logger;
        synchronized (ht) {
            Object o = ht.get(key);
            if (o == null) {
                // 为空. 创建logger实例. 默认实现 Logger logger = new Logger(name)
                logger = factory.makeNewLoggerInstance(name);
                logger.setHierarchy(this);
                // 写入Hierarchy的ht(Hashtable)缓存中
                ht.put(key, logger);
                // 绑定当前logger和父logger
                updateParents(logger);
                return logger;
            } else if (o instanceof Logger) {
                // Logger类型. 直接返回
                return (Logger) o;
            } else if (o instanceof ProvisionNode) {
                // 临时节点类型. 先创建出该节点
                logger = factory.makeNewLoggerInstance(name);
                logger.setHierarchy(this);
                // 写入Hierarchy的ht(Hashtable)缓存中
                ht.put(key, logger);
                // 找到创建logger之前临时存储的子节点, 重新绑定(子logger->旧parent  =>  子logger->当前logger->旧parent)
                updateChildren((ProvisionNode) o, logger);
                // 绑定当前logger和父logger
                updateParents(logger);
                return logger;
            } else {
                return null;
            }
        }
    }

说明:
1.Hierarchy有个属性Hashtable ht, 用于缓存初始化时已创建的appender和临时节点ProvisionNode
临时节点ProvisionNode介绍: ht存储临时节点示例 com.clj -> [logger(com.clj.abc), logger(com.clj.def)]. 其中com.clj为待logger的name, 列表中元素为已实例化的logger(具体使用见Hierarchy.updateChildren(…)和Hierarchy.updateParents(.)方法)
2.若ht中未找到值, 则创建logger, 加入ht缓存, 绑定当前logger和父logger
3.若ht中找到值且为Logger类型, 直接返回
4.若ht中找到值且为ProvisionNode类型, 则创建logger, 加入ht缓存, 绑定当前logger和子logger, 绑定当前logger和父logger

进入Hierarchy的updateParents(Logger cat)方法中

    final private void updateParents(Logger cat) {
        // 示例logger的name = com.clj.abc, 假设初始化到当前只解析了 根logger和logger(com.clj.abc)
        String name = cat.name;
        int length = name.length();
        // 标识是否找到父类了
        boolean parentFound = false;

        // 这里遍历出com.clj 和 com
        for (int i = name.lastIndexOf('.', length - 1); i >= 0;
             i = name.lastIndexOf('.', i - 1)) {
            // 第一次遍历得到com.clj. 第二次遍历得到com
            String substr = name.substring(0, i);
            CategoryKey key = new CategoryKey(substr);
            Object o = ht.get(key);
            // Create a provision node for a future parent.
            if (o == null) {
                // ht中没缓存到, 则创建出临时节点, 临时节点存储的是当前的logger(com.clj.abc)
                ProvisionNode pn = new ProvisionNode(cat);
                // ht缓存临时节点. 第一次遍历(缓存了com.clj -> [logger(com.clj.abc)]), 第二次遍历(缓存了com -> [logger(com.clj.abc)])
                ht.put(key, pn);
            } else if (o instanceof Category) {
                // 若ht中得到是Category类型, 直接绑定当前logger的父节点, 修改parentFound标识
                parentFound = true;
                cat.parent = (Category) o;
                break;
            } else if (o instanceof ProvisionNode) {
                // 若ht中得到是ProvisionNode类型, 则将当前cat加入列表.
                // 这里假设之前o的列表为 [logger(com.clj.def)], 添加后列表为 [logger(com.clj.def), logger(com.clj.abc)],
                //  对应ht中的缓存键值对为com.clj -> [logger(com.clj.def), logger(com.clj.abc)]
                ((ProvisionNode) o).addElement(cat);
            } else {
                Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht.");
                e.printStackTrace();
            }
        }
        // 暂没找到父类, 那直接设置根logger为父类
        if (!parentFound) {
            cat.parent = root;
        }
    }

进入Hierarchy的updateChildren(ProvisionNode pn, Logger logger)方法中

    final private void updateChildren(ProvisionNode pn, Logger logger) {
        // 遍历临时节点中所有的子节点
        final int last = pn.size();
        for (int i = 0; i < last; i++) {
            Logger l = (Logger) pn.elementAt(i);
            // 若子节点的 原父logger 不是当前logger, 则设置子节点的 父节点为 当前logger, 设置当前logger的 父节点为 原父logger
            // 示例: 当前logger(com.clj), 子节点logger(com.clj.abc), 子节点的父logger为 root, 即 logger(com.clj.abc) -> logger(root)
            //       处理后为: logger(com.clj.abc) -> logger(com.clj) -> logger(root)
            if (!l.parent.name.startsWith(logger.name)) {
                logger.parent = l.parent;
                l.parent = logger;
            }
        }
    }

进入PropertyConfigurator的parseAdditivityForLogger(…)方法中

    void parseAdditivityForLogger(Properties props, Logger cat, String loggerName) {
        // 获取additivity属性(key为log4j.additivity.[loggerName])
        String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
        LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]");
        // 设置到logger的additive字段中(作用: 表示打印本logger日志后, 是否将日志添加到父级logger日志中处理, 默认值为true)
        if ((value != null) && (!value.equals(""))) {
            boolean additivity = OptionConverter.toBoolean(value, true);
            LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity);
            cat.setAdditivity(additivity);
        }
    }

说明:
1.获取additivity属性(key为log4j.additivity.[loggerName])
2.设置到logger的additive字段中
3.logger的additive字段作用: 打印本logger日志后, 是否将日志添加到父级logger日志中处理, 默认值为true

四. 执行日志打印

流程

在这里插入图片描述

说明:

  1. 从main方法出发, 这里示例是调用info()方法. (故后续都是判断info级别)
  2. 判断是否满足全局的level级别>=info, 满足则 再判断本logger的level级别是否>=info(本logger没有值则找其父类直至有值)
  3. 全局level或本体系的level有其一不满足>=info, 提前结束流程
  4. 执行本logger再递归父类logger, 执行其aai.appendLoopOnAppenders()方法 (注: 当前logger执行完会判断additive属性, 为false则不再递归父类, 该点在流程图第20步体现)
  5. 遍历当前logger的日志输出器appender, 执行其doAppend()方法
  6. 进入appender的公共父类AppenderSkeleton的doAppend()方法, 先判断当前appender的日志级别level是否>=info
  7. 当前appender日志级别level<info, 结束该appender的执行, 遍历下一个appender
  8. 递归执行本appender的过滤器执行链
  9. Filter中进行过滤判定
  10. 不满足过滤条件则结束该appender的执行, 遍历下一个appender
  11. 执行当前appender的append()方法. (这里假设实现类为RollingFileAppender)
  12. 进入appender的公共父类WriterAppender的append()方法, 执行子类RollingFileAppender的subAppend()方法
  13. 进入RollingFileAppender的subAppend()方法, 立即调用父类WriterAppender的subAppend()方法, 执行后再执行本类的滚动文件逻辑(该点在流程图第19步体现)
  14. 在WriterAppender的subAppend()方法中调用layout执行内容格式化
  15. layout完成输出内容格式化, 返回内容信息
  16. 调用QuietWriter将内容写入缓存
  17. 在WriterAppender中, 判断属性是否为立即输出, 是则调用QuietWriter写出内容
  18. QuietWriter将内容写出到文件中(若appender为ConsoleAppender则是输出到控制台)
  19. 回到RollingFileAppender的subAppend()方法中, 判断是否满足滚动文件逻辑, 是则执行rollOver()
  20. 遍历完当前logger后, 判断当前logger的additive属性, 为false则不再递归父类, 提前结束流程

入口

public class LogTest {
    public static void main(String[] args) {
    	Logger logger = Logger.getLogger(LogTest.class);
    	// 入口. 这里示例为输出info级别的日志
        logger.info("这是info级别的日志");
    }
}

进入Category的info(Object message)方法

    public void info(Object message) {
        // 判断全局日志级别是否 >= info, 不满足则退出.
        // (repository为初始化时 LogManager的repositorySelector属性的LoggerRepository属性 -> Hierarchy)
        if (repository.isDisabled(Level.INFO_INT)) {
            return;
        }
        // 获取本logger继承体系中有效的level, 判断是否满足日志级别是否 >= info, 不满足则退出.
        if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) {
        	// 核心代码: 输出日志
            forcedLog(FQCN, Level.INFO, message, null);
        }
    }

说明:

  1. 判断全局日志级别是否 >= info, 不满足则退出 (全局日志级别配置key为log4j.threshold, 存储在Hierarchy中)
  2. 获取本logger继承体系中有效的level, 判断是否满足日志级别是否 >= info, 不满足则退出.

进入Category的getEffectiveLevel()方法

    public Level getEffectiveLevel() {
        // 递归当前logger及其父类, 直到找到有效的level, 返回
        for (Category c = this; c != null; c = c.parent) {
            if (c.level != null) {
                return c.level;
            }
        }
        return null;
    }

进入Category的forcedLog(…)方法

    protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
        // 将要输出的基础日志内容,level,logger等封装到LoggingEvent中
        callAppenders(new LoggingEvent(fqcn, this, level, message, t));
    }

进入Category的callAppenders(LoggingEvent event)方法

    public void callAppenders(LoggingEvent event) {
        int writes = 0;

        // 递归本logger及其继承体系的logger
        for (Category c = this; c != null; c = c.parent) {
            synchronized (c) {
                // 核心代码: 获取logger属性AppenderAttachableImpl中的appenderList, 遍历执行appender
                if (c.aai != null) {
                    writes += c.aai.appendLoopOnAppenders(event);
                }
                // 若当前logger的additive为false, 则不再执行其父类logger, 结束日志输出
                if (!c.additive) {
                    break;
                }
            }
        }

        // 本logger及其继承体系中, 一个appender都没有找到, 输出warn的警告日志(项目启动后只会执行一次)
        if (writes == 0) {
            repository.emitNoAppenderWarning(this);
        }
    }

说明:

  1. 递归本logger及其继承体系的logger, 直至当前logger的additive属性为false
  2. 获取logger属性AppenderAttachableImpl中的appenderList, 遍历执行appender
  3. 若递归后一个appender都没有, 则输出warn的警告日志(项目启动后只能执行一次)

进入AppenderAttachableImpl的appendLoopOnAppenders(LoggingEvent event)方法中

    public int appendLoopOnAppenders(LoggingEvent event) {
        // 记录当前appender个数返回
        int size = 0;
        Appender appender;

        if (appenderList != null) {
            size = appenderList.size();
            // 核心代码: 遍历执行每个appender的doAppend()方法
            for (int i = 0; i < size; i++) {
                appender = (Appender) appenderList.elementAt(i);
                appender.doAppend(event);
            }
        }
        return size;
    }

进入AppenderSkeleton的doAppend(LoggingEvent event)方法中

注: 在这之后的示例使用Appender的实现类RollingFileAppender
注: 在这之后的示例使用Appender的实现类RollingFileAppender
注: 在这之后的示例使用Appender的实现类RollingFileAppender

    public synchronized void doAppend(LoggingEvent event) {
        if (closed) {
            LogLog.error("Attempted to append to closed appender named [" + name + "].");
            return;
        }

        // 判断当前appender的日志级别是否 >= 要输出的日志级别
        if (!isAsSevereAsThreshold(event.getLevel())) {
            return;
        }

        // 遍历过滤器链, 判断是否可以往下执行
        Filter f = this.headFilter;
        FILTER_LOOP:
        while (f != null) {
            switch (f.decide(event)) {
                case Filter.DENY:
                    return;
                case Filter.ACCEPT:
                    break FILTER_LOOP;
                case Filter.NEUTRAL:
                    f = f.getNext();
            }
        }

        // 执行子类append(LoggingEvent event)方法 (注: 在这之后的示例使用Appender的实现类RollingFileAppender)
        this.append(event);
    }

说明:

  1. 判断当前appender的日志级别是否 >= 要输出的日志级别, 不大于则退出
  2. 遍历过滤器链, 判断是否被拦截, 拦截则结束
  3. 执行子类append(LoggingEvent event)方法 (注: 在这之后的示例使用Appender的实现类RollingFileAppender)

进入WriterAppender的append(LoggingEvent event)方法中

注: 示例使用Appender的实现类RollingFileAppender

    public void append(LoggingEvent event) {
        // 判断QuietWriter和Layout是否为空, 为空则结束
        if (!checkEntryConditions()) {
            return;
        }
        // 核心代码: 进入RollingFileAppender的subAppend(LoggingEvent event)方法中
        subAppend(event);
    }

进入RollingFileAppender的subAppend(LoggingEvent event)方法中

    protected void subAppend(LoggingEvent event) {
    	// 核心代码: 执行父类WriterAppender的subAppend(LoggingEvent event)方法
    	super.subAppend(event);
	    if(fileName != null && qw != null) {
	    	// 超过文件最大size, 滚动文件
        	long size = ((CountingQuietWriter) qw).getCount();
        	if (size >= maxFileSize && size >= nextRollover) {
        		// 次核心代码: 滚动文件, 这里不展开讲. 
        		// 拓展: 若使用的appender为DailyRollingFileAppender, 需自动删除过期文件, 请查看[\[log4j配置详解及源码\]](https://blog.csdn.net/Java_dragon95/article/details/129953654)-[自定义DailyRollingFileAppender实现类, 自动删除过期日志]
            	rollOver();
        	}
    	}
    }

进入WriterAppender的subAppend(LoggingEvent event)方法中

	protected QuietWriter qw;
    protected void subAppend(LoggingEvent event) {
        // 核心代码: 格式化输出内容, 将格式化后的内容写入QuietWriter (write时出现异常会使用异常处理器errorHandler)
        // 假设Layout的实现类为PatternLayout
        this.qw.write(this.layout.format(event));

        // 处理的异常对象不包含在layout处理范围内, 则使用渲染器ThrowableRenderer渲染要输出的异常信息
        if (layout.ignoresThrowable()) {
            String[] s = event.getThrowableStrRep();
            if (s != null) {
                int len = s.length;
                for (int i = 0; i < len; i++) {
                    this.qw.write(s[i]);
                    this.qw.write(Layout.LINE_SEP);
                }
            }
        }
        // immediateFlush=true, 则立即写出到文件 (bufferedIO=true时, immediateFlush会被置为false)
        if (shouldFlush(event)) {
            this.qw.flush();
        }
    }

说明:

  1. 使用Layout格式化输出内容
  2. 使用QuietWriter写入格式化后的日志内容, write时出现异常会使用异常处理器errorHandler
  3. 处理的异常对象不包含在layout处理范围内, 则使用渲染器ThrowableRenderer渲染要输出的异常信息
  4. immediateFlush=true, 则立即写出到文件 (bufferedIO=true时, immediateFlush会被置为false, 这是要达到buffer大小才会写出)

进入PatternLayout的format(LoggingEvent event)方法

注: 上面已假设Layout的实现类为PatternLayout

    public String format(LoggingEvent event) {
        // 重置buffer容量 (因为有些大日志输出时会将buffer的容量变得很大)
        if (sbuf.capacity() > MAX_CAPACITY) {
            sbuf = new StringBuffer(BUF_SIZE);
        } else {
            sbuf.setLength(0);
        }

        // 遍历模式转化器链, 获取一个个模式转换器, 格式化拼接好日志内容
        PatternConverter c = head;
        while (c != null) {
        	// 按照具体的PatternConverter实现类, 格式化当前转化器负责的内容, 拼接到sbuf中. 
            c.format(sbuf, event);
            c = c.next;
        }
        // 返回最终的日志输出内容
        return sbuf.toString();
    }

进入父类PatternConverter的format(StringBuffer sbuf, LoggingEvent e)方法

注: 这里不列举所有子类重写的format方法

public abstract class PatternConverter {
    // 下一个模式转换器
    public PatternConverter next;
    // 小数点左侧的值
    int min = -1;
    // 小数点右侧的值
    int max = 0x7FFFFFFF;
    // 左对齐标志
    boolean leftAlign = false;
    /**
    * 转换出部分内容
    */
    abstract protected String convert(LoggingEvent event);

	public void format(StringBuffer sbuf, LoggingEvent e) {
		// 执行子类的convert方法
        String s = convert(e);
        if (s == null) {
            // 直接拼接min个空格
            if (0 < min) {
                spacePad(sbuf, min);
            }
            return;
        }

        int len = s.length();
        // 实际内容大于 阈值, 保留指定阈值长度右侧内容
        if (len > max) {
            sbuf.append(s.substring(len - max));
        } else if (len < min) {
            // 左对齐, 先拼接内容再拼接多余空格
            if (leftAlign) {
                sbuf.append(s);
                spacePad(sbuf, min - len);
            }
            // 右对齐, 先拼接多余空格再拼接内容
            else {
                spacePad(sbuf, min - len);
                sbuf.append(s);
            }
        } else {
            // 没有额外设置, 则直接拼接内容
            sbuf.append(s);
        }
    }

    static String[] SPACES = {" ", "  ", "    ", "        ", "                ", "                                "};
    /**
     * 有几个空格就拼接几个空格
     */
    public void spacePad(StringBuffer sbuf, int length) {
        while (length >= 32) {
            sbuf.append(SPACES[5]);
            length -= 32;
        }
        for (int i = 4; i >= 0; i--) {
            if ((length & (1 << i)) != 0) {
                sbuf.append(SPACES[i]);
            }
        }
    }
}

进入QuietWriter的write(String string)方法中

    public void write(String string) {
    	if (string != null) {
    		try {
    			// 写入日志
      			out.write(string);
    		} catch(Exception e) {
    			// 使用异常处理器处理异常. 该异常处理器就是appender初始化时appender内的异常处理器
      			errorHandler.error("Failed to write ["+string+"].", e, ErrorCode.WRITE_FAILURE);
	    	}
    	}
    }
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值