如何以编程方式更改 logback 的根日志记录级别

发布于 2024-09-25 16:06:06 字数 522 浏览 0 评论 0原文

我有以下 logback.xml 文件:

<configuration debug="true"> 

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
<encoder>
  <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
  <appender-ref ref="STDOUT" />
</root>
</configuration>

现在,在发生特定事件时,我想以编程方式将根记录器的级别从 debug 更改为 error。我不能使用变量替换,我必须在代码中执行此操作。

怎么办呢?谢谢。

I have the following logback.xml file:

<configuration debug="true"> 

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
<encoder>
  <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
  <appender-ref ref="STDOUT" />
</root>
</configuration>

Now, upon the occurrence of a specific event, I want to programmatically change the level of the root logger from debug to error. I can't use variable substitution, it is mandatory that I do this within the code.

How can it be done ? Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

简单爱 2024-10-02 16:06:06

试试这个:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

Logger root = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.INFO);

请注意,您还可以告诉 logback 定期扫描您的配置文件,如下所示:

<configuration scan="true" scanPeriod="30 seconds" > 
  ...
</configuration> 

Try this:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

Logger root = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.INFO);

Note that you can also tell logback to periodically scan your config file like this:

<configuration scan="true" scanPeriod="30 seconds" > 
  ...
</configuration> 
等风也等你 2024-10-02 16:06:06

使用 logback 1.1.3 我必须执行以下操作(Scala 代码):

import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory    
...
val root: Logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[Logger]

using logback 1.1.3 I had to do the following (Scala code):

import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory    
...
val root: Logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[Logger]
少女的英雄梦 2024-10-02 16:06:06

我假设您正在使用 logback (来自配置文件)。

logback 手册,我看到

Logger rootLogger = LoggerFactory.getLogger(org.slf4j .Logger.ROOT_LOGGER_NAME);

也许这可以帮助您更改该值?

I assume you are using logback (from the configuration file).

From logback manual, I see

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

Perhaps this can help you change the value?

迎风吟唱 2024-10-02 16:06:06

我认为您可以使用 MDC 以编程方式更改日志记录级别。下面的代码是更改当前线程上的日志记录级别的示例。此方法不会创建对 logback 实现的依赖性(SLF4J API 包含 MDC)。

<configuration>
  <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
    <Key>LOG_LEVEL</Key>
    <DefaultThreshold>DEBUG</DefaultThreshold>
    <MDCValueLevelPair>
      <value>TRACE</value>
      <level>TRACE</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>DEBUG</value>
      <level>DEBUG</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>INFO</value>
      <level>INFO</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>WARN</value>
      <level>WARN</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>ERROR</value>
      <level>ERROR</level>
    </MDCValueLevelPair>
  </turboFilter>
  ......
</configuration>
MDC.put("LOG_LEVEL", "INFO");

I think you can use MDC to change logging level programmatically. The code below is an example to change logging level on current thread. This approach does not create dependency to logback implementation (SLF4J API contains MDC).

<configuration>
  <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
    <Key>LOG_LEVEL</Key>
    <DefaultThreshold>DEBUG</DefaultThreshold>
    <MDCValueLevelPair>
      <value>TRACE</value>
      <level>TRACE</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>DEBUG</value>
      <level>DEBUG</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>INFO</value>
      <level>INFO</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>WARN</value>
      <level>WARN</level>
    </MDCValueLevelPair>
    <MDCValueLevelPair>
      <value>ERROR</value>
      <level>ERROR</level>
    </MDCValueLevelPair>
  </turboFilter>
  ......
</configuration>
MDC.put("LOG_LEVEL", "INFO");
听,心雨的声音 2024-10-02 16:06:06

正如其他人所指出的,您只需创建 mockAppender ,然后创建一个 LoggingEvent 实例,该实例本质上侦听 mockAppender 内注册/发生的日志记录事件。

这是它在测试中的样子:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;

@RunWith(MockitoJUnitRunner.class)
public class TestLogEvent {

// your Logger
private Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

// here we mock the appender
@Mock
private Appender<ILoggingEvent> mockAppender;

// Captor is generic-ised with ch.qos.logback.classic.spi.LoggingEvent
@Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

/**
 * set up the test, runs before each test
 */
@Before
public void setUp() {
    log.addAppender(mockAppender);
}

/**
 * Always have this teardown otherwise we can stuff up our expectations. 
 * Besides, it's good coding practise
 */
@After
public void teardown() {
    log.detachAppender(mockAppender);
}


// Assuming this is your method
public void yourMethod() {
    log.info("hello world");
}

@Test
public void testYourLoggingEvent() {

    //invoke your method
    yourMethod();

    // now verify our logging interaction
    // essentially appending the event to mockAppender
    verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());

    // Having a generic captor means we don't need to cast
    final LoggingEvent loggingEvent = captorLoggingEvent.getValue();

    // verify that info log level is called
    assertThat(loggingEvent.getLevel(), is(Level.INFO));

    // Check the message being logged is correct
    assertThat(loggingEvent.getFormattedMessage(), containsString("hello world"));
}
}

As pointed out by others, you simply create mockAppender and then create a LoggingEvent instance which essentially listens to the logging event registered/happens inside mockAppender.

Here is how it looks like in test:

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;

@RunWith(MockitoJUnitRunner.class)
public class TestLogEvent {

// your Logger
private Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

// here we mock the appender
@Mock
private Appender<ILoggingEvent> mockAppender;

// Captor is generic-ised with ch.qos.logback.classic.spi.LoggingEvent
@Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

/**
 * set up the test, runs before each test
 */
@Before
public void setUp() {
    log.addAppender(mockAppender);
}

/**
 * Always have this teardown otherwise we can stuff up our expectations. 
 * Besides, it's good coding practise
 */
@After
public void teardown() {
    log.detachAppender(mockAppender);
}


// Assuming this is your method
public void yourMethod() {
    log.info("hello world");
}

@Test
public void testYourLoggingEvent() {

    //invoke your method
    yourMethod();

    // now verify our logging interaction
    // essentially appending the event to mockAppender
    verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());

    // Having a generic captor means we don't need to cast
    final LoggingEvent loggingEvent = captorLoggingEvent.getValue();

    // verify that info log level is called
    assertThat(loggingEvent.getLevel(), is(Level.INFO));

    // Check the message being logged is correct
    assertThat(loggingEvent.getFormattedMessage(), containsString("hello world"));
}
}
另类 2024-10-02 16:06:06

所以我基本上同意最上面的答案,但发现它在 2023 年略有不同。我发现以下工作

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;

final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = loggerContext.exists(org.slf4j.Logger.ROOT_LOGGER_NAME); // give it your logger name
final Level newLevel = Level.toLevel("ERROR", null); // give it your log level
logger.setLevel(newLevel);

值得注意的主要区别是我必须使用 getILoggerFactory 而不是 getLogger代码>.要查看与此相关的其他帖子,请参阅以编程方式更改 Log4j2 中的日志级别 或者如果您希望每个请求都能执行此操作,请参阅 更改每个请求的 log4j 中的优先级

So I mostly agree with the top answer but found it to be slightly different in 2023. I found that the following works

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;

final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = loggerContext.exists(org.slf4j.Logger.ROOT_LOGGER_NAME); // give it your logger name
final Level newLevel = Level.toLevel("ERROR", null); // give it your log level
logger.setLevel(newLevel);

The primary difference of note is instead of getLogger I had to use getILoggerFactory. To see additional related posts to this see Programmatically change log level in Log4j2 or if you want to be able to this per request see Change priority level in log4j per request

把回忆走一遍 2024-10-02 16:06:06

另一种方法是使用 Logback TurboFilter。这可以给我们更多的控制权。

更改记录器本身的级别只能让我们打开或关闭特定的记录器。如果我们想要为 user:123team:456 获取 com.example.billing 中的所有内容,该怎么办? code> 模块在接下来的 90 分钟 中,但留下来对其他一切都是 WARN

如果我们使用 TurboFilter,我们就可以访问 MDC,从中获取用户上下文。我们可以访问动态配置系统来获取要匹配的用户的规则。

这就是 https://github.com/prefab-cloud/prefab-cloud-java 使用 prefab.cloud 作为动态配置和 UI。

简化:

public class PrefabMDCTurboFilter extends TurboFilter {

  private final ConfigClient configClient;

  PrefabMDCTurboFilter(ConfigClient configClient) {
    this.configClient = configClient;
  }

  public static void install(ConfigClient configClient) {
    PrefabMDCTurboFilter filter = new PrefabMDCTurboFilter(configClient);
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    loggerContext.addTurboFilter(filter);
  }

  @Override
  public FilterReply decide(
    Marker marker,
    Logger logger,
    Level level,
    String s,
    Object[] objects,
    Throwable throwable
  ) {
      Optional<Prefab.LogLevel> loglevelMaybe = configClient.getLogLevelFromStringMap(
        logger.getName(),
        MDC.getCopyOfContextMap()
      );
      if (loglevelMaybe.isPresent()) {
        Level calculatedMinLogLevelToAccept = LogbackLevelMapper.LEVEL_MAP.get(
          loglevelMaybe.get()
        );
        if (level.isGreaterOrEqual(calculatedMinLogLevelToAccept)) {
          return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
      }
      return FilterReply.NEUTRAL;
  }
}

Another approach is to use a Logback TurboFilter. This can give us more control.

Changing the level of the logger itself only let's us turn a particular logger on or off. What if we want to get DEBUG for user:123 or team:456 for everything in the com.example.billing module for the next 90 minutes but stay are WARN for everything else?

If we use a TurboFilter, we have access to the MDC where we can get the user context. And we can access a dynamic config system to get the rules for which users to match.

This is what https://github.com/prefab-cloud/prefab-cloud-java does using prefab.cloud as the dynamic config and UI.

Simplified:

public class PrefabMDCTurboFilter extends TurboFilter {

  private final ConfigClient configClient;

  PrefabMDCTurboFilter(ConfigClient configClient) {
    this.configClient = configClient;
  }

  public static void install(ConfigClient configClient) {
    PrefabMDCTurboFilter filter = new PrefabMDCTurboFilter(configClient);
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    loggerContext.addTurboFilter(filter);
  }

  @Override
  public FilterReply decide(
    Marker marker,
    Logger logger,
    Level level,
    String s,
    Object[] objects,
    Throwable throwable
  ) {
      Optional<Prefab.LogLevel> loglevelMaybe = configClient.getLogLevelFromStringMap(
        logger.getName(),
        MDC.getCopyOfContextMap()
      );
      if (loglevelMaybe.isPresent()) {
        Level calculatedMinLogLevelToAccept = LogbackLevelMapper.LEVEL_MAP.get(
          loglevelMaybe.get()
        );
        if (level.isGreaterOrEqual(calculatedMinLogLevelToAccept)) {
          return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
      }
      return FilterReply.NEUTRAL;
  }
}
等往事风中吹 2024-10-02 16:06:06

您可以通过 LogManager.getLogManager().updateConfiguration() 方法拦截日志记录配置。只需检查包含 .level 后缀的配置属性,并将默认值替换为 Level.ALL 值。

public class Application {

    // Static initializer is executed when class is loaded by a class loader
    static {
        try {
            java.util.logging.LogManager.getLogManager().updateConfiguration(propertyName -> {
                // Check for the log level property
                if (propertyName.contains(".level")) {
                    // Level = ALL => logs all messages
                    return (oldValue, newValue) -> java.util.logging.Level.ALL.getName();
                } else {
                    // Identity mapper for other propeties
                    return (oldValue, newValue) -> newValue;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

You may intercept logging configuration via the LogManager.getLogManager().updateConfiguration() method. Just check for configuration properties which contains .level suffix, and replace default value with Level.ALL value.

public class Application {

    // Static initializer is executed when class is loaded by a class loader
    static {
        try {
            java.util.logging.LogManager.getLogManager().updateConfiguration(propertyName -> {
                // Check for the log level property
                if (propertyName.contains(".level")) {
                    // Level = ALL => logs all messages
                    return (oldValue, newValue) -> java.util.logging.Level.ALL.getName();
                } else {
                    // Identity mapper for other propeties
                    return (oldValue, newValue) -> newValue;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
清醇 2024-10-02 16:06:06

我似乎成功地做到了

org.jboss.logmanager.Logger logger = org.jboss.logmanager.Logger.getLogger("");
logger.setLevel(java.util.logging.Level.ALL);

然后从 netty 获取详细的日志记录,以下已经完成了

org.slf4j.impl.SimpleLogger.setLevel(org.slf4j.impl.SimpleLogger.TRACE);

I seem to be having success doing

org.jboss.logmanager.Logger logger = org.jboss.logmanager.Logger.getLogger("");
logger.setLevel(java.util.logging.Level.ALL);

Then to get detailed logging from netty, the following has done it

org.slf4j.impl.SimpleLogger.setLevel(org.slf4j.impl.SimpleLogger.TRACE);
撩人痒 2024-10-02 16:06:06

这是一个控制器

@RestController
@RequestMapping("/loggers")
public class LoggerConfigController {

private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PetController.class);

@GetMapping()
public List<LoggerDto> getAllLoggers() throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    List<Logger> loggers = loggerContext.getLoggerList();
    
    List<LoggerDto> loggerDtos = new ArrayList<>();
    
    for (Logger logger : loggers) {
        
        if (Objects.isNull(logger.getLevel())) {
            continue;
        }
        
        LoggerDto dto = new LoggerDto(logger.getName(), logger.getLevel().levelStr);
        loggerDtos.add(dto);
    }
    
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("All loggers retrieved. Total of {} loggers found", loggerDtos.size());
    }
    
    return loggerDtos;
}

@PutMapping
public boolean updateLoggerLevel(
        @RequestParam String name, 
        @RequestParam String level
)throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    Logger logger = loggerContext.getLogger(name);
    
    if (Objects.nonNull(logger) && StringUtils.isNotBlank(level)) {
        
        switch (level) {
            case "INFO":
                logger.setLevel(Level.INFO);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "DEBUG":
                logger.setLevel(Level.DEBUG);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "ALL":
                logger.setLevel(Level.ALL);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "OFF":
            default: 
                logger.setLevel(Level.OFF);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
        }
    }
    
    return true;
}

}

Here's a controller

@RestController
@RequestMapping("/loggers")
public class LoggerConfigController {

private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PetController.class);

@GetMapping()
public List<LoggerDto> getAllLoggers() throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    List<Logger> loggers = loggerContext.getLoggerList();
    
    List<LoggerDto> loggerDtos = new ArrayList<>();
    
    for (Logger logger : loggers) {
        
        if (Objects.isNull(logger.getLevel())) {
            continue;
        }
        
        LoggerDto dto = new LoggerDto(logger.getName(), logger.getLevel().levelStr);
        loggerDtos.add(dto);
    }
    
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("All loggers retrieved. Total of {} loggers found", loggerDtos.size());
    }
    
    return loggerDtos;
}

@PutMapping
public boolean updateLoggerLevel(
        @RequestParam String name, 
        @RequestParam String level
)throws CoreException {
    
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    Logger logger = loggerContext.getLogger(name);
    
    if (Objects.nonNull(logger) && StringUtils.isNotBlank(level)) {
        
        switch (level) {
            case "INFO":
                logger.setLevel(Level.INFO);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "DEBUG":
                logger.setLevel(Level.DEBUG);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "ALL":
                logger.setLevel(Level.ALL);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
                break;
                
            case "OFF":
            default: 
                logger.setLevel(Level.OFF);
                LOGGER.info("Logger [{}] updated to [{}]", name, level);
        }
    }
    
    return true;
}

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文