线上的系统优先级都设为INFO之上,如果修日日志级别,需要重启服务,对线上的影响比较大。如何做到动态地修改日志级别,而且不用重启服务?Log4jConfigListener
就上场了
Log4jConfigListener
在spring-web
中,需要添加maven的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
在web.xml
中配置
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.xml</param-value>
</context-param>
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
想动态修改日志级别,只需要修改log4j.xml
。
那么,Log4jConfigListener
做了什么,可以知道文件变化了并加以应用,难道是起了个线程来做的?
首先看下Log4jConfigListener
public class Log4jConfigListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
Log4jWebConfigurer.initLogging(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
Log4jWebConfigurer.shutdownLogging(event.getServletContext());
}
}
这里Log4jConfigListener
使用了Log4jWebConfigure
,让我们继续
public static void initLogging(ServletContext servletContext) {
// Expose the web app root system property.
if (exposeWebAppRoot(servletContext)) {
WebUtils.setWebAppRootSystemProperty(servletContext);
}
// Only perform custom log4j initialization in case of a config file.
String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
if (location != null) {
// Perform actual log4j initialization; else rely on log4j's default initialization.
try {
// Resolve system property placeholders before potentially
// resolving a real path.
location = SystemPropertyUtils.resolvePlaceholders(location);
// Leave a URL (e.g. "classpath:" or "file:") as-is.
if (!ResourceUtils.isUrl(location)) {
// Consider a plain file path as relative to the web
// application root directory.
location = WebUtils.getRealPath(servletContext, location);
}
// Write log message to server log.
servletContext.log("Initializing log4j from [" + location + "]");
// Check whether refresh interval was specified.
String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM);
if (intervalString != null) {
// Initialize with refresh interval, i.e. with log4j's watchdog thread,
// checking the file in the background.
try {
long refreshInterval = Long.parseLong(intervalString);
Log4jConfigurer.initLogging(location, refreshInterval);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid 'log4jRefreshInterval' parameter: " + ex.getMessage());
}
}
else {
// Initialize without refresh check, i.e. without log4j's watchdog thread.
Log4jConfigurer.initLogging(location);
}
}
catch (FileNotFoundException ex) {
throw new IllegalArgumentException("Invalid 'log4jConfigLocation' parameter: " + ex.getMessage());
}
}
}
这里有几行代码需要是重点,
String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM);
Log4jConfigurer.initLogging(location, refreshInterval);
那Log4jConfigure.initLogging
有干了啥呢?
public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
File file = ResourceUtils.getFile(resolvedLocation);
if (!file.exists()) {
throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
}
if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
}
else {
PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
}
}
获取配置文件,根据log4配置文件的格式(xml,properties)方式进行加载xml,那么一定是在`DOMConfigurator.configureAndWatch` 或者`PropertyConfigurator.configureAndWatch`里面有个线程在做幕后工作,由于LZ采用的是XML格式的配置文件,那就看下`DOMConfigurator.configureAndWatch`,看看它到底怎么实现的吧。
static
public
void configureAndWatch(String configFilename, long delay) {
XMLWatchdog xdog = new XMLWatchdog(configFilename);
xdog.setDelay(delay);
xdog.start();
}
`XMLWatchdog`,这是个`WatchDog`
class XMLWatchdog extends FileWatchdog {
XMLWatchdog(String filename) {
super(filename);
}
/**
Call {@link DOMConfigurator#configure(String)} with the
filename
to reconfigure log4j. */
public
void doOnChange() {
new DOMConfigurator().doConfigure(filename,
LogManager.getLoggerRepository());
}
}
FileWatchDog
public abstract class FileWatchdog extends Thread{
…….
abstract
protected
void doOnChange();
public void run() {
while(!interrupted) {
try {
Thread.sleep(delay);
} catch(InterruptedException e) {
// no interruption expected
}
checkAndConfigure();
}
}
protected void checkAndConfigure() {
……..
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;
}
}
}
}
``
FileWatchDog
有个抽象方法,
doOnChange`,就是对文件变化后的响应,抽象方法的定义,为子类的扩展提供了可能。
我们看到,Log4jConfirgureListener
也就是通过线程的方式扫描log4j.xml
,当发现log4j的配置文件发生变化后就作出响应,从而做到了不重启应用修改日志的输出级别。