Setting runtime message log level in slf4j

When using Logger.log(Priority p, Object message) method is available and can be used to log messages at the log level defined at runtime. We use this fact and this advice to redirect stderr to the logger at a specific log level.

slf4j does not have a common log() method that I can find. Does this mean that there is no way to implement the above?

+89
java logging slf4j log4j
Apr 12 2018-10-12T00:
source share
16 answers

There is no way to do this with slf4j .

I assume that the reason this functionality is missing is that it is almost impossible to create a Level type for slf4j that can be effectively matched with a Level type (or equivalent) used in all possible logging implementations behind the facade. As an alternative, the designers decided that your use case is too unusual to justify the cost of supporting it.

Regarding the use case of @ ripper234 (unit testing), I think the pragmatic solution is to modify unit tests to get a solid idea of ​​which logging system is behind the slf4j facade ... when running unit tests.

+41
Apr 12 2018-12-12T00:
source share

Richard Fern has the right idea, so I wrote a complete class based on his skeleton code. It is hopefully short enough to post here. Copy and paste for fun. Perhaps I should add a magic spell: "This code has been released into the public domain"

 import org.slf4j.Logger; public class LogLevel { /** * Allowed levels, as an enum. Import using "import [package].LogLevel.Level" * Every logging implementation has something like this except SLF4J. */ public static enum Level { TRACE, DEBUG, INFO, WARN, ERROR } /** * This class cannot be instantiated, why would you want to? */ private LogLevel() { // Unreachable } /** * Log at the specified level. If the "logger" is null, nothing is logged. * If the "level" is null, nothing is logged. If the "txt" is null, * behaviour depends on the SLF4J implementation. */ public static void log(Logger logger, Level level, String txt) { if (logger != null && level != null) { switch (level) { case TRACE: logger.trace(txt); break; case DEBUG: logger.debug(txt); break; case INFO: logger.info(txt); break; case WARN: logger.warn(txt); break; case ERROR: logger.error(txt); break; } } } /** * Log at the specified level. If the "logger" is null, nothing is logged. * If the "level" is null, nothing is logged. If the "format" or the "argArray" * are null, behaviour depends on the SLF4J-backing implementation. */ public static void log(Logger logger, Level level, String format, Object[] argArray) { if (logger != null && level != null) { switch (level) { case TRACE: logger.trace(format, argArray); break; case DEBUG: logger.debug(format, argArray); break; case INFO: logger.info(format, argArray); break; case WARN: logger.warn(format, argArray); break; case ERROR: logger.error(format, argArray); break; } } } /** * Log at the specified level, with a Throwable on top. If the "logger" is null, * nothing is logged. If the "level" is null, nothing is logged. If the "format" or * the "argArray" or the "throwable" are null, behaviour depends on the SLF4J-backing * implementation. */ public static void log(Logger logger, Level level, String txt, Throwable throwable) { if (logger != null && level != null) { switch (level) { case TRACE: logger.trace(txt, throwable); break; case DEBUG: logger.debug(txt, throwable); break; case INFO: logger.info(txt, throwable); break; case WARN: logger.warn(txt, throwable); break; case ERROR: logger.error(txt, throwable); break; } } } /** * Check whether a SLF4J logger is enabled for a certain loglevel. * If the "logger" or the "level" is null, false is returned. */ public static boolean isEnabledFor(Logger logger, Level level) { boolean res = false; if (logger != null && level != null) { switch (level) { case TRACE: res = logger.isTraceEnabled(); break; case DEBUG: res = logger.isDebugEnabled(); break; case INFO: res = logger.isInfoEnabled(); break; case WARN: res = logger.isWarnEnabled(); break; case ERROR: res = logger.isErrorEnabled(); break; } } return res; } } 
+25
Oct 19 2018-11-11T00:
source share

Try switching to Logback and use

 ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); rootLogger.setLevel(Level.toLevel("info")); 

I believe this will be the only Logback call, and the rest of your code will remain unchanged. Logback uses SLF4J, and the migration will be painless, you only need to change the xml configuration files.

Remember to set the log level after completion.

+13
Jan 18 '13 at 13:20
source share

You can implement this using Java 8 lambdas.

 import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; public class LevelLogger { private static final Logger LOGGER = LoggerFactory.getLogger(LevelLogger.class); private static final Map<Level, LoggingFunction> map; static { map = new HashMap<>(); map.put(Level.TRACE, (o) -> LOGGER.trace(o)); map.put(Level.DEBUG, (o) -> LOGGER.debug(o)); map.put(Level.INFO, (o) -> LOGGER.info(o)); map.put(Level.WARN, (o) -> LOGGER.warn(o)); map.put(Level.ERROR, (o) -> LOGGER.error(o)); } public static void log(Level level, String s) { map.get(level).log(s); } @FunctionalInterface private interface LoggingFunction { public void log(String arg); } } 
+11
Mar 10 '16 at 17:01
source share

This can be done using enum and a helper method:

 enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR, } public static void log(Logger logger, LogLevel level, String format, Object[] argArray) { switch (level) { case TRACE: logger.trace(format, argArray); break; case DEBUG: logger.debug(format, argArray); break; case INFO: logger.info(format, argArray); break; case WARN: logger.warn(format, argArray); break; case ERROR: logger.error(format, argArray); break; } } // example usage: private static final Logger logger = ... final LogLevel level = ... log(logger, level, "Something bad happened", ...); 

You can add other log options, say if you need the general equivalents of the SLF4J 1-parameter or 2-parameter warn / error / etc. Methods

+6
May 25 '11 at 21:46
source share

Anyone who wants a fully compatible SLF4J solution to this problem can try Lidalia SLF4J Extensions - it is on Maven Central.

+5
Mar 28 '14 at 13:14
source share

I just needed something similar and came up with:

 @RequiredArgsConstructor //lombok annotation public enum LogLevel{ TRACE(l -> l::trace), INFO (l -> l::info), WARN (l -> l::warn), ERROR(l -> l::error); private final Function<Logger, Consumer<String>> function; public void log(Logger logger, String message) { function.apply(logger).accept(message); } } 

using:

  LogLevel level = LogLevel.TRACE; level.log(logger, "message"); 

Logger is passed during the call, so class information should be fine, and it works great with @ Slf4j lombok annotation.

+2
Apr 25 '19 at 10:36
source share

It is not possible to determine the level of logging in sjf4j 1.x out of the box. But there is hope that slf4j 2.0 will solve the problem . In 2.0, it might look like this:

 // POTENTIAL 2.0 SOLUTION import org.slf4j.helpers.Util; import static org.slf4j.spi.LocationAwareLogger.*; // does not work with slf4j 1.x Util.log(logger, DEBUG_INT, "hello world!"); 

Meanwhile, for slf4j 1.x you can use this workaround:

Copy this class into your classpath:

 import org.slf4j.Logger; import java.util.function.Function; public enum LogLevel { TRACE(l -> l::trace, Logger::isTraceEnabled), DEBUG(l -> l::debug, Logger::isDebugEnabled), INFO(l -> l::info, Logger::isInfoEnabled), WARN(l -> l::warn, Logger::isWarnEnabled), ERROR(l -> l::error, Logger::isErrorEnabled); interface LogMethod { void log(String format, Object... arguments); } private final Function<Logger, LogMethod> logMethod; private final Function<Logger, Boolean> isEnabledMethod; LogLevel(Function<Logger, LogMethod> logMethod, Function<Logger, Boolean> isEnabledMethod) { this.logMethod = logMethod; this.isEnabledMethod = isEnabledMethod; } public LogMethod prepare(Logger logger) { return logMethod.apply(logger); } public boolean isEnabled(Logger logger) { return isEnabledMethod.apply(logger); } } 

Then you can use it like this:

 Logger logger = LoggerFactory.getLogger(Application.class); LogLevel level = LogLevel.ERROR; level.prepare(logger).log("It works!"); // just message, without parameter level.prepare(logger).log("Hello {}!", "world"); // with slf4j parameter replacing try { throw new RuntimeException("Oops"); } catch (Throwable t) { level.prepare(logger).log("Exception", t); } if (level.isEnabled(logger)) { level.prepare(logger).log("logging is enabled"); } 

This will output the log as follows:

 [main] ERROR Application - It works! [main] ERROR Application - Hello world! [main] ERROR Application - Exception java.lang.RuntimeException: Oops at Application.main(Application.java:14) [main] ERROR Application - logging is enabled 

Is it worth it?

  • Pro It saves the location of the source code (class names, method names, line numbers will point to your code)
  • Pro You can easily define variables , parameters and return types as LogLevel
  • Pro Your business code remains short and easy to read, no additional dependencies required.

The source code, as a minimal example, is hosted on GitHub .

+1
May 21 '19 at 9:27
source share

I just encountered a similar need. In my case, slf4j is configured using the java logging adapter (jdk14 one). Using the following code snippet, I was able to change the debugging level at runtime:

 Logger logger = LoggerFactory.getLogger("testing"); java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger("testing"); julLogger.setLevel(java.util.logging.Level.FINE); logger.debug("hello world"); 
0
Jun 11
source share

Based on the answer of massimo virgilio, I also managed to do this with slf4j-log4j using introspection. NTN.

 Logger LOG = LoggerFactory.getLogger(MyOwnClass.class); org.apache.logging.slf4j.Log4jLogger LOGGER = (org.apache.logging.slf4j.Log4jLogger) LOG; try { Class loggerIntrospected = LOGGER.getClass(); Field fields[] = loggerIntrospected.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { String fieldName = fields[i].getName(); if (fieldName.equals("logger")) { fields[i].setAccessible(true); org.apache.logging.log4j.core.Logger loggerImpl = (org.apache.logging.log4j.core.Logger) fields[i].get(LOGGER); loggerImpl.setLevel(Level.DEBUG); } } } catch (Exception e) { System.out.println("ERROR :" + e.getMessage()); } 
0
Feb 24 '16 at 12:39
source share

Here the lambda solution is not as convenient as @Paul Croarkin in one way (the level is effectively transmitted twice). But I think: (a) the user must go through Logger; and (b) AFAIU the original question does not require a convenient way to be ubiquitous in the application, only the situation with several usages inside the library.

 package test.lambda; import java.util.function.*; import org.slf4j.*; public class LoggerLambda { private static final Logger LOG = LoggerFactory.getLogger(LoggerLambda.class); private LoggerLambda() {} public static void log(BiConsumer<? super String, ? super Object[]> logFunc, Supplier<Boolean> logEnabledPredicate, String format, Object... args) { if (logEnabledPredicate.get()) { logFunc.accept(format, args); } } public static void main(String[] args) { int a = 1, b = 2, c = 3; Throwable e = new Exception("something went wrong", new IllegalArgumentException()); log(LOG::info, LOG::isInfoEnabled, "a = {}, b = {}, c = {}", a, b, c); // warn(String, Object...) instead of warn(String, Throwable), but prints stacktrace nevertheless log(LOG::warn, LOG::isWarnEnabled, "error doing something: {}", e, e); } } 

Since slf4j allows Throwable (stack stack trace) in the varargs parameter , I think there is no need to overload the log for other consumers than (String, Object[]) .

0
Jul 29 '16 at 9:30
source share

I was able to do this to bind JDK14 by first querying the Logger SLF4J instance, and then setting the level on the bind - you can try this to bind Log4J.

 private void setLevel(Class loggerClass, java.util.logging.Level level) { org.slf4j.LoggerFactory.getLogger(loggerClass); java.util.logging.Logger.getLogger(loggerClass.getName()).setLevel(level); } 
0
Jun 21 '17 at 0:50
source share

The method I use is to import the ch.qos.logback modules and then convert the slf4j logger instance type to ch.qos.logback.classic.Logger. This instance includes the setLevel () method.

 import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; Logger levelSet = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); // Now you can set the desired logging-level levelSet.setLevel( Level.OFF ); 

To find out the possible logging levels, you can parse the ch.qos.logback class to see all the possible values ​​for the level :

 prompt$ javap -cp logback-classic-1.2.3.jar ch.qos.logback.classic.Level 

The results are as follows:

 { // ...skipping public static final ch.qos.logback.classic.Level OFF; public static final ch.qos.logback.classic.Level ERROR; public static final ch.qos.logback.classic.Level WARN; public static final ch.qos.logback.classic.Level INFO; public static final ch.qos.logback.classic.Level DEBUG; public static final ch.qos.logback.classic.Level TRACE; public static final ch.qos.logback.classic.Level ALL; } 
0
Feb 16 '19 at 0:46
source share

It is not possible to dynamically change the log level in the slf4j API, but you can configure logback (if you use it) yourself. In this case, create a factory class for your registrar and implement the root registrar with the configuration you need.

 LoggerContext loggerContext = new LoggerContext(); ch.qos.logback.classic.Logger root = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); // Configure appender final TTLLLayout layout = new TTLLLayout(); layout.start(); // default layout of logging messages (the form that message displays // eg 10:26:49.113 [main] INFO com.yourpackage.YourClazz - log message final LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>(); encoder.setCharset(StandardCharsets.UTF_8); encoder.setLayout(layout); final ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>(); appender.setContext(loggerContext); appender.setEncoder(encoder); appender.setName("console"); appender.start(); root.addAppender(appender); 

After setting up the root registrar (only once is enough) you can delegate the receipt of a new registrar with.

 final ch.qos.logback.classic.Logger logger = loggerContext.getLogger(clazz); 

Remember to use the same loggerContext .

Changing the log level is easy with a root logger derived from loggerContext .

 root.setLevel(Level.DEBUG); 
0
Aug 18 '19 at 8:35
source share

using java introspection you can do this, for example:

 private void changeRootLoggerLevel(int level) { if (logger instanceof org.slf4j.impl.Log4jLoggerAdapter) { try { Class loggerIntrospected = logger.getClass(); Field fields[] = loggerIntrospected.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { String fieldName = fields[i].getName(); if (fieldName.equals("logger")) { fields[i].setAccessible(true); org.apache.log4j.Logger loggerImpl = (org.apache.log4j.Logger) fields[i] .get(logger); if (level == DIAGNOSTIC_LEVEL) { loggerImpl.setLevel(Level.DEBUG); } else { loggerImpl.setLevel(org.apache.log4j.Logger.getRootLogger().getLevel()); } // fields[i].setAccessible(false); } } } catch (Exception e) { org.apache.log4j.Logger.getLogger(LoggerSLF4JImpl.class).error("An error was thrown while changing the Logger level", e); } } } 
-2
Apr 27 '12 at 15:45
source share

no, it has several methods: info (), debug (), warn (), etc. (this replaces the priority field)

see http://www.slf4j.org/api/org/slf4j/Logger.html for a full logger.

-6
Apr 12 2018-10-12T00:
source share



All Articles