Log4j第一步就是初始化Logger容器Repository,這一章我們來探究Logger容器,從別從獨立應用以及servlet容器下啟動初始化兩方面探究。
靜態初始化,java語言保證靜態初始化只被執行一次,靜態初始化源碼在LogManager
中。
時序圖:
初始化流程:
第一步: LogManager
獲取配置文件的URL
第二步: OptionConverter
獲取Configurator實現類(配置類)
第三步: Configurator
讀取配置文件內容,配置Logger容器(默認配置Hierarchy)
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容器
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容器
源碼:
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
最常用的就是與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
publicvoidcontextInitialized(ServletContextEvent event) {
Log4jWebConfigurer.initLogging(event.getServletContext());
}
@Override
publicvoidcontextDestroyed(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);
}
更多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 的下載地址:請點這裡