Log4j源码分析

本文详细分析了Log4J的源码,包括Logger、Appender、Layout等核心组件的结构和功能,以及日志记录器的管理仓库LoggerRepository。Log4J允许用户控制日志输出级别、格式和目的地,通过Appender实现不同输出方式,Layout则负责格式化日志信息。
摘要由CSDN通过智能技术生成

专栏文章参考:
Java Util Logger源码分析
Slf4j源码分析
Logback源码分析
Springboot Logger源码分析

Log4J 是 Apache 的一个开源项目(官网),通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、GUI 组件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。

组成

针对用户侧,Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件等);Layout 控制日志信息的输出格式。

架构

image.png
本文源码基于1.2.17

Logger UML结构

image.png
AppenderAttachable定义了管理Appender的方法,一个Logger中可以添加多个Appender

public interface AppenderAttachable {
    // 增
    public void addAppender(Appender newAppender);
    // 查
    public Enumeration getAllAppenders();
    public Appender getAppender(String name);
    public boolean isAttached(Appender appender);
    // 删
    void removeAllAppenders();
    void removeAppender(Appender appender);
    void removeAppender(String name);   
}

其唯一实现是AppenderAttachableImpl

public class AppenderAttachableImpl implements AppenderAttachable {
    // Appender 容器
    protected Vector  appenderList;

    public void addAppender(Appender newAppender) {
        // Null values for newAppender parameter are strictly forbidden.
        if(newAppender == null)
            return;
        if(appenderList == null) {
            appenderList = new Vector(1);
        }
        if(!appenderList.contains(newAppender))
            appenderList.addElement(newAppender);
    }
}

Category是早期JUL尚未发布之前的日志记录器名称,后期通过继承的方式统一名称为Logger

public class Category implements AppenderAttachable {

    // 记录器名称
    protected String   name;
    // 日志级别
    volatile protected Level level;
    // 父记录器
    volatile protected Category parent;
    // 国际化
    protected ResourceBundle resourceBundle;
    // 存放记录器的容器 Repository
    protected LoggerRepository repository;
    // 所有的Appender管理代理给AppenderAttachableImpl类
    AppenderAttachableImpl aai;
    // 是否继承父Logger的Appender,默认true
    protected boolean additive = true;

    protected Category(String name) {
        this.name = name;
    }
}
// logger
public class Logger extends Category {
    private static final String FQCN = Logger.class.getName();
    protected Logger(String name) {
        super(name);
    }
}
// root logger
public final class RootLogger extends Logger {
    public RootLogger(Level level) {
        super("root");
        setLevel(level);
    }
}
Appender UML结构

image.png
针对不同的日志输出目的地,log4j定义了多个Appender

public interface Appender {

    // 过滤器管理
    void addFilter(Filter newFilter);
    Filter getFilter();
    void clearFilters();
	// 格式化工具管理
    void setLayout(Layout layout);
    public Layout getLayout();
    public boolean requiresLayout();

    // 日志输出
    void doAppend(LoggingEvent event);

    void close();

    public void setName(String name);
    String getName();

    void setErrorHandler(ErrorHandler errorHandler);
    ErrorHandler getErrorHandler();
}
其基础实现AppenderSkeleton
public abstract class AppenderSkeleton implements Appender, OptionHandler {
    // 格式化工具
    protected Layout layout;
    // name
    protected String name;
    // 日志级别
    protected Priority threshold;

    protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();

    // 过滤器链
    protected Filter headFilter;
    protected Filter tailFilter;
    // 该Appender是否关闭
    protected boolean closed = false;
    
	// 日志输出模板方法
    public synchronized  void doAppend(LoggingEvent event) {
        if(closed) {
            LogLog.error("Attempted to append to closed appender named ["+name+"].");
            return;
        }
        // 日志级别检查
        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();
            }
        }
        // 输出(子类实现)
        this.append(event);    
    }
}
字符流输出WriterAppender
public class WriterAppender extends AppenderSkeleton {
    // 立即刷新
    protected boolean immediateFlush = true;
    // 编码方式
    protected String encoding;
    // Writer
    protected QuietWriter qw;
    
    // 输出
    public void append(LoggingEvent event) {
        // 参数检查
        if(!checkEntryConditions()) {
            return;
        }
        subAppend(event);
    }
    // outstream 输出
    protected void subAppend(LoggingEvent event) {
        this.qw.write(this.layout.format(event));

        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);
                }
            }
        }
		// 及时刷新
        if(shouldFlush(event)) {
            this.qw.flush();
        }
    }
}
Layout UML结构

image.png

Layout
public abstract class Layout implements OptionHandler {
    // Note that the line.separator property can be looked up even by
    // applets.
    public final static String LINE_SEP = System.getProperty("line.separator");
    public final static int LINE_SEP_LEN  = LINE_SEP.length();
	// 格式化
    abstract public String format(LoggingEvent event);

    public String getContentType() {
        return "text/plain";
    }
    public String getHeader() {
        return null;
    }
    public String getFooter() {
        return null;
    }
    abstract public boolean ignoresThrowable();
}

Layout主要定义了format()方法,日志输出时会将LogEvent对象交给Layout,其format()具体格式化为字符串用于输出记录或展示。

LogEvent日志信息
public class LoggingEvent implements java.io.Serializable {

    private static long startTime = System.currentTimeMillis();
    /** Fully qualified name of the calling category class. */
    transient public final String fqnOfCategoryClass;
	// 日志记录器Logger
    transient private Category logger;
	// LoggerName
    final public String categoryName;
	// 当前日志信息Level
    transient public Priority level;
    /** The nested diagnostic context (NDC) of logging event. */
    private String ndc;
    /** The mapped diagnostic context (MDC) of logging event. */
    private Hashtable mdcCopy;
    private boolean ndcLookupRequired = true;
    private boolean mdcCopyLookupRequired = true;

    // 日志信息 通常为String
    transient private Object message;
    private String renderedMessage;
	// 线程名
    private String threadName;
    private ThrowableInformation throwableInfo;
    public final long timeStamp;
    // 日志发生位置(一般不使用,及耗性能)
    private LocationInfo locationInfo;
    static final long serialVersionUID = -868428216207166145L;
    static final Integer[] PARAM_ARRAY = new Integer[1];
    static final String TO_LEVEL = "toLevel";
    static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class};
    static final Hashtable methodCache = new Hashtable(3); // use a tiny table
    
    public LoggingEvent(String fqnOfCategoryClass, Category logger,
                        long timeStamp, Priority level, Object message,
                        Throwable throwable) {
        this.fqnOfCategoryClass = fqnOfCategoryClass;
        this.logger = logger;
        this.categoryName = logger.getName();
        this.level = level;
        this.message = message;
        // 错误信息
        if(throwable != null) {
            this.throwableInfo = new ThrowableInformation(throwable, logger);
        }

        this.timeStamp = timeStamp;
    }
}
LoggerRepository Logger对象管理器

image.png
LoggerRepository

public interface LoggerRepository {
    
    // 记录器管理方法
    Logger getLogger(String name);
    Logger getLogger(String name, LoggerFactory factory);
    Logger getRootLogger();
    Logger exists(String name);
    Enumeration getCurrentLoggers();
    Enumeration getCurrentCategories();
	// 日志级别 低于该级别 日志不输出
    void setThreshold(Level level);
    void setThreshold(String val);
    Level getThreshold();
    
    void emitNoAppenderWarning(Category cat);
    void shutdown();
    void addHierarchyEventListener(HierarchyEventListener listener);
    boolean isDisabled(int level);
    void fireAddAppenderEvent(Category logger, Appender appender);
    void resetConfiguration();
}

LoggerRepository(记录器仓库)主要提供对记录器的管理,使用时直接从仓库中获取,不存在时记录器会自动创建一个日志记录器返回。以及定义仓库中日志记录器的最低日志级别。

Hierarchy - 具体实现
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
    // 创建Logger的工厂类
    private LoggerFactory defaultFactory;
    // 日志增删改查的监听器
    private Vector listeners;
    // 存放Logger的Map结构
    Hashtable ht;
    // 根日志对象,所有的Logger会在内存中构建为一个以root为根的树形结构
    Logger root;
    // 对象渲染格式化容器
    RendererMap rendererMap;
    // 日志输出级别
    int thresholdInt;
    Level threshold;
    // 没有Appender是否告警
    boolean emittedNoAppenderWarning = false;
    // 没有国际化资源是否告警
    boolean emittedNoResourceBundleWarning = false;
    private ThrowableRenderer throwableRenderer = null;

    public Hierarchy(Logger root) {
        // Logger容器
        ht = new Hashtable();
        // 监听器
        listeners = new Vector(1);
        // 根日志
        this.root = root;
        // Enable all level levels by default.
        // 默认输出所有日志
        setThreshold(Level.ALL);
        // 
        this.root.setHierarchy(this);
        rendererMap = new RendererMap();
        // 日志Logger创建工仓
        defaultFactory = new DefaultCategoryFactory();
    }

    // 从仓库中获取Logger
    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 = factory.makeNewLoggerInstance(name);
                logger.setHierarchy(this);
                // 放入容器中
                ht.put(key, logger);
                // 设置逻辑父记录器
                updateParents(logger);
                return logger;
            } else if(o instanceof Logger) {
                return (Logger) o;
            } else if (o instanceof ProvisionNode) {
                // 临时存放了子节点
                logger = factory.makeNewLoggerInstance(name);
                logger.setHierarchy(this);
                ht.put(key, logger);
                // 设置子节点
                updateChildren((ProvisionNode) o, logger);
                // 设置父节点
                updateParents(logger);
                return logger;
            }
            else {
                // It should be impossible to arrive here
                return null;  // but let's keep the compiler happy -_-.
            }
        }
    }

    // 更新父节点
    final private void updateParents(Logger cat) {
        String name = cat.name;
        int length = name.length();
        boolean parentFound = false;

        // x.y.z  依次往上找
        for(int i = name.lastIndexOf('.', length - 1); i >= 0;
            i = name.lastIndexOf('.', i - 1))  {
            String substr = name.substring(0, i);
            // 查找父节点
            CategoryKey key = new CategoryKey(substr); // simple constructor
            Object o = ht.get(key);
            if(o == null) {
                // Create a provision node for a future parent.
                ProvisionNode pn = new ProvisionNode(cat);
                ht.put(key, pn);
            } else if(o instanceof Category) {
                parentFound = true;
                cat.parent = (Category) o;
                break; // no need to update the ancestors of the closest ancestor
            } else if(o instanceof ProvisionNode) {
                ((ProvisionNode) o).addElement(cat);
            } else {
                Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht.");
                e.printStackTrace();
            }
        }
        // If we could not find any existing parents, then link with root.
        if(!parentFound) {
            // 父节点直接设置为根节点
            cat.parent = root;
        }
    }

    // 更新子节点的父节点
    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);
            // Unless this child already points to a correct (lower) parent,
            // make cat.parent point to l.parent and l.parent to cat.
            if(!l.parent.name.startsWith(logger.name)) {
                logger.parent = l.parent;
                l.parent = logger;
            }
        }
    }
}

Hierarchy的主要作用就是创建和存储Logger对象,同时管理Logger的层级关系,在内存中构建维护一个基于root为根的Logger树。

日志系统初始化 - 配置加载LoggerManager

LoggerManager是管理日志仓库,以及加载初始化日志配置文件的入口类,在其静态代码块中创建了一个仓库对象,同时加载配置文件到内存。

public class LogManager {

    static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";

    static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
    static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
    public static final String DEFAULT_INIT_OVERRIDE_KEY =  "log4j.defaultInitOverride";
    // 上面的在1.2.17.0版本都标注了过期,也就是说后期就支持xml
    static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  

    static private Object guard = null;
    static private RepositorySelector repositorySelector;

    static {
        // 静态代码块执行,默认使用Hierarchy 可继承性的仓库
        Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);

        // 类路径下加载
        String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);

        // if there is no default init override, then get the resource
        // specified by the user or the default config file.
        if(override == null || "false".equalsIgnoreCase(override)) {
            String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
            String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);

            URL url = null;

            // 首先使用xml,然后查找properties
            if(configurationOptionStr == null) {	
                url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
                if(url == null) {
                    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
                }
            } else {
                try {
                    url = new URL(configurationOptionStr);
                } catch (MalformedURLException ex) {
                    // so, resource is not a URL:
                    // attempt to get the resource from the class path
                    url = Loader.getResource(configurationOptionStr); 
                }	
            }
            if(url != null) {
                LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
                try {
                    //  读取配置文件
                    OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository());
                } catch (NoClassDefFoundError e) {
                    LogLog.warn("Error during default initialization", e);
                }
            }
        }
    }
}

这里主要创建了一个仓库对象Hierarchy,然后根据属性加载配置文件,如log4j.properties.
继续看如何加载配置文件OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository());

// OptionConverter中
static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
    // 根据配置文件类型确认
    Configurator configurator = null;
    String filename = url.getFile();
	// xml
    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 {
        // properties类型文件解析
        configurator = new PropertyConfigurator();
    }
	// 解析配置文件,并设置内容到仓库
    configurator.doConfigure(url, hierarchy);
}

这里确认配置文件类型,然后使用对应的解析工具解析配置文件
以Porperty文件为例

public class PropertyConfigurator implements Configurator {
    // 解析配置
    public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
        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) {
            return;
        }
        // 省略资源关闭代码....
        doConfigure(props, hierarchy);
    }

    public void doConfigure(Properties properties, LoggerRepository hierarchy) {
        //
        //   if log4j.reset=true then reset hierarchy
        String reset = properties.getProperty(RESET_KEY);
        if (reset != null && OptionConverter.toBoolean(reset, false)) {
            hierarchy.resetConfiguration();
        }

        // 配置日志级别
        String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
        if(thresholdStr != null) {
            hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
        }
        // 解析配置 如:root的配置
        configureRootCategory(properties, hierarchy);
        // 解析工厂类
        configureLoggerFactory(properties);
        // 解析渲染工具
        parseCatsAndRenderers(properties, hierarchy);

        registry.clear();
    }

    // 定义的常量属性
    static final String      CATEGORY_PREFIX = "log4j.category.";
    static final String      LOGGER_PREFIX   = "log4j.logger.";
    static final String       FACTORY_PREFIX = "log4j.factory";
    static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
    static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
    static final String      APPENDER_PREFIX = "log4j.appender.";
    static final String      RENDERER_PREFIX = "log4j.renderer.";
    static final String      THRESHOLD_PREFIX = "log4j.threshold";
    private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
    private static final String LOGGER_REF	= "logger-ref";
    private static final String ROOT_REF		= "root-ref";
    private static final String APPENDER_REF_TAG 	= "appender-ref";  
    public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";

    private static final String RESET_KEY = "log4j.reset";

    static final private String INTERNAL_ROOT_NAME = "root";
    // 配置root日志记录器
    void configureRootCategory(Properties props, LoggerRepository hierarchy) {
        String effectiveFrefix = ROOT_LOGGER_PREFIX;// log4j.rootLogger
        String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);

        if(value == null) {
            // ROOT_CATEGORY_PREFIX = 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 {
            Logger root = hierarchy.getRootLogger();
            synchronized(root) {
                // INTERNAL_ROOT_NAME = root
                parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
            }
        }
    }
	// 根据具体LoggerName解析Appender,Laylout等
    void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) {
        StringTokenizer st = new StringTokenizer(value, ",");

        // If value is not in the form ", appender.." or "", then we should set
        // the level of the loggeregory.

        if(!(value.startsWith(",") || value.equals(""))) {
            if(!st.hasMoreTokens())
                return;
            // “,”隔开的第一个字符串是日志级别
            String levelStr = st.nextToken();
            LogLog.debug("Level token is [" + levelStr + "].");
            // 省略.....
        }

        // Begin by removing all existing appenders.
        logger.removeAllAppenders();

        Appender appender;
        String appenderName;
        // 后面所有的都当做appender
        while(st.hasMoreTokens()) {
            appenderName = st.nextToken().trim();
            if(appenderName == null || appenderName.equals(","))
                continue;
            // parse appender
            appender = parseAppender(props, appenderName);
            if(appender != null) {
                logger.addAppender(appender);
            }
        }
    }

    // 配置文件Appender
    Appender parseAppender(Properties props, String appenderName) {
        Appender appender = registryGet(appenderName);
        if((appender != null)) {
            return appender;
        }
        // APPENDER_PREFIX=log4j.appender.
        String prefix = APPENDER_PREFIX + appenderName;
        String layoutPrefix = prefix + ".layout";

        appender = (Appender) OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null);
        if(appender == null) {
            LogLog.error("Could not instantiate appender named \"" + appenderName+"\".");
            return null;
        }
        appender.setName(appenderName);
        // 解析Laylout
        if(appender instanceof OptionHandler) {
            if(appender.requiresLayout()) {
                Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null);
                if(layout != null) {
                    appender.setLayout(layout);
                    PropertySetter.setProperties(layout, props, layoutPrefix + ".");
                }
            }
            // 该Appender的其他属性
            PropertySetter.setProperties(appender, props, prefix + ".");
        }
        // 解析过滤器
        parseAppenderFilters(props, appenderName, appender);
        registryPut(appender);
        return appender;
    }

    // 解析其他Logger配置
    protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
        Enumeration enumeration = props.propertyNames();
        while(enumeration.hasMoreElements()) {
            String key = (String) enumeration.nextElement();
            // 其他logger
            if(key.startsWith("log4j.category.") || key.startsWith("log4j.logger.")) {
                String loggerName = null;
                if(key.startsWith("log4j.category.")) {
                    loggerName = key.substring("log4j.category.".length());
                } else if(key.startsWith("log4j.logger.")) {
                    loggerName = key.substring("log4j.logger.".length());
                }
                // 获取该key对应的value值,并解析占位符${}
                String value =  OptionConverter.findAndSubst(key, props);
                Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
                synchronized(logger) {
                    // 设置该Logger的Appender等
                    parseCategory(props, logger, key, loggerName, value);
                    // 设置
                    parseAdditivityForLogger(props, logger, loggerName);
                }
            } else if(key.startsWith(RENDERER_PREFIX)) {
                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)) {
                if (hierarchy instanceof ThrowableRendererSupport) {
                    ThrowableRenderer tr = (ThrowableRenderer)
                        OptionConverter.instantiateByKey(props,
                                                         THROWABLE_RENDERER_PREFIX,
                                                         org.apache.log4j.spi.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);

                    }
                }
            }
        }
    }
	// 解析是否继承父Logger的属性
    void parseAdditivityForLogger(Properties props, Logger cat, String loggerName) {
        String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
        LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
        // touch additivity only if necessary
        if((value != null) && (!value.equals(""))) {
            boolean additivity = OptionConverter.toBoolean(value, true);
            cat.setAdditivity(additivity);
        }
    }
}

该类主要解析配置文件,对于自定义的Logger也会提前初始化到仓库中(提前解析其配置属性)。
至此,Logger的配置加载就完成了。

整个日志输出流程

image.png

总结

  1. Log4j作为java领域最早的日志工具,其出现填补了行业空白,也为JUL的出现提供了参考,JUL的设计大多参考了Log4j的思路。
  2. Logger作为日志记录器,其定义了日志级别,绑定了多个Appender,所有的Logger都交给LoggerRepository管理,在内存中所有的Logger构建成以RootLogger为根节点的单根树形结构,通过RootLogger能方便的找到所有的Logger。
  3. 每个Logger可关联多个Appender,Appender作为日志输出的目的地处理器,对应于JUL的Handler,可以输出到如控制台,文件,网络等地方,其关联了对应的Layout,用于格式化日志输出。
  4. LogEvent封装了日志信息,如日志内容,日志发生位置,发生时间,当前日志所属级别等等。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值