it-swarm.com.de

Protokollierebene der Nachricht zur Laufzeit in slf4j einstellen

Bei Verwendung von log4j ist die Logger.log(Priority p, Object message)-Methode verfügbar, mit der eine Nachricht auf einer zur Laufzeit bestimmten Protokollebene protokolliert werden kann. Wir verwenden diese Tatsache und diesen Tipp , um stderr auf einem bestimmten Log-Level zu einem Logger umzuleiten.

slf4j hat keine generische log()-Methode, die ich finden kann. Bedeutet das, dass es keine Möglichkeit gibt, das Obige umzusetzen?

78
scompt.com

Dies ist mit slf4j nicht möglich.

Ich kann mir vorstellen, dass der Grund für das Fehlen dieser Funktionalität darin liegt, dass es nahezu unmöglich ist, einen Level-Typ für slf4j zu erstellen, der effizient dem Level (oder einem gleichwertigen) Typ zugeordnet werden kann, der in allen möglichen Protokollierungsimplementierungen hinter der Fassade verwendet wird. Alternativ entschieden die Designer, dass Ihr Anwendungsfall ist zu ungewöhnlich , um den Aufwand für die Unterstützung zu rechtfertigen.

In Bezug auf @ ripper234 's Use-Case (Komponententest) denke ich, dass die pragmatische Lösung darin besteht, den Komponententest (en) so zu ändern, dass das Protokolliersystem hinter der slf4j-Fassade steckt. Wenn Sie die Gerätetests ausführen.

38
Stephen C

Richard Fearn hat die richtige Idee, also habe ich die gesamte Klasse basierend auf seinem Code geschrieben. Es ist hoffentlich kurz genug, um hier zu posten. Kopieren und Einfügen zum Genießen. Ich sollte wahrscheinlich auch eine magische Beschwörung hinzufügen: "Dieser Code ist öffentlich zugänglich"

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
David Tonhofer

Sie können dies mit Java 8-Lambdas implementieren.

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
Paul Croarkin

Wechseln Sie zu Logback und verwenden Sie

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

Ich glaube, dass dies der einzige Aufruf von Logback ist und der Rest Ihres Codes unverändert bleibt. Logback verwendet SLF4J und die Migration wird problemlos durchgeführt, lediglich die XML-Konfigurationsdateien müssen geändert werden.

Denken Sie daran, die Protokollebene nach Abschluss des Vorgangs zurückzusetzen.

11
Αλέκος

Dies kann mit einer enum und einer Hilfsmethode erfolgen:

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

Sie können andere Varianten von log hinzufügen, beispielsweise, wenn Sie generische Entsprechungen für die 1-Parameter- oder 2-Parameter-Variablen von SLF4J bzw. warn/error/etc wünschen. Methoden.

6
Richard Fearn

Wer eine vollständig SLF4J-kompatible Drop-In-Lösung für dieses Problem sucht, sollte Lidalia SLF4J Extensions ausprobieren - es ist auf Maven Central.

5
Robert Elliot

Ich bin gerade auf ein ähnliches Bedürfnis gestoßen ... In meinem Fall ist slf4j mit dem Java-Protokollierungsadapter (dem jdk14-Adapter) konfiguriert. Mit dem folgenden Code-Snippet habe ich es geschafft, den Debug-Level zur Laufzeit zu ändern:

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");
1
Yair Zaslavsky

Ich habe einfach so etwas gebraucht und mir Folgendes ausgedacht:

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

verwendungszweck:

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

Logger wird während des Aufrufs übergeben, daher sollten die Klasseninformationen in Ordnung sein und es funktioniert gut mit @ Slf4j lombok Annotation.

1
Kamil Nowak

Es ist nicht möglich, eine Protokollebene in sjf4j 1.x als Standard festzulegen. Aber es gibt Hoffnung, dass slf4j 2.0das Problem behebt. In 2.0 könnte es so aussehen:

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

In der Zwischenzeit können Sie für slf4j 1.x diese Problemumgehung verwenden:

Kopieren Sie diese Klasse in Ihren Klassenpfad:

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

Dann kannst du es so benutzen:

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

Dies wird ein Protokoll wie folgt ausgeben:

[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

Ist es das wert?

  • Pro Es behält die Position des Quellcodes (Klassennamen, Methodennamen, Zeilennummern zeigen auf Ihren Code )
  • Pro Sie können einfach Variablen , Parameter und Rückgabetypen als LogLevel definieren.
  • Pro Ihr Geschäftscode bleibt kurz und einfach zu lesen und es sind keine zusätzlichen Abhängigkeiten erforderlich.

Der Quellcode als minimales Beispiel wird gehostet auf GitHub .

1
slartidan

Mit der slf4j-API ist es nicht möglich, die Protokollierungsstufe dynamisch zu ändern. Sie können jedoch die Rückmeldung (sofern Sie diese verwenden) selbst konfigurieren. In diesem Fall erstellen Sie eine Factory-Klasse für Ihren Logger und implementieren den Root-Logger mit der von Ihnen benötigten Konfiguration.

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

Nachdem Sie den Root-Logger konfiguriert haben (nur ein einziges Mal ist ausreichend), können Sie das Abrufen eines neuen Loggers durch delegieren

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

Denken Sie daran, das gleiche loggerContext zu verwenden.

Das Ändern der Protokollebene ist mit dem Root-Logger aus loggerContext ganz einfach.

root.setLevel(Level.DEBUG);
0
pablo127

Basierend auf der Antwort von massimo virgilio habe ich es auch mit slf4j-log4j mit Introspection geschafft. 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());
}
0
Guido

Hier ist eine Lambda-Lösung, die nicht so benutzerfreundlich ist wie das von @ Paul Croarkin (das Level wird tatsächlich zweimal bestanden). Aber ich denke, (a) sollte der Benutzer den Logger übergeben; und (b) AFAIU Die ursprüngliche Frage stellte nicht nach einem geeigneten Weg für die gesamte Anwendung, sondern nur für eine Situation mit wenigen Verwendungen innerhalb einer Bibliothek.

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

Da slf4j ein Throwable (dessen Stack-Trace protokolliert werden soll) im varargs-Parameter erlaubt, denke ich, dass es nicht notwendig ist, die log -Hilfermethode für andere Benutzer als (String, Object[]) zu überladen.

0
EndlosSchleife

Die Methode, die ich verwende, ist, die ch.qos.logback-Module zu importieren und dann die slf4j-Logger-Instanz in einen ch.qos.logback.classic.Logger zu konvertieren. Diese Instanz enthält eine setLevel () -Methode. 

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

Um die möglichen Protokollierungsstufen herauszufinden, können Sie die Klasse ch.qos.logback auflösen, um alle möglichen Werte für Level anzuzeigen:

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

Die Ergebnisse sind folgende:

{
   // ...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
Glenn Inn

Ich konnte dies für die JDK14-Bindung tun, indem ich zuerst die SLF4J-Logger-Instanz anforderte und dann das Niveau an der Bindung festlegte. Sie können dies für die Log4J-Bindung versuchen.

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
youurayy