应用场景与问题

当项目在运行时,我们如果需要修改log4j 1.X或者log4j2的配置文件,一般来说我们是不能直接将项目停止运行再来修改文件重新部署的。于是就有这样一个问题:如何在不停止当前项目的运行的情况下,让系统能够自动地监控配置文件的修改状况,从而实现动态加载配置文件的功能?而log4j 1.X和log4j2的差别略大,各自应该怎么实现这个功能?

log4j 1.X动态加载配置文件

log4j 1.X提供了动态加载配置文件的方法:

DOMConfigurator.configureAndWatch()
PropertyConfigurator.onfigureAndWatch()

DOMConfigurator对应的是xml配置文件,PropertyConfigurator对应的是properties配置文件。这两个类都有configureAndWatch这个方法,该方法有个重载方法,如下:

configureAndWatch(String configFilename)
configureAndWatch(String configFilename, long delay)

configureAndWatch方法用来监控配置文件是否被改动,监控的时间间隔是delay参数来决定,如果不传入该参数则使用默认的时间间隔1分钟(60000L)。configureAndWatch(String configFilename)实际上还是调用的configureAndWatch(String configFilename, long delay)

log4j2动态加载配置文件

和log4j 1.X比起来,log4j2的动态加载配置很简单就能实现,不需要另外在代码中调用api,方法如下:

<configuration monitorInterval="30">
...
</configuration>

在log4j2.xml配置文件中的configuration节点添加monitorInterval的值,单位是秒,如果配置的值大于0,则会按照时间间隔来自动扫描配置文件是否被修改,并在修改后重新加载最新的配置文件。如果不配置该值,默认为0,即不扫描配置文件是否被修改。

Log4j 1.X动态加载配置文件的底层实现原理

DOMConfigurator#configureAndWatch源码解析

org.apache.log4j.xml.DOMConfigurator#configureAndWatch源码如下:

static public void configureAndWatch(String configFilename, long delay) {
XMLWatchdog xdog = new XMLWatchdog(configFilename);
xdog.setDelay(delay);
xdog.start();
}

这里new了一个XMLWatchdog对象,接着设置了delay参数,最后调用了start()方法。

watchdog是看门狗、检查者的意思,XMLWatchdog继承了FileWatchdog这个类,在XMLWatchdog中仅仅重写了doOnChange方法:

public void doOnChange() {
new DOMConfigurator().doConfigure(filename, LogManager.getLoggerRepository());
}

从方法名就可以看出来,如果XMLWatchdog监控到配置文件被改动了,就会调用这个doOnChange方法,用来重新加载配置文件。那么它又是怎么知道配置文件被改动过了呢?接着看其父类FileWatchdog的源码:

public abstract class FileWatchdog extends Thread {

  /**
The default delay between every file modification check, set to 60
seconds. */
static final public long DEFAULT_DELAY = 60000;
/**
The name of the file to observe for changes.
*/
protected String filename; /**
The delay to observe between every check. By default set {@link
#DEFAULT_DELAY}. */
protected long delay = DEFAULT_DELAY; File file;
long lastModif = 0;
boolean warnedAlready = false;
boolean interrupted = false; protected FileWatchdog(String filename) {
super("FileWatchdog");
this.filename = filename;
file = new File(filename);
setDaemon(true);
checkAndConfigure();
} /**
Set the delay to observe between each check of the file changes.
*/
public void setDelay(long delay) {
this.delay = delay;
} abstract protected void doOnChange(); protected void checkAndConfigure() {
boolean fileExists;
try {
fileExists = file.exists();
} catch(SecurityException e) {
LogLog.warn("Was not allowed to read check file existance, file:["+
filename+"].");
interrupted = true; // there is no point in continuing
return;
} if(fileExists) {
long l = file.lastModified(); // this can also throw a SecurityException
if(l > lastModif) { // however, if we reached this point this
lastModif = l; // is very unlikely.
doOnChange();
warnedAlready = false;
}
} else {
if(!warnedAlready) {
LogLog.debug("["+filename+"] does not exist.");
warnedAlready = true;
}
}
} public void run() {
while(!interrupted) {
try {
Thread.sleep(delay);
} catch(InterruptedException e) {
// no interruption expected
}
checkAndConfigure();
}
}
}

可以看到,FileWatchdog继承了Thread类,类里定义了几个成员变量,比如默认的监控时间间隔等。而在该类的构造方法中可以看到,首先该线程类将名字设定成FileWatchdog,接着根据传入的配置文件的路径new了一个File对象,然后该线程类又设置成了守护线程(daemon thread),最后调用了checkAndConfigure()

checkAndConfigure()中,则是对new出来的配置文件File对象进行检查是否存在该文件,若不存在该文件则会设置成员变量的值,这样就不会去监控不存在的配置文件了。如果该配置文件存在,则通过lastModified()来获取文件的最后更新时间,和上次的更新时间作对比,如果比上次更新时间大则会调用doOnChange()来重新加载配置文件。

而在FileWatchdog的run方法中,则是在无限循环中先让线程睡眠设置好的监控时间间隔,然后调用checkAndConfigure()

总结

可以看出,在log4j 1.X的DOMConfigurator中,是通过创建一个守护线程来不停地扫描配置文件的最后更新时间,并和上次的更新时间进行对比,如果最后更新时间大于上次更新时间则会重新加载配置文件。

PropertyConfigurator#configureAndWatch源码解析

PropertyConfigurator的configureAndWatch()其实和DOMConfigurator差不多,区别是PropertyConfigurator在方法里new了一个PropertyWatchdog对象,PropertyWatchdog和XMLWatchdog一样继承了FileWatchdog,一样重写了doOnChange()方法。只是PropertyWatchdog是通过new PropertyConfigurator().doConfigure()来加载配置文件的。

从源码实现来看,无论是使用xml配置文件,还是使用properties配置文件,其动态加载配置文件的底层实现是基本一样的。可以通过解析配置文件的文件后缀来判断是xml还是properties文件,然后调用对应的方法即可,大概的思路如下:

boolean flag = true;
boolean isXml = StringUtils.equalsIgnoreCase("xml", StringUtils.substringAfterLast(filepath, "."));
ling delay = 30000; if (isXml) {
if (flag) {
DOMConfigurator.configureAndWatch(filepath, delay);
} else {
DOMConfigurator.configure(filepath);
}
} else {
if (flag) {
PropertyConfigurator.configureAndWatch(filepath, delay);
} else {
PropertyConfigurator.configure(filepath);
}
}

log4j2底层实现动态加载配置文件的简单解析

虽然log4j2的动态加载配置很简单,但其底层实现比起log4j 1.X却要复杂很多,使用到了很多并发包下的类,具体也不是很了解,这里简单解释下流程。

对于log4j2.xml文件,对应的是org.apache.logging.log4j.core.config.xml.XmlConfiguration这个类。如果在log4j2.xml里配置了monitorInterval,在构建XmlConfiguration时会根据该值来走一段特定的逻辑:

for (final Map.Entry<String, String> entry : attrs.entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) {
statusConfig.withStatus(value);
} else if ("dest".equalsIgnoreCase(key)) {
statusConfig.withDestination(value);
} else if ("shutdownHook".equalsIgnoreCase(key)) {
isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
} else if ("shutdownTimeout".equalsIgnoreCase(key)) {
shutdownTimeoutMillis = Long.parseLong(value);
} else if ("verbose".equalsIgnoreCase(key)) {
statusConfig.withVerbosity(value);
} else if ("packages".equalsIgnoreCase(key)) {
pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
} else if ("name".equalsIgnoreCase(key)) {
setName(value);
} else if ("strict".equalsIgnoreCase(key)) {
strict = Boolean.parseBoolean(value);
} else if ("schema".equalsIgnoreCase(key)) {
schemaResource = value;
} else if ("monitorInterval".equalsIgnoreCase(key)) {
final int intervalSeconds = Integer.parseInt(value);
if (intervalSeconds > 0) {
getWatchManager().setIntervalSeconds(intervalSeconds);
if (configFile != null) {
final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
getWatchManager().watchFile(configFile, watcher);
}
}
} else if ("advertiser".equalsIgnoreCase(key)) {
createAdvertiser(value, configSource, buffer, "text/xml");
}
}

可以看到,如果monitorInterval的值大于0,则会拿到WatchManager并设置扫描配置文件的时间间隔,如果配置文件存在,则会new一个ConfiguratonFileWatcher对象,并将配置文件和该对象一起传递给WatchManager的watchFile方法。这两个方法的底层实现很绕,比起log4j 1.X要复杂得多,不容易看懂。不过最终实现的效果还是一样的,依然会开启一个守护线程来监控配置文件是否被改动。

区别在于,log4j2使用线程池来启动线程,在WatchManager#start()里实现的:

@Override
public void start() {
super.start();
if (intervalSeconds > 0) {
future = scheduler.scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds,
TimeUnit.SECONDS);
}
}

而该方法则是在启动配置文件时被调用的,AbstractConfiguration#start()

/**
* Start the configuration.
*/
@Override
public void start() {
// Preserve the prior behavior of initializing during start if not initialized.
if (getState().equals(State.INITIALIZING)) {
initialize();
}
LOGGER.debug("Starting configuration {}", this);
this.setStarting();
if (watchManager.getIntervalSeconds() > 0) {
watchManager.start();
}
...
}

这里只是简单解析了下主要的流程,具体的实现细节目前还看不太懂,有兴趣的可以自己去看看log4j2的源码。另外我在官方文档里看到说monitorInterval的最小值是5,但是在源码里也没看到这个,我觉得只要配置值大于0应该就是可以的。有不对之处,欢迎指出。

这是官方原文:

Automatic Reconfiguration

When configured from a File, Log4j has the ability to automatically detect changes to the configuration file and reconfigure itself. If the monitorInterval attribute is specified on the configuration element and is set to a non-zero value then the file will be checked the next time a log event is evaluated and/or logged and the monitorInterval has elapsed since the last check. The example below shows how to configure the attribute so that the configuration file will be checked for changes only after at least 30 seconds have elapsed. The minimum interval is 5 seconds.

参考链接

log4j和log4j2怎么动态加载配置文件的更多相关文章

  1. java动态加载配置文件(申明:来源于网络)

    java动态加载配置文件 地址:http://blog.csdn.net/longvs/article/details/9361449

  2. NGINX的启停命令、以及动态加载配置文件的命令

    -- 启动(不推荐):在nginx目录下有一个sbin目录,sbin目录下有一个nginx可执行程序../nginx -- 启动(指定配置文件,推荐)/usr/local/nginx/sbin/ngi ...

  3. java动态加载配置文件

    最近项目中需要做定时任务,即定时数据库的备份.定时时间用户可以在界面中配置,要求配置修改好立即生效. 想不到什么好办法.下面是一种实现思路 把用户配置的时间存到properties配置文件中,定时任务 ...

  4. springmvc 动态加载配置文件

    <import resource="classpath:config/spring-profile-properties.xml" /> <context:pro ...

  5. Nginx自动加载配置文件方案

    nginx自动加载配置文件方案一.nginx+consul+consul-template实现过程:consul作为服务发现软件,consul-template作为nginx配置文件的模板,consu ...

  6. 关于设置SQLPLUS提示符样式的方法----登陆配置文件,动态加载提示符

    工作中用到 sqlplus mdsoss/mdsoss, 所以来了解一下sqlplus (C shell .cshrc文件里中alisa) 关于设置SQLPLUS提示符样式的方法 12638阅读 1评 ...

  7. js动态加载js css文件,可以配置文件后辍,防止浏览器缓存

    js的引用,在浏览器,或微信上访问经常会遇到文件改了,但就是没有更新的问题,使用此函数可以轻松解决缓存问题只需要把js的引用方式改为使用此函数加载即可 源码如下: /** * js动态加载js css ...

  8. spring动态加载(刷新)配置文件 [复制链接]

    待验证 在程序开发时,通常会经常修改spring的配置文件,不得不重启tomcat来加载spring配,费时费力.如果能在不重启tomcat的情况下,手动动态加载spring 配置文件,动态重启读取s ...

  9. 页面加载异常 清除浏览器静态文件 js css 缓存 js动态加载js css文件,可以配置文件后辍,防止浏览器缓存

    js清除浏览器缓存的几种方法 - 兔老霸夏 - 博客园 https://www.cnblogs.com/Mr-Rocker/p/6031096.html js清除浏览器缓存的几种方法   一.CSS和 ...

随机推荐

  1. TortoiseSVN使用笔记

    TortoiseSVN版本冲突详解   下列步骤展示了如何将分支A中的修改合并到分支B. 1.在分支B的本地副本目录中选择"合并(Merge)". 2.选择“合并一个版本范围(Me ...

  2. hdu1198 Farm Irrigation —— dfs or 并查集

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1198 dfs: #include<cstdio>//hdu1198 dfs #includ ...

  3. hdu 1205 吃糖果(抽屉原理)

    题意:N种糖果,不能把一样的放在一起吃,即第一次吃一种,下一次吃另一种. 思路:找到个数最多的糖果种类(最大的数目记作 ma,该糖果种类记为a),首先把这n个糖果放到n个抽屉里面,然后把剩余的N-1种 ...

  4. jQuery ajax序列化函数

    参数序列化$.param() 举例: <!DOCTYPE html> <html> <head> <script src="https://ajax ...

  5. python中字符串的内置方法

    这里说的是字符串中的内置方法,毕竟字符串是最常用的操作对象. ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '_ ...

  6. regular

    regular.py import re # . # 只能匹配一个字母,而不是2个或0个 # \ # 转义 # 'abc\\.com' r'abc\.com' # 字符集[] # 匹配他所包括的任意字 ...

  7. P1816 忠诚

    题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人的挑拨,财主还是对管家产生了 ...

  8. 各个国家 不同字符集的unicode 编码范围

    原文地址:http://blog.csdn.NET/xzl04/article/details/6307416 0000-007F:C0控制符及基本拉丁文 (C0 Control and Basic  ...

  9. HDU2546(01背包变形)

    饭卡 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submissi ...

  10. nohup command > myout.file 2>&1 &

    nohup command > myout.file 2>&1 &