在slf4j中设置运行时消息的日志级别

Edw*_*ale 91 java logging log4j slf4j

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

slf4j没有log()我能找到的通用方法.这是否意味着无法实现上述目标?

Ste*_*n C 43

没有办法做到这一点slf4j.

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

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

  • WTF?SLF4J被誉为java日志记录的未来,这个简单的用例不受支持?(在整合SLF4J之后我尝试做的第一件事就是帮助减少单元测试中的混乱). (75认同)
  • @ ripper234 - 我不认为你的bug与scompt.com的原始问题解决了同样的问题.您询问了如何通过SLF4J API配置*底层日志系统*的级别.scompt.com之后是SLF4J API中的通用"log"方法,它将消息*的日志记录级别*作为参数. (9认同)
  • 真的没有必要的映射."org.slf4j.Logger"中的方法已经隐式定义了五个级别:debug,error,info,trace,warn. (7认同)
  • 发布此问题:http://bugzilla.slf4j.org/show_bug.cgi?id = 206 (6认同)
  • RFE链接不再解决.相关链接现在是:http://jira.qos.ch/browse/SLF4J-124和http://jira.qos.ch/browse/SLF4J-197 ......两者都已关闭.阅读基本原理的评论. (3认同)

Dav*_*fer 25

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为这个实现会增加一个不想要的变化。当您使用调用 logger.info(...) 时,记录器可以访问调用者类和方法,并且可以自动将其添加到日志条目中。现在,通过这个实现,调用 log(logger, level, txt) 将生成一个日志条目,该条目将始终具有相同的调用者:Loglevel.log。我对吗? (2认同)

Αλέ*_*κος 12

尝试切换到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"));
Run Code Online (Sandbox Code Playgroud)

我相信这将是对Logback的唯一调用,其余代码将保持不变.Logback使用SLF4J,迁移将毫不费力,只需要更改xml配置文件.

记得在完成后重新设置日志级别.

  • -1这不是这个问题的内容. (4认同)
  • @AlexandrosGelbessis你应该重读这个问题.有人要求提供一种方法,可以在任何级别以编程方式记录一条日志消息.您正在更改所有消息的根记录器级别,而不仅仅是一个消息. (3认同)
  • 这是我的第一个-1,谢谢.我相信你错了.Logback使用SLF4J,因此答案是相关的. (2认同)

Pau*_*kin 12

您可以使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 还请注意,当您使用该解决方案时,将不会记录进行实际日志记录的类(因为记录器是使用`LevelLogger`初始化的),这不是一件好事,因为它通常是非常有用的信息。 (3认同)

sla*_*dan 7

无法在 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!");
Run Code Online (Sandbox Code Playgroud)

同时,对于 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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");
}
Run Code Online (Sandbox Code Playgroud)

这将输出如下日志:

// 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!");
Run Code Online (Sandbox Code Playgroud)

这值得么?

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

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


Ric*_*arn 6

这可以使用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", ...);
Run Code Online (Sandbox Code Playgroud)

您可以添加的其他变体log,比方说,如果你想SLF4J的1参数或2个参数的一般等价物warn/ error的/ etc.方法.

  • SLF4J的目的是为不同的日志框架提供抽象.如果那个抽象没有提供你所需要的,你别无选择,只能编写一个帮助方法.唯一的另一种选择是提供一个类似于我对SLF4J项目的答案中的方法. (5认同)
  • 没错,但是slf4j的目的不是要写日志包装器. (3认同)
  • 我同意,但在这种情况下,有一些警告,例如您将无法再提供文件和行号,除非您为此实施了另一种解决方法。在这种情况下,我会坚持使用 log4j,直到框架支持该功能 - 最终通过扩展实现,请参阅 Robert Elliot 的最新答案。 (2认同)

Cek*_*eki 6

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

示例代码:

public void logAMessageAtGivenLevel(Level aLevel, String aMessage) {
  Logger logger = .. // some slf4j logger of choice
  logger.atLevel(aLevel).log(aMessage);
}
Run Code Online (Sandbox Code Playgroud)

NOPLoggingEventBuilder如果给定的记录器被禁用,默认实现将返回单例实例LevelLoggingEventBuilder正如名称 NOP 所示,该接口的这种实现不执行任何操作,而是为禁用的日志消息保留纳秒执行时间。

  • 繁荣!https://jira.qos.ch/browse/SLF4J-124?focusedCommentId=20911&amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-20911 `atLevel` :) 谢谢天哪! (3认同)

Rob*_*iot 5

任何想要完全解决此问题的SLF4J兼容解决方案的人都可以查看Lidalia SLF4J扩展 - 它位于Maven Central.


小智 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    LogLevel level = LogLevel.TRACE;
    level.log(logger, "message");
Run Code Online (Sandbox Code Playgroud)

Logger 在调用期间传递,因此类信息应该没问题,并且它与 @Slf4j lombok 注释配合得很好。


Tor*_*ino 5

确认答案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);
Run Code Online (Sandbox Code Playgroud)

你会得到结果:

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