• 动态新增、修改Logback的Appender(可实现动态调整日志级别,Appender参数)


    Logback相关知识

    动态修改Appender参数没有通用的代码,主要是方法论。需要看懂之后才能解决具体的需求,所以我下面的代码并不一定能直接适用于你的场景。我尽量用通俗的语言和实际例子来进行讲解。

    LoggerContext

    在Logback中,所有的配置相关的东西都放在ch.qos.logback.classic.LoggerContext类对象中,该对象全局只有一个。你可以通过下面两种方式进行获取:

    import org.slf4j.LoggerFactory;
    import ch.qos.logback.classic.LoggerContext;
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    • 1
    • 2
    • 3
    • 4

    或者

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);
    
    ((ch.qos.logback.classic.Logger) log).getLoggerContext()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其中ch.qos.logback.classic.Loggerorg.slf4j.Logger 的实现类。


    Logger的树形结构

    Loggger是一个树形结构,根节点为ROOT:

    在这里插入图片描述
    你可以通过如下代码来获取根节点的logger对象:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    Logger logger = LoggerFactory.getLogger("ROOT");
    // 或者
    Logger logger = loggerContext.getLogger("ROOT");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你可以通过类对象或类名获取logger对象:

    Logger log = LoggerFactory.getLogger(MyController.class);
    Logger log = LoggerFactory.getLogger("com.demo.controller.MyController");
    
    • 1
    • 2

    也可以通过包名获取:

    Logger logger = LoggerFactory.getLogger("com.fsk");
    
    • 1

    每一个类和包都对应有logger对象。对于部分配置(例如Level),如果该logger没配,就会沿用父类的配置。

    你可以通过如下代码获取所有的Logger对象:

    List<Logger> loggerList = loggerContext.getLoggerList();
    
    • 1

    Appender

    logback.xml中的标签的内容会被映射成ch.qos.logback.core.Appender类对象,Appender是一个接口。你可以通过ROOTLogger对象查看:

    在这里插入图片描述

    你可通过logger对象进行获取(必须是ROOT)Appender对象:

    Logger logger = loggerContext.getLogger("ROOT");
    Appender appender = logger.getAppender("FILE");
    
    • 1
    • 2

    这里传的名字就对应logback.xml的name。


    你也可以增添和删除Appender

    Logger logger = loggerContext.getLogger("ROOT");
    logger.addAppender(myAppender);
    logger.detachAppender("FILE");
    
    • 1
    • 2
    • 3

    动态修改日志级别

    修改日志级别只需要对ROOTlogger对象修改即可:

    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.Logger;
    import ch.qos.logback.classic.LoggerContext;
    
    Logger logger = loggerContext.getLogger("ROOT");
    logger.setLevel(Level.INFO);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这是立即生效的。

    如果想对某一个类修改日志级别,那只需要对这个类的logger修改即可:

    import ch.qos.logback.classic.Level;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    Logger log = LoggerFactory.getLogger(MyController.class);
    ((ch.qos.logback.classic.Logger)log).setLevel(Level.DEBUG);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以通过类名或包名获取logger对象:

    Logger log = LoggerFactory.getLogger("com.demo.controller.MyController");
    Logger logger = LoggerFactory.getLogger("com.fsk");
    
    • 1
    • 2

    每一个类和包都对应有logger对象,如果你想对某个包调整日志级别,那调整包对应的logger日志级别即可。


    动态修改Appender参数

    首先,需要获取到你要修改的Appender的对象:

    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Logger logger = loggerContext.getLogger("ROOT");
    Appender appender = logger.getAppender("FILE");
    
    • 1
    • 2
    • 3

    获取到要修改的Appender后,你可以用debug工具,找一下你需要修改的参数对应的对象或对应的属性在哪:

    在这里插入图片描述

    之后你就可以使用代码对其修改即可,我这里使用LayoutPattern进行一个实验:

    在这里插入图片描述

    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Logger logger = loggerContext.getLogger("ROOT");
    Appender appender = logger.getAppender("FILE");
    LayoutWrappingEncoder encoder = (LayoutWrappingEncoder) ((RollingFileAppender)appender).getEncoder();
    TraceIdPatternLogbackLayout layout = (TraceIdPatternLogbackLayout) encoder.getLayout();
    layout.setPattern("%d{yyyy-MM-dd} %-5level [%tid] [%thread] %logger{36} [%file:%line] [%msg] %n");
    layout.start(); // 重启layout
    logger.info("修改Layout");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    如上图,在修改layout后,还需要对其进行重启操作layout.start()才能生效。


    我们再来尝试一下fileName这个参数:

    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Logger logger = loggerContext.getLogger("ROOT");
    Appender appender = logger.getAppender("FILE");
    ((RollingFileAppender)appender).setFile("testFile2.log");
    
    // stop
    appender.stop(); 
    
    // start
    ((RollingFileAppender) appender).getRollingPolicy().start();
    ((RollingFileAppender) appender).getTriggeringPolicy().start();
    appender.start();
    
    logger.info("修改Filename, 结果:" + appender.isStarted());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    最终成功生成了新的文件,之后的日志都在testFile2.log中。

    如果你修改的参数没有成功,请看一下对应的对象是否start()成功。 可以通过obj.isStarted()来判断。 若没有start成功,可以debug进入断点看一下是哪一步没成功。

    例如,我在修改fileName时第一遍并没有成功,所以我进入debug后,发现是这步存在问题:在这里插入图片描述
    所以我又补充了这两行代码就可以了:((RollingFileAppender) appender).getRollingPolicy().start();((RollingFileAppender) appender).getTriggeringPolicy().start();

    其他参数我就不一一演示了,具体哪些参数需要重启,哪些不需要我也不清楚,需要你们自己测试。


    动态新增Appender

    要动态新增Appender,只需要在运行期对ROOTlogger对象新增Appender对象即可:

    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Logger logger = loggerContext.getLogger("ROOT");
    Appender appender = getAppender(); // 获取Appender
    logger.addAppender(appender);
    
    • 1
    • 2
    • 3
    • 4

    关键问题在于如何编写这个getAppender()方法,这里我先贴一下我写的一个感受一下:

    public static String filename = "testFile.log";
    public static long bufferSize = 8 * FileSize.KB_COEFFICIENT;
    public static String appenderName = "FILE";
    public static String pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%tid] [%thread] %logger{36} [%file:%line] [%msg] %n";
    public static String filenamePattern = "server.%d{yyyy-MM-dd}.%i.log";
    public static int maxHistory = 30;
    public static long maxFileSize = 50 * FileSize.MB_COEFFICIENT;
    
    private static LoggerContext loggerContext;
    private static RollingFileAppender rollingFileAppender;
    private static TimeBasedRollingPolicy timeBasedRollingPolicy;
    private static SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP;
    private static TraceIdPatternLogbackLayout layout;
    
    
    private synchronized static Appender getAppender() {
        rollingFileAppender = new RollingFileAppender<>();
        rollingFileAppender.setContext(loggerContext);
        rollingFileAppender.setFile(filename);
        rollingFileAppender.setRollingPolicy(getTriggeringPolicy());
        rollingFileAppender.setRollingPolicy(getRollingPolicy());
        rollingFileAppender.setAppend(true);
        rollingFileAppender.setBufferSize(new FileSize(bufferSize));
        rollingFileAppender.setEncoder(getEncoder());
        rollingFileAppender.setName(appenderName);
        rollingFileAppender.start();
    
        return rollingFileAppender;
    }
    
    private static Encoder getEncoder() {
        LayoutWrappingEncoder layoutWrappingEncoder = new LayoutWrappingEncoder();
        layoutWrappingEncoder.setLayout(getLayout());
        layoutWrappingEncoder.setParent(rollingFileAppender);
        layoutWrappingEncoder.start();
        return layoutWrappingEncoder;
    }
    
    private static Layout getLayout() {
        layout = new TraceIdPatternLogbackLayout();
        layout.setPattern(pattern);
        layout.setContext(loggerContext);
        layout.start();
        return layout;
    }
    
    private static RollingPolicy getRollingPolicy() {
        TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy();
        rollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
        rollingPolicy.setFileNamePattern(filenamePattern);
        rollingPolicy.setParent(rollingFileAppender);
        rollingPolicy.setContext(loggerContext);
        rollingPolicy.setMaxHistory(maxHistory);
        rollingPolicy.start();
        return rollingPolicy;
    }
    
    private static RollingPolicy getTriggeringPolicy() {
        timeBasedRollingPolicy = new TimeBasedRollingPolicy();
        timeBasedRollingPolicy.setFileNamePattern(filenamePattern);
        timeBasedRollingPolicy.setMaxHistory(maxHistory);
        timeBasedRollingPolicy.setContext(loggerContext);
        timeBasedRollingPolicy.setParent(rollingFileAppender);
        timeBasedRollingPolicy.start();
    
        timeBasedRollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(getTimeBasedFileNamingAndTriggeringPolicy());
        return timeBasedRollingPolicy;
    }
    
    private static TimeBasedFileNamingAndTriggeringPolicy getTimeBasedFileNamingAndTriggeringPolicy() {
        sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
        sizeAndTimeBasedFNATP.setMaxFileSize(new FileSize(maxFileSize));
        sizeAndTimeBasedFNATP.setTimeBasedRollingPolicy(timeBasedRollingPolicy);
        sizeAndTimeBasedFNATP.setContext(loggerContext);
        sizeAndTimeBasedFNATP.start();
        return sizeAndTimeBasedFNATP;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    是不是看起来非常复杂,其实我也不是具体每一行都知道什么意思,但是我可以写出来。这里我主要讲解一下我使用的方法:

    1. 首先我需要正常使用logback.xml将我的appender需要写出来:
      在这里插入图片描述
    2. 接下来,我需要找一个log处打个断点,找出该Appender对象:

    在这里插入图片描述

    一定要用ROOTlogger对象,否则获取不到Appender

    1. 然后对着Debug看到的类,然后自己一个一个new,一个一个set即可:

    在这里插入图片描述
    4. 然后不断调试(这是个体力活),直到成功。


    这里说几个比较容易遇到的坑:

    1. start()方法的对象都需要调用
    2. 诸如LoggerContextAppender等对象,在很多对象中都需要set,不要偷懒,否则可能会报一个你看不懂的报错。
    3. set的顺序也需要注意,和debug显示的并不一样,需要根据报错进行调整。
    4. start() 有可能执行失败但不报错,所以最好在start后打印一下isStart()确保执行成功。

    SpringBoot中使用动态新增Appender

    在Spring中动态新增Appender时,尤其是FileAppender,你可能会发现并不生效。例如:

    public static void main(String[] args) {
    	LogAppenderUtils.addAppender(); // 新增Appender
    	SpringApplication springApplication = new SpringApplication(new Class[]{clazz});
    	springApplication.run(args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这是因为在Spring的启动代码中会对Appender进行重置,具体在Spring源码中的哪一行我就不找了(忘了之前怎么找的了,现在找不到了)。

    要解决这个问题也不难,将新增Appender放在“Spring对Appender的重置之后”即可,所以我们需要用到SpringBoot的生命周期钩子函数,

    在这里插入图片描述

    Spring重置Appender就是在ApplicationEnvironmentPrepareEventApplicationContextInitializedEvent之间,所以我们在这样做就可以了:

    public class MyApplicationStartedEventListener implements ApplicationListener<ApplicationContextInitializedEvent> {
    
        @Override
        public void onApplicationEvent(ApplicationContextInitializedEvent event) {
            LogAppenderUtils.addAppender();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public static void main(String[] args) {
    	LogAppenderUtils.addAppender(); // 新增Appender
    	SpringApplication springApplication = new SpringApplication(new Class[]{clazz});
    	// 新增监听器
    	springApplication.addListeners(new MyApplicationStartedEventListener());
    	springApplication.run(args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于Appender参数的修改也建议放在这个监听器中。


    常见问题

    java.lang.IllegalStateException: FileNamePattern [server.%d{yyyy-MM-dd}.%i.log] does not contain a valid DateToken

    这是因为这个类对象没有进行setContext,或者set了个空的。

    FileAppender成功生成文件,但文件中没有内容

    1. 检查一下Appender是否正常增添到ROOTlogger对象中
    2. 检查Appender对象是否正常启动,outputStream是否为空

    正常情况应该是这样:

    在这里插入图片描述

  • 相关阅读:
    重温Python基础,都是最基础的知识点
    算法刷题第九天:广度优先搜索 / 深度优先搜索--3
    【云原生】Docker网络原理及Cgroup硬件资源占用控制
    Oracle/PLSQL: Lpad Function
    【C++】常用查找算法
    Linux网络编程(四)
    Jmeter压测工具和Docker服务端接口压测的安装使用详细教程
    一次 MySQL 误操作导致的事故,「高可用」都顶不住了
    Intouch Historian历史曲线配置导入导出
    DYC算法开发与测试(基于ModelBase实现)
  • 原文地址:https://blog.csdn.net/zhaohongfei_358/article/details/125894130