Установка уровня журнала сообщений во время выполнения в slf4j - PullRequest
85 голосов
/ 12 апреля 2010

При использовании log4j доступен метод Logger.log(Priority p, Object message), который можно использовать для регистрации сообщения на уровне журнала, определенном во время выполнения. Мы используем этот факт и этот совет для перенаправления stderr в регистратор на определенном уровне журнала.

slf4j не имеет общего метода log(), который я могу найти. Означает ли это, что нет способа реализовать вышесказанное?

Ответы [ 15 ]

40 голосов
/ 12 апреля 2010

Нет способа сделать это с slf4j.

Я полагаю, что причина отсутствия этой функциональности заключается в том, что почти невозможно создать тип Level для slf4j, который можно эффективно сопоставить с типом Level (или эквивалентным), используемым во всех возможные реализации каротажа за фасадом. В качестве альтернативы дизайнеры решили, что ваш вариант использования слишком необычен , чтобы оправдать затраты на его поддержку.

Что касается @ ripper234 * сценарий использования (модульное тестирование), я думаю, что прагматическое решение состоит в том, чтобы модифицировать модульные тесты для точного понимания системы регистрации находится за фасадом slf4j ... при запуске модульных тестов.

25 голосов
/ 20 октября 2011

У Ричарда Фирна правильная идея, поэтому я написал полный класс на основе его скелетного кода. Надеюсь, он достаточно короткий, чтобы оставлять сообщения. Скопируйте и вставьте для удовольствия. Вероятно, мне следует добавить и магическое заклинание: «Этот код опубликован в открытом доступе»

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;
    }
}
12 голосов
/ 18 января 2013

Попробуйте переключиться на 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.

Не забудьте вернуть уровень журнала после завершения.

11 голосов
/ 10 марта 2016

Вы можете реализовать это, используя лямбды Java 8.

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);
    }
}
6 голосов
/ 26 мая 2011

Это можно сделать с помощью 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, скажем, если вы хотите общие эквиваленты 1-параметра SLF4J или 2-параметра warn / error / и т. Д. методы.

5 голосов
/ 28 марта 2014

Любой, кто хочет получить полностью совместимое с SLF4J решение этой проблемы, может попробовать Lidalia SLF4J Extensions - это на Maven Central.

2 голосов
/ 25 апреля 2019

Мне просто нужно что-то подобное, и я придумал:

@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.

1 голос
/ 21 мая 2019

не можно указать уровень журнала в 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 .

0 голосов
/ 16 февраля 2019

Метод, который я использую, заключается в том, чтобы импортировать модули 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;
}
0 голосов
/ 21 июня 2017

Я смог сделать это для привязки JDK14, сначала запросив экземпляр регистратора SLF4J и , а затем установив уровень привязки - вы можете попробовать это для привязки 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);
}
...