Log4j容器初始化探究
Log4j第一步就是初始化Logger容器Repository,这一章我们来探究Logger容器,从别从独立应用以及servlet容器下启动初始化两方面探究。
1 独立应用
静态初始化,java语言保证静态初始化只被执行一次,静态初始化源码在
LogManager中。
时序图:初始化流程:第一步:
LogManager获取配置文件的URL第二步:
OptionConverter获取Configurator实现类(配置类)第三步:
Configurator读取配置文件内容,配置Logger容器(默认配置Hierarchy)
1.1 LoggManager探究
LogManager获取配置文件的URL
源码://只在内部使用,将来版本将变为protected级别。static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";//将来版本变为private级别public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";//将来版本将变为private级别。用来指定在定义配置类static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";//将来版本将变为private级别。如果不为空并且不为`false`则直接跳过初始化阶段public static final String DEFAULT_INIT_OVERRIDE_KEY ="log4j.defaultInitOverride";static private Object guard = null;//Logger容器选择器static private RepositorySelector repositorySelector;static {//初始化Logger容器为Hierarchy。根节点是RootLogger,默认级别是DEBUGHierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));//初始化Logger容器选择器,以Hierarchy为Logger容器repositorySelector = new DefaultRepositorySelector(h);//获取系统属性log4j.defaultInitOverrideString override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);//如果没有设置log4j.defaultInitOverride,或者log4j.defaultInitOverride为false,进入初始化流程,否则跳过初始化if(override == null || "false".equalsIgnoreCase(override)) {//读取系统属性log4j.configurationString configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);//读取系统属性log4j.configuratorClassString configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);URL url = null;//如果不存在log4j.configurationif(configurationOptionStr == null) {//第一步先检查是否有log4j.xmlurl = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);//如果没有检查是否有log4j.propertiesif(url == null) {url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);}} else {try {url = new URL(configurationOptionStr);} catch (MalformedURLException ex) {url = Loader.getResource(configurationOptionStr); }}if(url != null) {LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");try {//如果存在url,则利用URL配置Logger容器OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());} catch (NoClassDefFoundError e) {LogLog.warn("Error during default initialization", e);}} else {LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");}} else {LogLog.debug("Default initialization of overridden by " +DEFAULT_INIT_OVERRIDE_KEY + "property."); }} 源码流程解析:1.初始化Logger容器Hierarchy,设置根节点为RootLogger2.初始LoggerRepositorySelector(容器选择器)为默认的
DefaultRepositorySelector,容器为
Hierarchy3.读取系统属性
log4j.defaultInitOverride,如果没有设置或者为
false进行初始化,否则跳过初始化4.读取系统属性
log4j.configuration(log4j文件路径配置),如果存在对应的文件,则得到URL.如果没有对应的文件,首先检查是否存在
log4j.xml文件,如果存在,得到Log4j配置文件URL,如果不存在
log4j.xml,继续检查是否存在
log4j.properties文件,如果存在该文件,得到log4j配置文件的URL,否则提示没有发现配置文件。5.读取系统属性
log4j.configuratorClass(自定义Configurator配置类全路径,一般不自定义)6.调用
OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository()),初始化logger容器
1.2 OptionConverter探究
OptionConverter获取Configurator实现类(配置类)
源码://利用给定URL配置Logger容器staticpublic void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); //优先检查使用xml文件,并查看是否有自定义的configuratorif(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 { configurator = new PropertyConfigurator(); } configurator.doConfigure(url, hierarchy);}源码流程解析:1.如果没有自定义配置类
Configurator并且文件的后缀名是xml.配置类设置为
org.apache.log4j.xml.DOMConfigurator2.如果自定义了配置类,根据配置类的全限定名,发射得到配置类实例3.上面两种情况都没有匹配成功,默认是
PropertyConfigurator配置类4.调用
configurator.doConfigure(url,hierarchy),根据配置文件URL,配置logger容器
Hierarchy(已经静态化构造了简单的容器,RootLogger是根节点)
1.3 Configurator探究(以ProptertyConfigurator为例)
Configurator读取配置文件内容,配置Logger容器
源码://从URL中读取配置文件,配置Logger容器Hierarchypublicvoid doConfigure(java.net.URL configURL, LoggerRepository hierarchy) { Properties props = new Properties(); LogLog.debug("Reading configuration from URL " + configURL); InputStream istream = null; URLConnection uConn = null; try { uConn = configURL.openConnection(); uConn.setUseCaches(false); istream = uConn.getInputStream(); props.load(istream); } catch (Exception e) { if (e instanceof InterruptedIOException || e instanceof InterruptedException) {Thread.currentThread().interrupt(); } LogLog.error("Could not read configuration file from URL [" + configURL + "].", e); LogLog.error("Ignoring configuration file [" + configURL +"]."); return; } finally { if (istream != null) { try { istream.close(); } catch(InterruptedIOException ignore) { Thread.currentThread().interrupt(); } catch(IOException ignore) { } catch(RuntimeException ignore) { } } } doConfigure(props, hierarchy);}源码流程解析:1.文件URL读取文件内容,赋值给Properties2.调用
doConfigure(properties,hierarchy)配置logger容器
源码:publicvoiddoConfigure(Properties properties, LoggerRepository hierarchy) {repository = hierarchy; String value = properties.getProperty(LogLog.DEBUG_KEY); if(value == null) { value = properties.getProperty("log4j.configDebug"); if(value != null)LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");} if(value != null) { LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); } // 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)); LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"]."); }configureRootCategory(properties, hierarchy); configureLoggerFactory(properties); parseCatsAndRenderers(properties, hierarchy); LogLog.debug("Finished configuring."); // We don"t want to hold references to appenders preventing their // garbage collection. registry.clear(); }voidconfigureRootCategory(Properties props, LoggerRepository hierarchy) {String effectiveFrefix = ROOT_LOGGER_PREFIX;String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);if(value == null) {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) {parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);}}}protectedvoidconfigureLoggerFactory(Properties props) {String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,props);if(factoryClassName != null) {LogLog.debug("Setting category factory to ["+factoryClassName+"].");loggerFactory = (LoggerFactory)OptionConverter.instantiateByClassName(factoryClassName,LoggerFactory.class,loggerFactory);PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");}}protectedvoidparseCatsAndRenderers(Properties props, LoggerRepository hierarchy) { Enumeration enumeration = props.propertyNames(); while(enumeration.hasMoreElements()) { String key = (String) enumeration.nextElement(); if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {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 = hierarchy.getLogger(loggerName, loggerFactory);synchronized(logger) { 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); } } } } }源码流程解析:1.获取
log4j.debug(log4j内部是否debug打印日志),如果为ture打印,false不打印。如果没有设置,尝试读取
log4j.configdebug(已经废弃,用logdebug取代)2.读取
log4j.reset,如果设置为true,重置logger容器3.读取
log4j.threshold,设置logger容器总阀值,低于阀值将不打印日志。如果没有配置,默认设置为最低级别
Level.ALL4.调用
configureRootCategory(Properties, LoggerRepository),
配置RootLogger.RootLogger级别不能设置为空或者
inherit.解析设置RootLogger的Appenders和Filters.5.调用
configureLoggerFactory(Properties props),
配置Logger工厂类LoggerFactory.6.调用
parseCatsAndRenderers(Properties, LoggerRepository),
配置Logger以及Renderer2 Web应用
最常用的就是与Spring集成,这里主要将和Spring集成以及启动流程.其实web应用初始化log4j流程就是,容器启动的时候,首先找到Log4j配置文件,然后调用log4j API进行log4j初始化配置(同上)
2.1 搭建web环境
第一步:加入依赖<properties><spring.version>4.2.4.RELEASE</spring.version><log4j.version>1.2.17</log4j.version></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.4</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><!--Log4j1 日志框架包--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency></dependencies>第二步:在web.xml中加入Log4jConfigListener <listener><listener-class>org.springframework.web.util.Log4jConfigListener</listener-class></listener>第三步:在resources文件夹下加入log4j.xml或者log4j.properties### 设置###log4j.rootLogger = debug,stdout,D,Elog4j.threshold= debug## log4j内部是否debuglog4j.debug= false### 配置自己的log工厂类log4j.loggerFactory=com.log.log4j.configure.MyLoggerFactory### 输出信息到控制抬 ###log4j.appender.stdout = org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target = System.outlog4j.appender.stdout.Threshold = warnlog4j.appender.stdout.layout = org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n### 输出DEBUG 级别以上的日志到=/data/applogs/log/logtopic/app.log ###log4j.appender.D = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File = /data/applogs/log/logtopic/app.loglog4j.appender.D.Append = truelog4j.appender.D.Threshold = DEBUGlog4j.appender.D.layout = org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ] - [ %p ]%m%n### 输出ERROR 级别以上的日志到=/data/applogs/log/log4jLearning/error.log ###log4j.appender.E = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.E.File =/data/applogs/log/logtopic/error.loglog4j.appender.E.Append = truelog4j.appender.E.Threshold = ERRORlog4j.appender.E.layout = org.apache.log4j.PatternLayoutlog4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ] - [ %p ]%m%n第四步:编写ServletDemo并配置ServletDemo代码:
public class Log4jServletDemo extends HttpServlet {public static final Logger LOGGER = Logger.getLogger(Log4jServletDemo.class);@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {service(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{service(req, resp);}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{LOGGER.debug("Log4jServletDemo Info Level");LOGGER.info("Log4jServletDemo Info Level");LOGGER.warn("Log4jServletDemo Info Level");LOGGER.error("Log4jServletDemo Info Level");req.getRequestDispatcher("/index.jsp").forward(req, resp);}}web.xml中配置:
<servlet><servlet-name>servletDemo</servlet-name><servlet-class>com.log.log4j.web.Log4jServletDemo</servlet-class></servlet><servlet-mapping><servlet-name>servletDemo</servlet-name><url-pattern>/demo</url-pattern></servlet-mapping>2.2 log4jweb初始化
初始化时序图:初始化流程:1.tomcat容器加载
Log4jConfigListener2.
Log4jConfigListener把初始化Log4j的工作为委托给
Log4jWebConfigurer3.
Log4jWebConfigurer获取配置文件路径。然后再委托给
Log4jConfigurer4.
Log4jConfigurer调用Log4j框架的
DomConfigurator.configure(url)或者
PropertyConfigurator.configure(url)初始化配置Log4j,这样就走到了上面独立应用初始化Log4j的过程
2.3 源码探究
2.3.1 Log4jConfigListener
源码:public class Log4jConfigListener implements ServletContextListener { @Override publicvoidcontextInitialized(ServletContextEvent event) {Log4jWebConfigurer.initLogging(event.getServletContext()); } @Override publicvoidcontextDestroyed(ServletContextEvent event) { Log4jWebConfigurer.shutdownLogging(event.getServletContext()); }}源码流程解析:1.调用
contextInitialized(ServletContextEvent)初始化Log4j2.委托给
Log4jWebConfigurer初始化Log4j
源码:public static void initLogging(ServletContext servletContext) { // 首先检查是否暴露系统属性,默认是暴露if (exposeWebAppRoot(servletContext)) { WebUtils.setWebAppRootSystemProperty(servletContext); } //得到自定义的log4j配置文件位置String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM); if (location != null) { // 主要是获取log4j配置文件的真实路径try { // Resolve property placeholders before potentially resolving a real path. location = ServletContextPropertyUtils.resolvePlaceholders(location, servletContext);// 判断是否是资源路径,以classpath:" or "file:"开头 if (!ResourceUtils.isUrl(location)) { // 获取配置文件的真实路径location = WebUtils.getRealPath(servletContext, location); } // Write log message to server log. servletContext.log("Initializing log4j from [" + location + "]"); // 读取 log4jRefreshInterval 属性String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM); if (StringUtils.hasText(intervalString)) { try { long refreshInterval = Long.parseLong(intervalString); //配置log4j并启动一个监控线程org.springframework.util.Log4jConfigurer.initLogging(location, refreshInterval); } catch (NumberFormatException ex) { throw new IllegalArgumentException("Invalid "log4jRefreshInterval" parameter: " + ex.getMessage()); } } else { //配置log4jorg.springframework.util.Log4jConfigurer.initLogging(location); } } catch (FileNotFoundException ex) { throw new IllegalArgumentException("Invalid "log4jConfigLocation" parameter: " + ex.getMessage()); } }}//设置WebAppRoot属性public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException { Assert.notNull(servletContext, "ServletContext must not be null");String root = servletContext.getRealPath("/");if (root == null) { throw new IllegalStateException( "Cannot set web app root system property when WAR file is not expanded"); }String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM);String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);String oldValue = System.getProperty(key);if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) { throw new IllegalStateException( "Web app root system property already set to different value: "" + key + "" = [" + oldValue + "] instead of [" + root + "] - " + "Choose unique values for the "webAppRootKey" context-param in your web.xml files!"); }System.setProperty(key, root);servletContext.log("Set web app root system property: "" + key + "" = [" + root + "]");}源码流程解析1.
exposeWebAppRoot判断是否暴露WebAppRoot,默认是暴露.可以自定义,如下配置
<context-param> <param-name>log4jExposeWebAppRoot</param-name> <param-value>true</param-value></context-param>2.如果暴露,将设置系统属性为
webapp.root =
servletContext.getRealPath("/")(项目部署根路径),也可以自定义
webAppRootKey,如下
<context-param><param-name>webAppRootKey</param-name><param-value>logtopic.root</param-value></context-param>这样就会设置系统属性
logtopic.root = servletContext.getRealPath("/"),再配置文件中就可以用
${logtopic.root}代替部署根路径3.
String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM)获取Log4j自定义配置路径,如果不为空解析得到真实路径
location = WebUtils.getRealPath(servletContext, location)如下配置
<context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j.properties</param-value></context-param>配置有两种情况- `classpath`开头,找到项目类路径,最后用ClassLoader加载,所以不要用"/"开头<context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j.properties</param-value></context-param><context-param> <param-name>log4jConfigLocation</param-name> <param-value>file:///Users/lh/Desktop/log4j.properties</param-value></context-param>4.读取
log4jRefreshInterval属性,表示每隔一段时间,会重新读取配置文件,重新配置Log4j,自动检测更新。会单独启动一个线程来监控定时监控,单位是(ms).配置如下:
<context-param> <param-name>log4jRefreshInterval</param-name> <param-value>2000</param-value></context-param>5.最后调用log4j自身的API进行配置
if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) { DOMConfigurator.configure(url);}else { PropertyConfigurator.configure(url);}更多Log4j相关教程见以下内容:Log4j配置详解 http://www.linuxidc.com/Linux/2014-10/108401.htm
Apache Log4j 2 更多内容请看: http://logging.apache.org/log4j/2.x/ Log4j入门使用教程 http://www.linuxidc.com/Linux/2013-06/85223.htmLog4j 日志详细用法 http://www.linuxidc.com/Linux/2014-09/107303.htmHibernate配置Log4j显示SQL参数 http://www.linuxidc.com/Linux/2013-03/81870.htmLog4j学习笔记(1)_Log4j 基础&配置项解析 http://www.linuxidc.com/Linux/2013-03/80586.htmLog4j学习笔记(2)_Log4j配置示例&Spring集成Log4j http://www.linuxidc.com/Linux/2013-03/80587.htm
Log4j 的详细介绍:请点这里
Log4j 的下载地址:请点这里
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-11/137083.htm