歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Log4j容器初始化探究

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,默認級別是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."); 

    }  

} 

源碼流程解析:

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容器

1.2 OptionConverter探究

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是根節點)

1.3 Configurator探究(以ProptertyConfigurator為例)

Configurator讀取配置文件內容,配置Logger容器

1.3.1 doConfigure(URL,LoggerRepository)

源碼:


//從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容器

1.3.2 doConfigure(Properties , LoggerRepository)

源碼:


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.ALL

4.調用configureRootCategory\(Properties, LoggerRepository\),配置RootLogger.RootLogger級別不能設置為空或者inherit.解析設置RootLogger的Appenders和Filters.

5.調用configureLoggerFactory(Properties props),配置Logger工廠類LoggerFactory.

6.調用parseCatsAndRenderers(Properties, LoggerRepository),配置Logger以及Renderer

2 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,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>
2.2 log4jweb初始化

初始化時序圖:

初始化流程:

1.tomcat容器加載Log4jConfigListener

2.Log4jConfigListener把初始化Log4j的工作為委托給Log4jWebConfigurer

3.Log4jWebConfigurer獲取配置文件路徑。然後再委托給Log4jConfigurer

4.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)初始化Log4j

2.委托給Log4jWebConfigurer初始化Log4j

2.3.2 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);

}

更多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.htm

Log4j 日志詳細用法 http://www.linuxidc.com/Linux/2014-09/107303.htm

Hibernate配置Log4j顯示SQL參數 http://www.linuxidc.com/Linux/2013-03/81870.htm

Log4j學習筆記(1)_Log4j 基礎&配置項解析 http://www.linuxidc.com/Linux/2013-03/80586.htm

Log4j學習筆記(2)_Log4j配置示例&Spring集成Log4j http://www.linuxidc.com/Linux/2013-03/80587.htm

Log4j 的詳細介紹:請點這裡
Log4j 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved