热切换Log4j级别配置

热切换Log4j级别配置

原创不易,转载请注明原始地址: https://www.cnblogs.com/jiangxinnju/p/6848472.html

做一个产品或者项目,在测试时一般要打印详细的log,发布以后,因为打印日志会损失性能,所以通常在生产机上将log4j级别设置为最高,以提高效率,一旦客户那里出了问题,需要查看详细的日志信息来跟踪问题,此时打印日志就是很重要的事情。这就需要在应用开发不重启的情况下,动态切换log4j日志策略了。

目前有两种方式可以实现热切换Log4j级别配置,一是定时刷新log4j配置文件,二是调用setlevel()动态设置。

定时刷新log4j配置文件

使用log4j原生动态更新配置文件的方法

使用log4j自带的动态更新配置很简单,只要调用 PropertyConfigurator 或者 DOMConfigurator类的 configureAndWatch(String configFileName)或者 configureAndWatch(String configFileName, long delay)方法就可以了。其中configFileName值配置文件的路径加文件名,delay指扫描配置文件是否改变的间隔时间,默认值是 60 秒。在调用时log4j会创建一个线程,定时的去检查配置文件是否改变,如果改变的话就重新加载配置文件。需要注意的是在log4j中每调用一次configureAndWatch方法都会启动一个新的扫描线程:

Log4j configureAndWatch() spawning thousands of threads: https://stackoverflow.com/questions/1337742/log4j-configureandwatch-spawning-thousands-of-threads

实例代码参考:

package edu.jiangxin.log4j.hotchange;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class Log4jHotChangeWatchdog {
    public static Logger logger = Logger.getLogger(Log4jHotChangeWatchdog.class);
    static {
        PropertyConfigurator.configureAndWatch("log4j.properties", 60000);
    }

    public static void main(String[] args) {
        while (!Thread.interrupted()) {
            if (logger.isDebugEnabled()) {
                logger.debug("debug!!");
            }
            if (logger.isInfoEnabled()) {
                logger.info("info!!");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}

使用触发方式更新配置文件

我们可以通过configureAndWatch方法来进行动态的改变log4j的配置,但是他采用了轮询方式来实现的,现在我们需要某种触发机制自己调用PropertyConfigurator对象的configure(String configFilename)方法重新加载log4j的配置。触发机制需要结合实际业务情况,比如提供JSP页面触发,提供Rest接口触发。

实例代码参考:

<!-- log4jReload.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.*, java.io.*" %>
<%@ page import="org.apache.log4j.PropertyConfigurator" %>
<%
    String state = request.getParameter("state");
    if (state != null && state.trim().equals("start")) {
        PropertyConfigurator.configure("./log4j.properties");
    }
%>
<HTML>
<HEAD>
    <TITLE>动态更新log4j配置</TITLE>
</HEAD>
<BODY>
<A HREF="log4jReload.jsp?state=start">开始</A>
</BODY>
</HTML>

使用Spring定时更新配置文件

spring通过org.springframework.util. Log4jConfigListener实现运行时切换需求,Log4jConfigListener对log4j原生方法进行封装。默认情况是1分钟重新加载一次。

在web.xml文件中 配置 加载 log4j.properties的属性

<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<context-param>
    <param-name>log4jRefreshInterval</param-name>
    <param-value>10000</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- 为避免项目间冲突,定义唯一的 webAppRootKey -->
<context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>scheduleProject</param-value>
</context-param>
  1. log4jConfigLocation 指定Spring从哪个目录下加载 log4j.properties 配置文件
  2. log4jRefreshInterval 当修改了配置文件时,不需要重启就能加载变化了的log4j.properties 配置文件
  3. webAppRootKey 项目的标识,一个窗口中可能部署了多个项目,用它进行区分。当配置日志文件的输出目录时,可能会用到它。

调用setlevel()动态设置

通过界面

通过自己做的web界面,客户在前台设置日志级别,后台调用logger.setlevel()来完成日志级别切换,但是这有个缺点,下次服务重启后,本次的日志级别调整持久保存下来。

实例代码参考:

<!-- log4jHotChange.jsp -->
<%@ page import="org.apache.log4j.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=GBK" language="java" %>

<html>
<head><title>Log4J级别控制</title></head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<body>
<h1>Log4J级别控制</h1>
<% String logName = request.getParameter("log");
    if (null != logName) {
        Logger log = ("".equals(logName) ?
                Logger.getRootLogger() : Logger.getLogger(logName));
        log.setLevel(Level.toLevel(request.getParameter("level"), Level.DEBUG));
    }
%>
<c:set var="rootLogger" value="<%= Logger.getRootLogger() %>"/>
<form>
    <table border="1">
        <tr>
            <th>Level</th>
            <th>Logger</th>
            <th>Set New Level</th>
        </tr>
        <tr>
            <td>${rootLogger.level}</td>
            <td>${rootLogger.name}</td>
            <td>
                <c:forTokens var="level" delims="," items="DEBUG,INFO,WARN,ERROR,OFF">
                    <a href="log4jHotChange.jsp?log=&level=${level}">${level}</a>
                </c:forTokens>
            </td>
        </tr>
        <c:forEach var="logger" items="${rootLogger.loggerRepository.currentLoggers}">
            <c:if test="${!empty logger.level.syslogEquivalent || param.showAll}">
                <tr>
                    <td>${logger.level}</td>
                    <td>${logger.name}</td>
                    <td>
                        <c:forTokens var="level" delims="," items="DEBUG,INFO,WARN,ERROR,OFF">
                            <a href="log4jHotChange.jsp?log=${logger.name}&level=${level}">${level}</a>
                        </c:forTokens>
                    </td>
                </tr>
            </c:if>
        </c:forEach>
        <tr>
            <td></td>
            <td><input type="text" name="log"/></td>
            <td>
                <select name="level">
                    <c:forTokens var="level" delims="," items="DEBUG,INFO,WARN,ERROR,OFF">
                        <option>${level}</option>
                    </c:forTokens>
                </select> <input type="submit" value="Add New Logger"/></td>
        </tr>
    </table>
</form>
Show <a href="log4jHotChange.jsp?showAll=true">所有已知 loggers</a>
</body>
</html>

效果如图所示:

调用setlevel()方法与配置文件动态加载一样,除了可以通过JSP触发,还可以通过Rest接口触发,暴露Rest接口可以参考如下文章,本文不再展开。
Build a RESTful Web service using Jersey and Apache Tomcat: https://developer.ibm.com/articles/wa-aj-tomcat/

提供一个暴露Rest服务的代码样例:

package edu.jiangxin.jersey.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

@Path("/Log4JHotChange")
public class Log4jHotChangeWS {
    @GET
    @Produces(MediaType.APPLICATION_XML)
    @Path("/{package}/{level}")
    public Response index(@PathParam("package") String p, @PathParam("level") String l) {
        Level level = Level.toLevel(l);
        Logger logger = LogManager.getLogger(p);
        logger.setLevel(level);
        return Response.ok().build();
    }

    @GET
    @Produces(MediaType.APPLICATION_XML)
    @Path("/root/{level}")
    public Response index(@PathParam("level") String l) {
        Level level = Level.toLevel(l);
        LogManager.getRootLogger().setLevel(level);
        return Response.ok().build();
    }

}

通过Spring+JMX方式

如果使用spring和jmx会很简单

实例代码参考:Log4jHotChangeMBean

本文所有实例代码见:https://github.com/jiangxincode/JavaWebTest/commit/69e5cca548fd0ea6af0c7c705cc4aef39df1f621

posted @ 2017-05-13 11:29  Aloys_Code  阅读(3765)  评论(0编辑  收藏  举报
我的GITHUB|