java - mvc - Configuración del nivel de registro del mensaje en tiempo de ejecución en slf4j
spring java tutorial (12)
Acabo de encontrar una necesidad similar. En mi caso, slf4j está configurado con el adaptador de registro java (el jdk14 uno). Usando el siguiente fragmento de código, he logrado cambiar el nivel de depuración en tiempo de ejecución:
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");
Cuando se usa log4j, el Logger.log(Priority p, Object message)
está disponible y se puede usar para registrar un mensaje en un nivel de registro determinado en tiempo de ejecución. Estamos utilizando este hecho y este consejo para redirigir stderr a un registrador en un nivel de registro específico.
slf4j no tiene un método de log()
genérico log()
que pueda encontrar. ¿Eso significa que no hay forma de implementar lo anterior?
Aquí hay una solución lambda no tan fácil de usar como la de @Paul Croarkin de una manera (el nivel se pasa efectivamente dos veces). Pero creo que (a) el usuario debería pasar el Logger; y (b) AFAIU la pregunta original no estaba preguntando por una forma conveniente para todas partes en la aplicación, solo una situación con pocos usos dentro de una biblioteca.
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);
}
}
Como slf4j permite un Throwable (cuyo seguimiento de pila debe registrarse) dentro del parámetro varargs , creo que no hay necesidad de sobrecargar el método de log
helper para otros consumidores que (String, Object[])
.
Basado en la respuesta de Massimo Virgilio, también he logrado hacerlo con slf4j-log4j usando la introspección. 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());
}
Cualquier persona que desee una solución totalmente compatible con SLF4J para este problema, puede que quiera echar un vistazo a las extensiones Lidalia SLF4J , está en Maven Central.
Esto se puede hacer con un enum
y un método de ayuda:
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", ...);
Puede agregar otras variantes de log
, por ejemplo, si desea equivalentes genéricos de 1-parámetro de SLF4J o 2-parámetros de warn
/ error
/ etc. métodos.
Intenta cambiar a Logback y usa
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"));
Creo que esta será la única llamada a Logback y el resto de tu código permanecerá sin cambios. Logback usa SLF4J y la migración será sencilla, solo se tendrán que cambiar los archivos de configuración xml.
Recuerde volver a establecer el nivel de registro una vez que haya terminado.
No hay forma de hacer esto con slf4j
.
Imagino que la razón de que falte esta funcionalidad es que es casi imposible construir un tipo de Level
para slf4j
que se pueda mapear eficientemente al tipo de Level
(o equivalente) utilizado en todas las posibles implementaciones de registro detrás de la fachada. Alternativamente, los diseñadores decidieron que su caso de uso es demasiado inusual para justificar los gastos generales de soporte.
Con respecto use-case ripper234 (prueba unitaria), creo que la solución pragmática es modificar la (s) prueba (s) de la unidad para conocer qué sistema de registro está detrás de la fachada slf4j ... cuando se ejecutan las pruebas unitarias.
Pude hacer esto para el enlace JDK14 solicitando primero la instancia SLF4J Logger y luego estableciendo el nivel en el enlace; puede intentar esto para el enlace 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);
}
Puede implementar esto usando 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);
}
}
Richard Fearn tiene la idea correcta, así que escribí la clase completa en base a su código de esqueleto. Es de esperar lo suficientemente corto como para publicar aquí. Copie y pegue para disfrutar. Probablemente debería agregar un hechizo mágico también: "Este código se lanza al dominio público"
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;
}
}
no, tiene una serie de métodos, info (), debug (), warn (), etc. (esto reemplaza el campo de prioridad)
Eche un vistazo a slf4j.org/api/org/slf4j/Logger.html para obtener la API completa de Logger.
usando la introspección de Java puedes hacerlo, por ejemplo:
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);
}
}
}