Log4j第一步就是初始化Logger容器Repository,這一章咱們來探究Logger容器,從別從獨立應用以及servlet容器下啓動初始化兩方面探究。java
靜態初始化,java語言保證靜態初始化只被執行一次,靜態初始化源碼在LogManager
中。web
時序圖:spring
初始化流程:apache
第一步: LogManager
獲取配置文件的URLapi
第二步: OptionConverter
獲取Configurator實現類(配置類)tomcat
第三步: Configurator
讀取配置文件內容,配置Logger容器(默認配置Hierarchy)mvc
LogManager
獲取配置文件的URLapp
源碼:框架
//只在內部使用,未來版本將變爲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,默認級別是DEBUG Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //初始化Logger容器選擇器,以Hierarchy爲Logger容器 repositorySelector = new DefaultRepositorySelector(h); //獲取系統屬性log4j.defaultInitOverride String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null); //若是沒有設置log4j.defaultInitOverride,或者log4j.defaultInitOverride爲false,進入初始化流程,不然跳過初始化 if(override == null || "false".equalsIgnoreCase(override)) { //讀取系統屬性log4j.configuration String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null); //讀取系統屬性log4j.configuratorClass String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null); URL url = null; //若是不存在log4j.configuration if(configurationOptionStr == null) { //第一步先檢查是否有log4j.xml url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); //若是沒有檢查是否有log4j.properties if(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."); } }
源碼流程解析:webapp
1.初始化Logger容器Hierarchy,設置根節點爲RootLogger
2.初始LoggerRepositorySelector(容器選擇器)爲默認的DefaultRepositorySelector
,容器爲Hierarchy
3.讀取系統屬性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容器
OptionConverter
獲取Configurator實現類(配置類)
源碼:
//利用給定URL配置Logger容器 static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); //優先檢查使用xml文件,並查看是否有自定義的configurator 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 { configurator = new PropertyConfigurator(); } configurator.doConfigure(url, hierarchy); }
源碼流程解析:
1.若是沒有自定義配置類Configurator
而且文件的後綴名是xml.配置類設置爲org.apache.log4j.xml.DOMConfigurator
2.若是自定義了配置類,根據配置類的全限定名,發射獲得配置類實例
3.上面兩種狀況都沒有匹配成功,默認是PropertyConfigurator
配置類
4.調用configurator.doConfigure(url,hierarchy)
,根據配置文件URL,配置logger容器Hierarchy
(已經靜態化構造了簡單的容器,RootLogger是根節點)
Configurator讀取配置文件內容,配置Logger容器
源碼:
//從URL中讀取配置文件,配置Logger容器Hierarchy publicvoid 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讀取文件內容,賦值給Properties
2.調用doConfigure(properties,hierarchy)
配置logger容器
源碼:
public void doConfigure(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(); } void configureRootCategory(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); } } } protected void configureLoggerFactory(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 + "."); } } protected void parseCatsAndRenderers(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.ALL
4.調用configureRootCategory\(Properties, LoggerRepository\)
,配置RootLogger.RootLogger級別不能設置爲空或者inherit
.解析設置RootLogger的Appenders和Filters.
5.調用configureLoggerFactory(Properties props)
,配置Logger工廠類LoggerFactory.
6.調用parseCatsAndRenderers(Properties, LoggerRepository)
,配置Logger以及Renderer
最經常使用的就是與Spring集成,這裏主要將和Spring集成以及啓動流程.其實web應用初始化log4j流程就是,容器啓動的時候,首先找到Log4j配置文件,而後調用log4j API進行log4j初始化配置(同上)
第一步:加入依賴
<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,E log4j.threshold= debug ## log4j內部是否debug log4j.debug= false ### 配置本身的log工廠類 log4j.loggerFactory=com.log.log4j.configure.MyLoggerFactory ### 輸出信息到控制擡 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.Threshold = warn log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.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.DailyRollingFileAppender log4j.appender.D.File = /data/applogs/log/logtopic/app.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.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.DailyRollingFileAppender log4j.appender.E.File =/data/applogs/log/logtopic/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.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); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { service(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ service(req, resp); } @Override protected 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>
初始化時序圖:
初始化流程:
1.tomcat容器加載Log4jConfigListener
2.Log4jConfigListener
把初始化Log4j的工做爲委託給Log4jWebConfigurer
3.Log4jWebConfigurer
獲取配置文件路徑。而後再委託給Log4jConfigurer
4.Log4jConfigurer
調用Log4j框架的DomConfigurator.configure(url)
或者PropertyConfigurator.configure(url)
初始化配置Log4j,這樣就走到了上面獨立應用初始化Log4j的過程
Log4jConfigListener
源碼:
public class Log4jConfigListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent event) { Log4jWebConfigurer.initLogging(event.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent event) { Log4jWebConfigurer.shutdownLogging(event.getServletContext()); } }
源碼流程解析:
1.調用contextInitialized(ServletContextEvent)
初始化Log4j
2.委託給Log4jWebConfigurer
初始化Log4j
Log4jWebConfigurer
源碼:
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 { //配置log4j org.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>
file開頭
,配置文件具體位置<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); }