Log4jを使用する場合、Logger.log(Priority p, Object message)
メソッドが使用可能であり、実行時に決定されたログレベルでメッセージを記録するために使用できます。この事実と このヒント を使用して、stderrを特定のログレベルのロガーにリダイレクトします。
slf4jには、一般的なlog()
メソッドがありません。それは上記を実装する方法がないということですか?
slf4j
でこれを行う方法はありません。
この機能が欠けている理由は、Level
(または同等のもの)に効率的にマッピングできるslf4j
のLevel
型を構築することがほとんど不可能だからだと思いますファサードの背後にあるすべての可能なロギング実装で使用されるタイプ。あるいは、設計者は、サポートのオーバーヘッドを正当化するために、 ユースケースがあまりにも珍しい と決定しました。
@ ripper234 の se-case (単体テスト)については、実用的な解決策は単体テストを変更して、ロギングシステムとは何かを明確に把握することだと思いますslf4jファサードの背後...単体テストの実行時。
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;
}
}
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構成ファイルを変更するだけです。
完了したら、必ずログレベルを元に戻してください。
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);
}
}
これは、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", ...);
SLF4Jの1パラメーターまたは2パラメーターlog
/warn
/etcの一般的な同等物が必要な場合など、error
の他のバリアントを追加できます。メソッド。
この問題に対する完全なSLF4J互換のドロップインソリューションが必要な場合は、チェックアウトすることをお勧めします Lidalia SLF4J Extensions -Maven Centralにあります。
私はちょうどそのようなものが必要で、思いついた:
@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アノテーションでうまく機能します。
notsjf4j 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
その価値はありますか?
LogLevel
として定義できます最小限の例としてのソースコードは、ホストされています GitHubで 。
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);
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());
}
これは、@ Paul Croarkinのようにユーザーフレンドリーではないラムダソリューションです(レベルは事実上2回渡されます)。ただし、(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 param内でThrowable(スタックトレースをログに記録する必要がある)を許可する であるため、(String, Object[])
以外のコンシューマに対してlog
ヘルパーメソッドをオーバーロードする必要はないと思います。
私が使用する方法は、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;
}
最初にSLF4J Loggerインスタンスをリクエストし、thenバインディングのレベルを設定することで、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);
}
私はちょうど同様のニーズに遭遇しました。私の場合、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");