在没有设置自动加载log4j.xml的时候,一般我们需要这么处理
static void initLogger()
{
System.out.println("configurating log4j with log4j xml");
DOMConfigurator.configure("log4j.xml");
logger = Logger.getLogger("name");
}
此时如果在eclipse中,log4j.xml是与src工程目录并列的位置;
如果工程编译成jar包,log4j.xml也是jar包在同一目录下使用,不会包含在jar包当中;
我们其实还可以将log4j的读取过程通过log4j的自动加载机制完成,省去第一行代码
DOMConfigurator.configure("log4j.xml");;
我们只需要把log4j.xml的位置挪动下,挪动到src代码目录里层即可;
下面介绍下一般方式下的加载原理:
org.apache.log4j.xml.DOMConfigurator.java源码如下
/**A static version of {@link #doConfigure(String, LoggerRepository)}.*/
static
public
void configure(String filename) throws FactoryConfigurationError
{
new DOMConfigurator().doConfigure(filename,
LogManager.getLoggerRepository());
}
public
void doConfigure(final String filename, LoggerRepository repository)
{
ParseAction action = new ParseAction()
{
public Document parse(final DocumentBuilder parser)
throws SAXException, IOException
{
return parser.parse(new File(filename));
}
public String toString()
{
return "file [" + filename + "]";
}
};
doConfigure(action, repository);
}
private final void doConfigure(final ParseAction action,
final LoggerRepository repository)
throws FactoryConfigurationError
{
DocumentBuilderFactory dbf = null;
this.repository = repository;
try {
LogLog.debug("System property is :"+
OptionConverter.getSystemProperty(dbfKey, null));
dbf = DocumentBuilderFactory.newInstance();
LogLog.debug("Standard DocumentBuilderFactory search succeded.");
LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
} catch(FactoryConfigurationError fce) {
Exception e = fce.getException();
LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
throw fce;
}
try {
dbf.setValidating(true);
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
docBuilder.setErrorHandler(new SAXErrorHandler());
docBuilder.setEntityResolver(new Log4jEntityResolver());
Document doc = action.parse(docBuilder);
parse(doc.getDocumentElement());
} catch (Exception e) {
if (e instanceof InterruptedException ||
e instanceof InterruptedIOException)
{
Thread.currentThread().interrupt();
}
// I know this is miserable... LogLog.error("Could not parse "+ action.toString() + ".", e);
}
}
这样就开始了log4j.xml的解析过程,具体过程不表,那是另外一个问题;
我们注意到:
LogManager.getLoggerRepository()返回的
org.apache.log4j.spi.LoggerRepository repository这个对象对象
org.apache.log4j.spi.LoggerRepository 接口如下,顾名思义,就是存放Logger的仓库
package org.apache.log4j.spi;
import java.util.Enumeration;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
/**A LoggerRepository
is used to create and retrieveLoggers
. The relation between loggers in a repositorydepends on the repository but typically loggers are arranged in anamed hierarchy.
In addition to the creational methods, aLoggerRepository
can be queried for existing loggers,can act as a point of registry for events related to loggers.@author Ceki Gülcü@since 1.2 */
public interface LoggerRepository {
/**Add a {@link HierarchyEventListener} event to the repository.*/
public
void addHierarchyEventListener(HierarchyEventListener listener);
/**Returns whether this repository is disabled for a givenlevel. The answer depends on the repository threshold and thelevel
parameter. See also {@link #setThreshold}method. */
boolean isDisabled(int level);
/**Set the repository-wide threshold. All logging requests below thethreshold are immediately dropped. By default, the threshold isset to Level.ALL
which has the lowest possible rank. */
public
void setThreshold(Level level);
/**Another form of {@link #setThreshold(Level)} accepting a stringparameter instead of a Level
. */
public
void setThreshold(String val);
public
void emitNoAppenderWarning(Category cat);
/**Get the repository-wide threshold. See {@link#setThreshold(Level)} for an explanation. */
public
Level getThreshold();
public
Logger getLogger(String name);
public
Logger getLogger(String name, LoggerFactory factory);
public
Logger getRootLogger();
public
abstract
Logger exists(String name);
public
abstract
void shutdown();
public
Enumeration getCurrentLoggers();
/**Deprecated. Please use {@link #getCurrentLoggers} instead. */
public
Enumeration getCurrentCategories();
public
abstract
void fireAddAppenderEvent(Category logger, Appender appender);
public
abstract
void resetConfiguration();
}
而在org.apache.log4j.LogManager类当中
static public LoggerRepository getLoggerRepository()
{
if (repositorySelector == null)
{
repositorySelector = new DefaultRepositorySelector
(new NOPLoggerRepository());
guard = null;
Exception ex = new IllegalStateException("Class invariant violation");
String msg =
"log4j called after unloading, seehttp://logging.apache.org/log4j/1.2/faq.html#unload.";
if (isLikelySafeScenario(ex))
{
LogLog.debug(msg, ex);
}
else
{
LogLog.error(msg, ex);
}
}
return repositorySelector.getLoggerRepository();
}
而LogManager中的org.apache.log4j.RepositorySelector对象执行到这里到底是不是null呢?
事实上并不是null
首先看下两个static的常量字符串的定义
static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
public static final String DEFAULT_INIT_OVERRIDEE_KEY=
"log4j.defaultInitOverride"
可以看下LogManager的static代码块
static {
// By default we use a DefaultRepositorySelector which always returns 'h'. Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
/** Search for the properties file log4j.properties in the CLASSPATH. */
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;
// if the user has not specified the log4j.configuration // property, we search first for the file "log4j.xml" and then // "log4j.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 we have a non-null url, then delegate the rest of the // configuration to the OptionConverter.selectAndConfigure // method. 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);
}
} else {
LogLog.debug("Could not find resource:["+configurationOptionStr+"].");
}
} else {
LogLog.debug("Default initialization of overridden by " +
DEFAULT_INIT_OVERRIDE_KEY + "property.");
}
}
我们知道在加载类的过程中首先会执行该类的static变量的初始化以及static代码块逻辑;
// By default we use a DefaultRepositorySelector which always returns 'h'.Hierarchy h =newHierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
Hierarchy类是RepositorySelector类的实现,
应为意思为层级的意思,也正符合Logger这种树形结构关系;
这样其实LogManager中的repositorySelector对象已经被初始化完毕了;
至此我们印证了LogManager.getLoggerRepository()中的返回值在static代码执行的时候就被初始化了;
后面的代码其实就是我们上面提到的自动加载机制;
首先我们来看下org.apache.log4j.xml.helpers.OptionConverter类的getSystemProperty()方法
/**Very similar to System.getProperty
exceptthat the {@link SecurityException} is hidden.@param key The key to search for.@param def The default value to return.@return the string value of the system property, or the defaultvalue if there is no property with that key.@since 1.1 */
public
static
String getSystemProperty(String key, String def) {
try {
return System.getProperty(key, def);
} catch(Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx LogLog.debug("Was not allowed to read system property \""+key+"\".");
return def;
}
}
自然我们没有设置这个值,所以就从默认的路径去寻找我们希望自动加载的log4j.xml的配置文件
自动加载机制会自动去Class路径上寻找log4j.xml log4j.properties文件
利用类加载的ClassLoader来寻找资源
所以
if(override == null || "false".equalsIgnoreCase(override))
后面再次进入if条件语句
if(configurationOptionStr == null)
if(configurationOptionStr == null)
{
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null)
{
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
}
截图中的找不到资源的日志也是上面的Loader类中的getResource代码逻辑;
注意:这个log日志默认是不打印的,需要提前设置
System.setProperty("log4j.debug","true");
我们来看下org.apache.log4j.helpers.Loader类中的getResource方法
static public URL getResource(String resource) {
ClassLoader classLoader = null;
URL url = null;
try {
if(!java1 && !ignoreTCL) {
classLoader = getTCL();
if(classLoader != null) {
LogLog.debug("Trying to find ["+resource+"] using context classloader "
+classLoader+".");
url = classLoader.getResource(resource);
if(url != null) {
return url;
}
}
}
// We could not find resource. Ler us now try with the // classloader that loaded this class. classLoader = Loader.class.getClassLoader();
if(classLoader != null) {
LogLog.debug("Trying to find ["+resource+"] using "+classLoader
+" class loader.");
url = classLoader.getResource(resource);
if(url != null) {
return url;
}
}
} catch(IllegalAccessException t) {
LogLog.warn(TSTR, t);
} catch(InvocationTargetException t) {
if (t.getTargetException() instanceof InterruptedException
|| t.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.warn(TSTR, t);
} catch(Throwable t) {
// // can't be InterruptedException or InterruptedIOException // since not declared, must be error or RuntimeError. LogLog.warn(TSTR, t);
}
// Last ditch attempt: get the resource from the class path. It // may be the case that clazz was loaded by the Extentsion class // loader which the parent of the system class loader. Hence the // code below. LogLog.debug("Trying to find ["+resource+
"] using ClassLoader.getSystemResource().");
return ClassLoader.getSystemResource(resource);
}
期间在考虑需要把log4j.xml放置到什么位置上的问题。关于Class.getResource和ClassLoader.getResource的路径问题 - yejg1212 - 博客园www.cnblogs.com
参看了这篇文章;
不过文章对于ClassLoader的getResource的路径描述是错误的。
由于log4j的自动加载机制就是用的ClassLoader的getResource方法,需要并且是不带"/"
的,事实上ClassLoader你使用"/"的参数会直接报错,默认路径就在工程的根目录;
总结下:
Log4j1.x 加载配置文件过程:
1.log4j1.x启动时,默认会寻找类路径下的log4j.xml配置文件,若没有,会寻找log4j.properties文件。
2.如果不想走默认的路径,可以在程序中指定配置文件路径。
类似于这种:
static Logger logger = null;
static
{
//此时指定的是src同级目录亦jar包同级目录
DOMConfigurator.configure("log4j.xml");
logger = Logger.getRootLogger();
}
3.如果以上两种方式均未指定,仍强行使用Logger,编译并不会报错,只是会在使用logger时在console打印红色警告。
log4j:WARN No appenders could be found for logger (xxx).
log4j:WARN Please initialize the log4j system properly.