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

文章目录

  • Logback相关知识
    • LoggerContext
    • Logger的树形结构
    • Appender
  • 动态修改日志级别
  • 动态修改Appender参数
  • 动态新增Appender
    • SpringBoot中使用动态新增Appender
  • 常见问题
    • java.lang.IllegalStateException: FileNamePattern [server.%d{yyyy-MM-dd}.%i.log] does not contain a valid DateToken
    • FileAppender成功生成文件,但文件中没有内容

Logback相关知识

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

LoggerContext

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

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

或者

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;private static final Logger log = LoggerFactory.getLogger(MyClass.class);((ch.qos.logback.classic.Logger) log).getLoggerContext()

其中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");

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

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

也可以通过包名获取:

Logger logger = LoggerFactory.getLogger("com.demo");

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

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

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

Appender

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

在这里插入图片描述

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

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

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


你也可以增添和删除Appender

Logger logger = loggerContext.getLogger("ROOT");
logger.addAppender(myAppender);
logger.detachAppender("FILE");

动态修改日志级别

修改日志级别只需要对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);

这是立即生效的。

如果想对某一个类修改日志级别,那只需要对这个类的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);

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

Logger log = LoggerFactory.getLogger("com.demo.controller.MyController");
Logger logger = LoggerFactory.getLogger("com.demo");

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


动态修改Appender参数

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

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

获取到要修改的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");

在这里插入图片描述

如上图,在修改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());

在这里插入图片描述

最终成功生成了新的文件,之后的日志都在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);

关键问题在于如何编写这个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. 首先我需要正常使用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(); // 新增AppenderSpringApplication springApplication = new SpringApplication(new Class[]{clazz});springApplication.run(args);
}

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

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

在这里插入图片描述

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

public class MyApplicationStartedEventListener implements ApplicationListener<ApplicationContextInitializedEvent> {@Overridepublic void onApplicationEvent(ApplicationContextInitializedEvent event) {LogAppenderUtils.addAppender();}
}
public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(new Class[]{clazz});// 新增监听器springApplication.addListeners(new MyApplicationStartedEventListener());springApplication.run(args);
}

对于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是否为空

正常情况应该是这样:

在这里插入图片描述


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部