2010-04-12 11 views
72

Al usar log4j, el método Logger.log(Priority p, Object message) está disponible y se puede utilizar para registrar un mensaje en un nivel de registro determinado en tiempo de ejecución. Estamos utilizando este hecho y this tip para redirigir stderr a un registrador en un nivel de registro específico.Configuración del nivel de registro del mensaje en tiempo de ejecución en slf4j

slf4j no tiene un método genérico log() que pueda encontrar. ¿Eso significa que no hay forma de implementar lo anterior?

+4

Parece que hay alguna explicación de cómo agregar esto a SLF4J 2.0 en la lista de correo dev: http://www.qos.ch/pipermail/slf4j-dev/2010-March/002865.html –

+0

depositado problema: http://bugzilla.slf4j.org/show_bug.cgi?id=206 – ripper234

+1

eche un vistazo a Marker, estos son datos personalizados que puede pasar a la cadena de registro. – tuxSlayer

Respuesta

33

No hay forma de hacerlo con slf4j.

me imagino que la razón por la que esta funcionalidad es que falta es que es casi imposible construir un tipo Level para slf4j que se pueden asignar de manera eficiente a la Level (o equivalente) tipo usado en todas las posibles implementaciones de registro detrás la fachada. Alternativamente, los diseñadores decidieron que your use-case is too unusual para justificar los gastos generales de apoyo.

@ripper234 En cuanto a 's use-case (unidad de pruebas), creo que la solución pragmática es modificar la prueba (s) unidad al conocimiento de alambre duro de lo que es el sistema de registro detrás de la fachada ... slf4j al ejecutar las pruebas unitarias.

+5

No es necesario mapear realmente. Hay cinco niveles ya definidos implícitamente por los métodos en 'org.slf4j.Logger': depuración, error, información, rastreo, advertencia. –

+0

Eso es un mapeo, y cuesta ciclos de CPU para soportarlo. –

+57

WTF? SLF4J se anuncia como el futuro del registro de Java, y este caso de uso simplemente no es compatible? (Lo primero que intenté hacer después de integrar SLF4J ... para ayudar a reducir el desorden en las pruebas unitarias). – ripper234

-4

no, tiene una serie de métodos, información(), debug(), advierten(), etc (esto reemplaza el campo de prioridad)

echar un vistazo a http://www.slf4j.org/api/org/slf4j/Logger.html para la API Logger completa.

+0

lo siento, veo lo que estás preguntando ahora. no, no hay una manera genérica de cambiar el nivel de registro en tiempo de ejecución, pero podría implementar fácilmente un método de ayuda con una declaración de cambio. – oedo

+0

Sí, pero luego debe hacerlo una vez por cada versión sobrecargada del método "log". –

6

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

Se podría añadir otras variantes de log, dicen que si quería equivalentes genéricos de la SLF4J 1-parámetro o 2 parámetros warn/error/etc. métodos.

+3

Es cierto, pero slf4j propósito no es tener que escribir contenedores de registro. – djjeck

+3

El objetivo de SLF4J es proporcionar una abstracción para diferentes marcos de registro. Si esa abstracción no proporciona exactamente lo que necesita, no tiene más remedio que escribir un método de ayuda. La única alternativa es contribuir con un método como el que aparece en mi respuesta al proyecto SLF4J. –

+0

Acepto, pero en este caso hay advertencias, como que ya no podría proporcionar el número de archivo y línea, a menos que haya implementado otra solución alternativa para eso. En este caso, me habría quedado con log4j, hasta que el marco haya admitido la función, lo que finalmente sucedió a través de una extensión, consulte la respuesta más reciente de Robert Elliot. – djjeck

20

Richard Fearn tiene la idea correcta, así que escribí toda la clase en función de su código de esqueleto. Es de esperar lo suficientemente corto como para publicar aquí. Copie & pasta para disfrutar. Probablemente debería añadir algo de conjuro mágico, también: "Este código es liberado 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; 
    } 
} 
+0

Esto sería más fácil de usar con un parámetro de argumento variado (Object ...). – Anonymoose

+0

"org.slf4j.Logger" tiene bastantes firmas de métodos de registro que no se manejan en la clase anterior, por lo que una extensión probablemente esté justificada: http://www.slf4j.org/api/org/slf4j/Logger.html –

+0

Creo que esta implementación agregará un cambio no deseado. Cuando usa la llamada logger.info (...) el registrador tiene acceso a la clase y método de llamante y podría agregarse automáticamente a la entrada de registro. Ahora, con esta implementación, el registro de llamadas (registrador, nivel, txt) generará una entrada de registro que siempre tendrá la misma llamada: Loglevel.log. ¿Estoy en lo cierto? – Domin

-1

usando la introspección de Java que puede 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); 
     } 
    } 

} 
+4

Esto se refiere explícitamente a log4j y no a slf4j genéricamente –

8

Intente cambiar a logback y use

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 su código se mantendrá 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.

+0

Ya estaba usando slf4j con respaldo de Logback, y esto me permitió al instante limpiar mis pruebas unitarias. ¡Gracias! – Lambart

+2

-1 De esto no se trata esta pregunta. – jan

+2

Este fue mi primer -1, gracias. Creo que estás equivocado. Logback usa SLF4J, por lo que la respuesta ES relevante. –

4

Cualquier persona que desee una solución completamente compatible con SLF4J para este problema puede querer consultar Lidalia SLF4J Extensions - está en Maven Central.

1

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 que 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"); 
+1

Al igual que otras respuestas, esto no aborda la pregunta original, es un problema diferente. –

0

Sobre la base de la respuesta de Virgilio Massimo, también me las he arreglado para hacerlo con slf4j-log4j mediante 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()); 
} 
7

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

he aquí una solución lambda no es tan fácil de usar como @ Pablo Croarkin de un modo (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); 
    } 
} 

Desde slf4j allows a Throwable (whose stack trace should be logged) inside the varargs param, creo que no hay necesidad de sobrecargar el método log ayudante para otros consumidores de (String, Object[]).

0

Pude hacer esto para el enlace JDK14 solicitando primero la instancia SLF4J Logger y luego configurando 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); 
} 
Cuestiones relacionadas