0
点赞
收藏
分享

微信扫一扫

动态新增、修改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.Logger​​​ 是 ​​org.slf4j.Logger​​ 的实现类。



Logger的树形结构

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

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


你可以通过如下代码来获取根节点的​​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.fsk");

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

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

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



Appender

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

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

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

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

这里传的名字就对应​​logback.xml​​​中​​<appender name="FILE" />​​的name。



你也可以增添和删除​​Appender​​:

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

动态修改日志级别

修改日志级别只需要对​​ROOT​​​的​​logger​​对象修改即可:

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.fsk");

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



动态修改Appender参数

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

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

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

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

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

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

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

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

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

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

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

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



动态新增Appender

要动态新增Appender,只需要在运行期对​​ROOT​​​的​​logger​​​对象新增​​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. 动态新增、修改Logback的Appender(可实现动态调整日志级别,Appender参数)_spring_08

  3. 接下来,我需要找一个​​log​​​处打个断点,找出该​​Appender​​对象:

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

一定要用​​ROOT​​​的​​logger​​​对象,否则获取不到​​Appender​

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

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


4. 然后不断调试(这是个体力活),直到成功。


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

  1. 有​​start()​​方法的对象都需要调用
  2. 诸如​​LoggerContext​​​和​​Appender​​​等对象,在很多对象中都需要​​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);
}

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

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

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

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

public class MyApplicationStartedEventListener implements ApplicationListener<ApplicationContextInitializedEvent> {

@Override
public void onApplicationEvent(ApplicationContextInitializedEvent event) {
LogAppenderUtils.addAppender();
}
}

public static void main(String[] args) {
LogAppenderUtils.addAppender(); // 新增Appender
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是否正常增添到​​ROOT​​​的​​logger​​对象中
  2. 检查Appender对象是否正常启动,outputStream是否为空

正常情况应该是这样:

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


举报

相关推荐

0 条评论