117

使用 log4j 时,该Logger.log(Priority p, Object message)方法可用,可用于在运行时确定的日志级别记录消息。我们正在使用这个事实和这个技巧将 stderr 重定向到特定日志级别的记录器。

slf4j 没有log()我能找到的通用方法。这是否意味着没有办法实现上述内容?

4

18 回答 18

56

slf4j 无法做到这一点。

我想这个功能缺失的原因是几乎不可能Level为 slf4j 构造一个可以有效地映射到Level外观背后所有可能的日志实现中使用的(或等效的)类型。或者,设计人员认为您的用例太不寻常,无法证明支持它的开销是合理的。

关于@ripper234用例(单元测试),我认为实用的解决方案是将单元测试修改为硬连线了解 slf4j 外观背后的日志系统......在运行单元测试时。


更新

他们打算在 slf4j 2.0 中实现日志事件(具有动态日志级别)的零碎构造;见https://jira.qos.ch/browse/SLF4J-124

于 2010-04-12T12:36:51.717 回答
32

Richard Fearn 的想法是正确的,所以我根据他的骨架代码编写了完整的课程。希望它足够短,可以在这里发布。复制和粘贴享受。我可能也应该添加一些魔法咒语:“此代码已发布到公共领域”

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;
    }
}
于 2011-10-19T23:41:18.997 回答
17

尝试切换到 Logback 并使用

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

我相信这将是对 Logback 的唯一调用,并且您的其余代码将保持不变。Logback 使用 SLF4J 并且迁移将是无痛的,只需更改 xml 配置文件。

请记住在完成后重新设置日志级别。

于 2013-01-18T13:20:18.220 回答
13

您可以使用 Java 8 lambda 来实现这一点。

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);
    }
}
于 2016-03-10T17:01:06.147 回答
8

这可以通过一个enum和一个辅助方法来完成:

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

您可以添加其他变体log,例如,如果您想要 SLF4J 的 1-parameter 或 2-parameter warn/ error/etc 的通用等效项。方法。

于 2011-05-25T21:46:53.747 回答
6

无法在 sjf4j 中指定日志级别1.x。但是 slf4j 有希望2.0解决这个问题。在 2.0 中,它可能看起来像这样:

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

同时,对于 slf4j 1.x,您可以使用以下解决方法:

将此类复制到您的类路径中:

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

然后你可以像这样使用它:

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's 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");
}

这将输出如下日志:

[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

这值得么?

  • Pro它保留源代码位置(类名、方法名、行号将指向您的代码)
  • Pro您可以轻松地将变量、参数和返回类型定义为LogLevel
  • Pro您的业​​务代码保持简短且易于阅读,并且不需要额外的依赖项。

作为最小示例的源代码托管在 GitHub 上

于 2019-05-21T09:27:39.607 回答
5

任何想要一个完全兼容 SLF4J 的解决方案来解决这个问题的人都可能想查看Lidalia SLF4J Extensions - 它位于 Maven Central。

于 2014-03-28T13:14:48.447 回答
5

我只是需要这样的东西并想出了:

@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);
    }
}

用法:

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

记录器在调用期间传递,因此类信息应该没问题,并且与@Slf4j lombok 注释很好地配合使用。

于 2019-04-25T10:36:30.787 回答
4

确认答案Ondrej Skopek

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

var rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.TRACE);

你会得到结果:

2020-05-14 14:01:16,644 TRACE [] [oakcmMetrics] 测试工作者注册的指标名为 MetricName [name=bufferpool-wait-time-total, group=producer-metrics, description=appender 等待空间分配的总时间., 标签={client-id=producer-2}]

于 2020-05-14T11:07:58.823 回答
2

我使用的方法是导入 ch.qos.logback 模块,然后将 slf4j Logger 实例类型转换为 ch.qos.logback.classic.Logger。此实例包括一个 setLevel() 方法。

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

要找出可能的日志记录级别,您可以展开 ch.qos.logback 类以查看Level的所有可能值:

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

结果如下:

{
   // ...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;
}
于 2019-02-16T00:46:51.567 回答
2

SLF4J v2.0 中的 fluent API 引入了一种新方法,即Logger.makeLoggingEventBuilder(Level)用于实现预期结果。

示例代码:

public void logAMessageAtGivenLevel(Level aLevel, String aMessage) {
  Logger logger = .. // some slf4j logger of choice
  logger.makeLoggingEventBuilder(aLevel).log(aMessage);
}

NOPLoggingEventBuilder如果对给定的 logger 禁用了记录器,默认实现将返回单例实例Level。正如名称 NOP 所示,该LoggingEventBuilder接口的实现什么也不做,为禁用的日志消息保留纳秒执行时间。

于 2021-06-29T21:03:59.900 回答
1

slf4j API 无法动态更改日志级别,但您可以自己配置 logback(如果您使用它)。在这种情况下,为您的记录器创建工厂类并使用您需要的配置实现根记录器。

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 
// e.g. 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);

配置根记录器后(一次就足够了),您可以委托获取新记录器

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

记住使用相同的loggerContext.

更改日志级别很容易使用从loggerContext.

root.setLevel(Level.DEBUG);
于 2019-08-18T08:35:03.057 回答
0

我刚刚遇到了类似的需求。在我的例子中,slf4j 配置了 java 日志适配器(jdk14 之一)。使用以下代码片段,我设法在运行时更改了调试级别:

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");
于 2014-06-11T03:33:07.987 回答
0

我可以通过首先请求 SLF4J Logger 实例然后设置绑定级别来为 JDK14 绑定执行此操作——您可以尝试为 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);
}
于 2017-06-21T00:50:05.423 回答
0

根据 massimo virgilio 的回答,我还设法使用自省来使用 slf4j-log4j 做到这一点。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());
}
于 2016-02-24T12:39:51.013 回答
0

这是一个 lambda 解决方案,其用户友好性不如@Paul Croarkin 的一种方式(该级别有效地通过了两次)。但我认为(a)用户应该通过 Logger;(b) AFAIU 最初的问题不是要求在应用程序中的任何地方提供一种方便的方式,而只是在图书馆内很少使用的情况。

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

由于 slf4j允许在 varargs 参数中使用 Throwable (应记录其堆栈跟踪),我认为log除了(String, Object[]).

于 2016-07-29T09:30:45.020 回答
-2

使用 java introspection 你可以做到这一点,例如:

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

}
于 2012-04-27T15:45:10.017 回答
-6

不,它有许多方法,info()、debug()、warn() 等(这取代了优先级字段)

查看http://www.slf4j.org/api/org/slf4j/Logger.html以获得完整的 Logger api。

于 2010-04-12T11:42:45.473 回答