动态修改日志级别


9/14/2018 Spring

介绍如何动态修改日志配置

why

为了减少日志文件的数量, 生产环境的日志等级都是 Error, 但是当遇到问题时, 错误日志可能不能快速准确的定位出错的地方, 如果能在不重启应用的情况下, 修改日志级别并且生效, 能更快的发现出错的地方.

what

这里选择使用 JMX 来实现日志级别动态修改.

JMX (Java Management Extensions)是管理 Java 的一种扩展. 这种机制可以方便的管理, 监控正在运行中的 Java 程序. 常用于管理线程, 内存, 日志 Level, 服务重启, 系统环境等.

实现一个被 JMX 托管的 MBean的方式:

  1. MBean的接口必须以MBean结尾, 比如 XxxxMBean
  2. 实现必须以Xxxx 命名因为接口定义是XxxxMBean

logback 定义的 MBean

jconsole 查看 MBean

how

动态修改日志级别的思路:

  1. API 调用 DynamicChangeLogLevel 修改日志级别
  2. DynamicChangeLogLevel 通过 LogBackMBean 修改日志级别
  3. 使用责任链, 修改 fkh-api 后, 后面相关的服务都会被修改

存在的问题

1. 在集群环境, 因为有负债均衡, 不同的请求被负载到不同的机器上面, 前面修改了日志级别, 下一次有可能不会生效

2. 不能单独修改具体应用的日志级别

解决方案

使用 Zookeeper Watcher.

  1. 每个应用看做一个单独的节点, 启动的时候向 Zookeeper 注册以项目服务名命名的节点, 并把 logback.xml中设置的日志级别写入节点,最后对这个节点监听.
  2. API 调用时, 传入 applicationName 和 level, 修改具体节点下的数据
  3. 节点数据被修改, 触发 watcher, 调用 LogBackMBean 修改日志级别

具体流程如下:

具体步骤

  1. 修改 logback.xml 配置, 添加 <jmxConfigurator /> 开启 JMX, 添加 com.fkhwl 的日志级别
<jmxConfigurator />
...
<root level="ERROR">
	<appender-ref ref="FILE" />
</root>
<logger name="com.fkhwl" level="ERROR"/>
1
2
3
4
5
6

设置一个 <logger name="com.fkhwl" level="ERROR"/> 的原因在于, 当需要查找执行流程时, 只需要将 com.fkhwl 设置为 INFO, 这样只会输出 com.fkhwl 包及子包中的 INFO 信息. 如果没有 com.fkhwl, 我们只有设置 ROOT 为 INFO, 这样会输出诸如 dubbo, zookeeper 等第三方包中的所有 INFO 信息.

  1. 添加一个 ServerListener, 一是解决动态修改日志级别时内存溢出, 二是应用启动完成后向 Zookeeper 创建节点
public class ServerListener implements ServletContextListener {
	private final Logger log = LoggerFactory.getLogger(this.getClass());

	public void contextDestroyed(ServletContextEvent contextEvent) {
	    // 防止动态修改日志级别时内存溢出
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        loggerContext.stop();
	}

	public void contextInitialized(ServletContextEvent contextEvent) {
        String applicationName = contextEvent.getServletContext().getServletContextName();
        log.info("=================================");
        log.info("system [{}] start finish!!!", applicationName);
        log.info("=================================");
        log.info("servlet path [{}]", System.getProperty(contextEvent.getServletContext().getServletContextName()));

        log.info("create zookeeper node start");
        String host = "127.0.0.1:2181";
        String defaultLogLevel = DynamicChangeLogLevel.getCurrentlyLevel(new LogNode());
        new LogNodeOperation(host, applicationName, defaultLogLevel);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 实现 ChangeLogLevel API

部署问题

因为 MBean 是通过 ObjectName 来获取对象, logback 的默认 OBjectName 为 ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator

当在同一个 Tomcat 中部署多个应用时, 每个 Web 应用程序中的记录器上下文相关联的各种实例将会相互冲突

解决方法

在 logback.xml 设置 contextName

<configuration>
    <!-- 设置别名 必须在 <jmxConfigurator/> 之前设置 -->
    <contextName>${project.artifactId}</contextName>
    <!--JMX监控-->
    <jmxConfigurator />
    ...
 </configuration>
1
2
3
4
5
6
7

这样就能区分不同的应用

新需求

要明确不同服务器上的不同应用, 能具体修改某一台服务器上的某一个应用的日志级别

Last Updated: 7/4/2019, 3:27:38 PM