Setting log level of message at runtime in slf4j

2019-01-13 09:07发布

When using log4j, the Logger.log(Priority p, Object message) method is available and can be used to log a message at a log level determined at runtime. We're using this fact and this tip to redirect stderr to a logger at a specific log level.

slf4j doesn't have a generic log() method that I can find. Does that mean there's no way to implement the above?

12条回答
小情绪 Triste *
2楼-- · 2019-01-13 09:44

I was able to do this for the JDK14 binding by first requesting the SLF4J Logger instance and then setting the level on the binding -- you may try this for the Log4J binding.

private void setLevel(Class loggerClass, java.util.logging.Level level) {
  org.slf4j.LoggerFactory.getLogger(loggerClass);
  java.util.logging.Logger.getLogger(loggerClass.getName()).setLevel(level);
}
查看更多
兄弟一词,经得起流年.
3楼-- · 2019-01-13 09:48

Here's a lambda solution not as user-friendly as @Paul Croarkin's in one way (the level is effectively passed twice). But I think (a) the user should pass the Logger; and (b) AFAIU the original question was not asking for a convenient way for everywhere in the application, only a situation with few usages inside a 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 a Throwable (whose stack trace should be logged) inside the varargs param, I think there is no need for overloading the log helper method for other consumers than (String, Object[]).

查看更多
萌系小妹纸
4楼-- · 2019-01-13 09:53

using java introspection you can do it, 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);
        }
    }

}
查看更多
在下西门庆
5楼-- · 2019-01-13 09:54

I have just encountered a similar need. In my case, slf4j is configured with the java logging adapter (the jdk14 one). Using the following code snippet I have managed to change the debug 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");
查看更多
【Aperson】
6楼-- · 2019-01-13 09:55

This can be done with an 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 could add other variants of log, say if you wanted generic equivalents of SLF4J's 1-parameter or 2-parameter warn/error/etc. methods.

查看更多
时光不老,我们不散
7楼-- · 2019-01-13 09:55

Based on the answer of massimo virgilio, I've also managed to do it with slf4j-log4j using introspection. HTH.

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());
}
查看更多
登录 后发表回答