slf4jでの実行時のメッセージのログレベルの設定


100

log4jを使用する場合、このLogger.log(Priority p, Object message)メソッドが使用可能であり、実行時に決定されたログレベルでメッセージを記録するために使用できます。この事実とこのヒントを使用していますを stderrを特定のログレベルのロガーにリダイレクトします。

slf4jにはlog()、私が見つけることができる一般的なメソッドがありません。それは、上記を実装する方法がないということですか?


4
これをdevメーリングリストのslf4j 2.0に追加することについての議論があるようです:qos.ch/pipermail/slf4j-dev/2010-March/002865.html
Edward Dale

1
マーカーを見てください。これは、ログチェーンに渡すことができるカスタムデータです。
tuxSlayer 2013

1
@tuxSlayerこの場合のマーカーの使用方法について詳しく説明していただけますか?
悲惨な変数

おそらく「ロギング」には最適ではありませんが、ログエントリの「優先度」に複数のマーカー(高|低|通常、情報|警告|致命的)を使用し、ログバックまたはカスタムアペンダーでフィルタリングを使用してマーカーを消費し、ログエントリを駆動できます。別のチャネル(ログ情報、致命的な電子メールなど)。ただし、より簡単な方法は、以下の回答で指摘されているように、このためのファサードを用意することです。
tuxSlayer 2013

2
この機能はの一部であることになっていますslf4j 2.0jira.qos.ch/browse/SLF4J-124詳細と可能なslf4j 1.x回避については、私の回答を参照してください。
slartidan

回答:


47

でこれを行う方法はありませんslf4j

この機能が欠落している理由は、ファサードの背後にある可能性のあるすべてのロギング実装で使用される(または同等の)タイプに効率的にマップできるLevelタイプを構築するslf4jことがほぼ不可能であることだと思いLevelます。あるいは、デザイナーはあなたのユースケースがあまりにも珍しいと決めました、それをサポートするオーバーヘッドを正当化しました。

@ ripper234ユースケース(ユニットテスト)を、私は実用的な解決策は、ユニットテストを実行するときにロギングシステムが... SLF4Jのファサードの背後にあるもののハードワイヤー知識ユニットテスト(複数可)を修正することだと思います。


9
実際にマッピングは必要ありません。のメソッドによって暗黙的に定義されている5つのレベルがありますorg.slf4j.Logger。デバッグ、エラー、情報、トレース、警告です。
エドワードデール

1
そして問題は無効として閉じられました。私が理解している限り、これは意図的な設計の選択です。
ripper234 2010年

9
@ ripper234-あなたのバグがscompt.comの元の質問と同じ問題に対処したとは思わない。SLF4J APIを介して、基盤となるロギングシステムのレベルを構成することについて質問しました。scompt.comの後には、メッセージのログレベルパラメーターとして取るSLF4J APIの一般的な 'log'メソッドがありました。
Richard Fearn、

1
1 @RichardFearnそして、もう一つは、60秒後にコメントupvoteを元に戻すことはできませんまあまあ。その間、機能のリクエストが存在します:bugzilla.slf4j.org/show_bug.cgi
jan

3
RFEリンクはこれ以上解決されません。関連リンクは次のとおりです。jira.qos.ch / browse / SLF4J-124およびjira.qos.ch/browse/SLF4J-197 ...とどちらもクローズされました。根拠のコメントを読んでください。
スティーブンC

27

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

これは、可変個の(Object ...)argsパラメーターで使用する方が簡単です。
Anonymoose

「org.slf4j.Logger」には、上記のクラスでは処理されないロギングメソッドシグネチャがかなりあるため、拡張がおそらく保証されます。slf4j.org
David Tonhofer

1
この実装により、望ましくない変更が加えられると思います。呼び出しlogger.info(...)を使用すると、ロガーは呼び出し元のクラスとメソッドにアクセスでき、自動的にログエントリに追加できます。現在、この実装では、呼び出しlog(logger、level、txt)は常に同じ呼び出し元であるLoglevel.logを持つログエントリを生成します。私は正しいですか?
ドミン

@Dominこんにちは、つまり、ロガーは現在のコールスタックを調べてから、自動ロギングの最後のエントリを抽出できますが、ここではそうではありませんか?原則的にはそうですが、実際には、実際のメッセージが書き出されるまで、スタックはこれよりも少し長くなります(特に、ある時点でlogbackを呼び出し、次に実際のアペンダーを呼び出す必要があります)。興味のないスタック行を破棄するのがアペンダーの役割であるべきだと思うので、このLoglevelクラスへの呼び出しを含め、すべてを破棄するようにそれを適応させることができます。
David Tonhofer、2015

@David、はい、あなたは正しいです:-)。それがアペンダーとロガーの間に強い依存関係を定義しているので、それがアペンダーのタスクであるかどうかはわかりません...しかし...それは解決策です。おかげでデビッド
ドミン

14

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構成ファイルを変更するだけで済みます。

完了したら、必ずログレベルを元に戻してください。


私はすでにLogback-backed slf4jを使用しており、これにより即座に単体テストをクリーンアップできました。ありがとう!
ランバート14年

2
これは私の最初の-1でした。ありがとう。私はあなたが間違っていると思います。LogbackはSLF4Jを使用しているため、答えは適切です。
Αλέκος

3
@AlexandrosGelbessis質問をもう一度読んでください。プログラムで任意のレベルの1つのログメッセージを記録できる方法を求められました。ルートロガーのレベルを1つだけではなく、すべてのメッセージに対して変更します。
2014

12

これは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);
    }
}

そうですね...しかし、今度はslf4jの代わりに、またはslf4jの代わりに、このAPIを使用するようにコードベースを変更する必要があります。slf4jの代わりにそれを使用する場合、1)おそらくよりリッチにする必要があります、2)(少なくとも)インポートの多くを変更する必要があります。3)slf4jの前のこの新しいレイヤーは、追加のロギングオーバーヘッドを追加します。
スティーブンC

4
また、このソリューションを使用する場合、実際のロギングを行うクラスはログに記録されないことにも注意してください(ロガーはで初期化されているためLevelLogger)。これは一般的に非常に有用な情報であるため、良いことではありません。
ヤマネ

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

logたとえば、SLF4Jの1パラメータまたは2パラメータwarn/ error/ etc と同等のジェネリックが必要な場合は、の他のバリアントを追加できます。メソッド。


3
真ですが、slf4jの目的は、ログラッパーを作成する必要がないことです。
djjeck 14年

5
SLF4Jの目的は、さまざまなロギングフレームワークの抽象化を提供することです。その抽象化で必要なものが正確に提供されない場合は、ヘルパーメソッドを作成するしかありません。他の唯一の選択肢は、SLF4Jプロジェクトに対する私の回答のような方法を提供することです。
Richard Fearn、

私は同意しますが、この場合、さらに別の回避策を実装しない限り、ファイルと行番号を提供できなくなるなどの警告があります。この場合、フレームワークが機能をサポートするまで、log4jを使用し続けていました。この機能は最終的に拡張機能を介して発生しました。RobertElliotの最新の回答を参照してください。
djjeck 14年


3

私はちょうどそのようなものを必要としていて思いついた:

@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アノテーションとうまく連携します。


この素晴らしいアプローチをありがとうございました-私はあなたのアイデアに基づいて同様の答えを投稿しました。
slartidan

DEBUG定数として欠落しています。
slartidan

このソリューションは常にLogLevelクラスおよびlogメソッドとしてログを記録するため、ログの意味が少なくなります。
slartidan

2

デフォルトで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でホストされています


注:LogMethodパッケージの外部のクラスを操作するには、インターフェースをパブリックにする必要があります。それ以外は、意図したとおりに機能します。ありがとう!
andrebrait

1

slf4j APIではログレベルを動的に変更することはできませんが、独自にログバックを構成できます(これを使用する場合)。その場合は、ロガーのファクトリクラスを作成し、必要な構成でルートロガーを実装します。

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

1

回答を確認する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] Test Worker Registered Metricdという名前のMetricName [name = bufferpool-wait-time-total、group = producer-metrics、description =アペンダーがスペース割り当てを待機する合計時間。、tags = {client-id = producer-2}]


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

1
他の回答と同様に、これは元の質問には対応していません。別の問題です。
E-Riz 2016年

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

0

これは、@ Paul Croarkinの方法ほどユーザーフレンドリーではないラムダソリューションです(レベルは事実上2回渡されます)。しかし、(a)ユーザーはロガーを渡す必要があると思います。(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 param内でThrowable(スタックトレースをログに記録する必要がある)を許可しているlogため、以外のコンシューマーのヘルパーメソッドをオーバーロードする必要はないと思います(String, Object[])


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

0

私が使用する方法は、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 );

可能なLoggingレベルを見つけるには、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;
}

-2

あなたがそれを行うことができるJavaのイントロスペクションを使用して、例えば:

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
これは明示的にlog4jを指し、slf4jを総称的に指すのではありません–ThorbjørnRavn
Andersen

-6

いいえ、info()、debug()、warn()などの多数のメソッドがあります(優先度フィールドを置き換えます)

完全なLogger APIについては、http://www.slf4j.org/api/org/slf4j/Logger.htmlご覧ください


申し訳ありませんが、あなたが今何を求めているのか分かります。いいえ、実行時にログレベルを変更する一般的な方法はありませんが、switchステートメントを使用してヘルパーメソッドを簡単に実装できます。
クリス

はい。ただし、 "log"メソッドのオーバーロードされたバージョンごとに1回実行する必要があります。
Andrew Swan 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.