基于 Nacos 配置中心的动态日志配置方案

2,294 阅读3分钟

log4j2 日志的级别不能落

SpringBoot 动态设置 logback 日志的级别

上面这两篇文章只是从技术角度说了,可以实现动态日志配置。但是并没有形成适用生产环境使用的方案。今天介绍一种基于 Nacos 配置中心的动态配置日志级别的方案。

0x01:安装 Nacos 配置中心

配置中心 Nacos 的官网

官网:nacos.io/zh-cn/docs/… 下载安装包

image.png

需要注意一下 Nacos 需要 64 位操作系统和 64 位的JDK,如果不是 64 位的启动时会出现以下异常。

 at com.alipay.sofa.jraft.core.NodeImpl.init(NodeImpl.java:138)
        at com.alipay.sofa.jraft.RaftServiceFactory.createAndInitRaftNode(RaftServiceFactory.java:47)
        at com.alipay.sofa.jraft.RaftGroupService.start(RaftGroupService.java:129)
        at com.alibaba.nacos.core.distributed.raft.JRaftServer.createMultiRaftGroup(JRaftServer.java:268)
        at com.alibaba.nacos.core.distributed.raft.JRaftProtocol.addRequestProcessors(JRaftProtocol.java:163)
        at com.alibaba.nacos.naming.core.v2.service.impl.PersistentClientOperationServiceImpl.<init>(PersistentClientOperationServiceImpl.java:92)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:175)
        ... 57 common frames omitted
Caused by: java.lang.UnsupportedOperationException: Cannot determine JNI library name for ARCH='x86' OS='windows 10' name='rocksdb'
        at org.rocksdb.util.Environment.getJniLibraryName(Environment.java:88)
        at org.rocksdb.NativeLibraryLoader.<clinit>(NativeLibraryLoader.java:19)
        ... 74 common frames omitted

启动命令

startup.cmd -m standalone

因为启动脚本默认使用集群模式启动,为了不需要使用命令还,改动一下启动脚本,修改如下

image.png

启动成功,显示如下日志

2021-05-14 22:00:34,140 INFO Initializing ExecutorService 'taskScheduler' 2021-05-14 22:00:34,178 INFO Exposing 16 endpoint(s) beneath base path '/actuator' 2021-05-14 22:00:34,465 INFO Tomcat started on port(s): 8848 (http) with context path '/nacos' 2021-05-14 22:00:34,473 INFO Nacos started successfully in stand alone mode. use embedded storage 访问 Nacos 配置中心,对应的账号和密码 nacos / nacos

http://192.168.10.6:8848/nacos/index.html

0x02: 在配置中心创建配置文件 dynamics-log.json

内容如下

{
    "logger": [
        {
            "name":"ROOT",
            "level":"debug"
        }
    ]
}

image.png

0x03:pom.xml 文件引入依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.olive</groupId>
    <artifactId>valid-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath /> 
    </parent>
    <name>valid-demo</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>0.5.0</version>
        </dependency>

    </dependencies>
</project>

主要多引入 Nacos 客户端(nacos-client )依赖

0x04:编译监听器

要达到动态的效果,一般有 PUSH 和 PULL 两种方式。Nacos 内部提供了监听器,只要实现相关方法就可以对不同的 data-id 进行监听。大致代码如下:

import java.util.concurrent.Executor;

import javax.annotation.PostConstruct;

import org.springframework.context.annotation.Configuration;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;

@Configuration
public class LoggerDynamicsConfig {

    private String serverAddr = "127.0.0.1:";

    private String dataId = "dynamics-log.json";

    private String group = "DEFAULT_GROUP";

    private long timeoutMs = 50000L;

    @PostConstruct
    public void init() {
        try {
            ConfigService configService = NacosFactory.createConfigService(serverAddr);
            String configInfo = configService.getConfig(dataId, group, timeoutMs);
            System.out.println("configInfo = " + configInfo);   
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    System.out.println("recieve configInfo : " + configInfo);
                }

                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }
}

启动服务,可以看到控制台打印如下日志,说明可以获取到 Nacos 配置中心的配置项。

2021-05-14 22:13:56.265 [main] INFO  c.alibaba.nacos.client.identify.CredentialWatcher - [] [] [] No credential found
configInfo = {
    "logger": [        {            "name":"ROOT",            "level":"debug"        }    ]
}
recieve configInfo : {
    "logger": [        {            "name":"ROOT",            "level":"debug"        }    ]
}
2021-05-14 22:13:56.563 [main] INFO  o.s.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'

动态在 Nacos 配置中心修改一下,可以看到控制台打印如下日志,说明服务是可以动态监听到配置的改变的。

recieve configInfo : {
    "logger": [
        {
            "name":"ROOT",
            "level":"debug"
        }, {
            "name":"com.spring",
            "level":"info"
        }
    ]
}

0x05:动态日志改造说明

在 receiveConfigInfo() 方法中,把拿到的配置 json 串构造成一个对象数组,比如定义为 LoggerConfig[]。

基于 [ SpringBoot 动态设置 logback 日志的级别 ] 改造,把 LoggerController 的 printAllLogger() 方法改成一个获取所有日志对象的方法,比如

public List<Logger> getAllLogger(){
     //TODO

}

对配置动态变化构造的 LoggerConfig[] 数组与调用 getAllLogger() 获取的所有日志对象进行遍历比较,进行动态更新