2009-06-03 21 views
21

Necesito algo similar al método String.format(...), pero con evaluación perezosa.String.format con evaluación perezosa

Este método lazyFormat debería devolver algún objeto cuyo método toString() evaluaría el patrón de formato.

Sospecho que alguien ya ha hecho esto. ¿Está disponible en cualquier libararies?

quiero reemplazar esto (ejemplo log4j registrador es):

if(logger.isDebugEnabled()) { 
    logger.debug(String.format("some texts %s with patterns %s", object1, object2)); 
} 

con esto:

logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2)); 

necesito lazyFormat de formato de cadena sólo si el registro de depuración está habilitada.

Respuesta

22

si usted está buscando una solución "simple":

public class LazyFormat { 

    public static void main(String[] args) { 
     Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string"); 
     System.out.println(o); 
    } 

    private static Object lazyFormat(final String s, final Object... o) { 
     return new Object() { 
      @Override 
      public String toString() { 
       return String.format(s,o); 
      } 
     }; 
    } 
} 

salidas:

algunos textos Looong cadena con patrones de otra cadena muuuucho

puede, por supuesto agregue cualquier instrucción isDebugEnabled() dentro de lazyFormat si lo desea.

+0

Las versiones más nuevas de Log4J permiten la sustitución de parámetros, vea http://stackoverflow.com/a/14078904/620113 – computermacgyver

+0

¿Funciona esto realmente? Formateará la cadena, pero ¿está haciendo lo "flojo"? Hice una pequeña prueba donde los argumentos de lazyFormat son llamadas a una función que se imprime en System.err y parecía que los argumentos en Object ... o se evalúan incluso si String.format (s, o) no lo es. – OneSolitaryNoob

+0

OneSolitaryNoob - No está haciendo la flojera. El proveedor de la respuesta indica que se puede hacer utilizando el método "isDebugEnabled()" en toString() methdod. He proporcionado un patrón de registro perezoso más general en mi respuesta: http://stackoverflow.com/a/18317629/501113 – chaotic3quilibrium

13

si usted está buscando para la concatenación perezoso por el bien de la tala eficiente, echar un vistazo a Slf4J esto le permite escribir:

LOGGER.debug("this is my long string {}", fatObject); 

la concatenación de cadenas sólo se producirá si el nivel de depuración se establece .

+2

el problema de la evaluación diferida fue exactamente la razón por la que se introdujo la sintaxis de slf4j {} –

+0

De lo contrario, es bueno, pero estoy de acuerdo con log4j. Tengo un tipo de configuración de registro compleja, así que no puedo simplemente dejar caer slf4j. –

+2

SLF4J tiene un enlace para log4j. Entonces, sea cual sea su "compleja configuración de registro", SLF4J lo manejará bien. Puede continuar utilizando log4j directamente y SLF4J solo cuando sea necesaria la evaluación de cadenas perezosas. – Ceki

0

Puede definir un contenedor para llamar al String.format() solo si es necesario.

Consulte this question para obtener un ejemplo de código detallado.

La misma pregunta tiene también un variadic function example, como se sugiere en la respuesta de Andreas.

3

Sobre la base de Andreas' answer, no puedo pensar en un par de aproximaciones al tema de la única realizar el formateo si los Logger.isDebugEnabled vuelve true:

Opción 1: Pasar de una bandera "no formatear"

Una opción es tener un argumento de método que indique si realizar el formateo o no.Un caso de uso podría ser:

System.out.println(lazyFormat(true, "Hello, %s.", "Bob")); 
System.out.println(lazyFormat(false, "Hello, %s.", "Dave")); 

donde la salida sería:

Hello, Bob. 
null 

El código para lazyFormat es:

private String lazyFormat(boolean format, final String s, final Object... o) { 
    if (format) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

En este caso, el String.format sólo se ejecuta cuando el format indicador se establece en true, y si se establece en false devolverá un null. Esto detendría el formateo del mensaje de registro y simplemente enviará información "ficticia".

Así que un caso de uso con el registrador podría ser:

logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue)); 

Este método no se ajusta exactamente el formato que se pide en la pregunta.

Opción 2: Compruebe el registrador

Otro enfoque consiste en pedir al registrador directamente si isDebugEnabled:

private static String lazyFormat(final String s, final Object... o) { 
    if (logger.isDebugEnabled()) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

En este enfoque, se espera que logger serán visibles en el lazyFormat método. Y el beneficio de este enfoque es que la persona que llama no necesita ser verificar la técnica isDebugEnabled cuando lazyFormat se llama, por lo que el uso típico puede ser:

logger.debug(lazyFormat("Debug message is %s", someMessage)); 
+0

No debería el último ejemplo ser logger.debug (lazyFormat ("El mensaje de depuración es% s", someMessage))); ? –

+0

@Juha S .: Sí, estás en lo correcto. Me di cuenta del error un poco después de publicar la respuesta, por lo que se ha solucionado. – coobird

3

Usted puede envolver la instancia registrador Log4J dentro de su propia Java5- compatible/Clase compatible con String.format. Algo así como:

public class Log4jWrapper { 

    private final Logger inner; 

    private Log4jWrapper(Class<?> clazz) { 
     inner = Logger.getLogger(clazz); 
    } 

    public static Log4jWrapper getLogger(Class<?> clazz) { 
     return new Log4jWrapper(clazz); 
    } 

    public void trace(String format, Object... args) { 
     if(inner.isTraceEnabled()) { 
      inner.trace(String.format(format, args));  
     } 
    } 

    public void debug(String format, Object... args) { 
     if(inner.isDebugEnabled()) { 
      inner.debug(String.format(format, args));  
     } 
    } 

    public void warn(String format, Object... args) { 
     inner.warn(String.format(format, args));  
    } 

    public void error(String format, Object... args) { 
     inner.error(String.format(format, args));  
    } 

    public void fatal(String format, Object... args) { 
     inner.fatal(String.format(format, args));  
    }  
} 

Para utilizar el contenedor, cambiar su declaración de campo registrador a:

clase
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class); 

La envoltura necesitaría algunos métodos adicionales, por ejemplo, actualmente, no manejar las excepciones de registro (es decir, logger.debug (mensaje, excepción)), pero esto no debería ser difícil de agregar.

Utilización de la clase sería casi idéntica a log4j, excepto cadenas tienen el formato:

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction) 
1

o usted podría escribir como

debug(logger, "some texts %s with patterns %s", object1, object2); 

con

public static void debug(Logger logger, String format, Object... args) { 
    if(logger.isDebugEnabled()) 
     logger.debug(String.format("some texts %s with patterns %s", args)); 
} 
16

Puede ser hecho utilizando la sustitución de parámetros en la versión más reciente de log4j 2.X http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:

4.1.1.2 Sustitución de parámetros

Con frecuencia, el objetivo del registro es proporcionar información sobre lo que está sucediendo en el sistema, que requiere, incluida la información sobre los objetos que se manipulan. En Log4J 1.x esto podría lograrse haciendo:

if (logger.isDebugEnabled()) {  
    logger.debug("Logging in user " + user.getName() + " with id " + user.getId()); 
} 

Hacer esto tiene en repetidas ocasiones el efecto de hacer el código siente como que es más acerca del registro de la tarea real a la mano. Además, da como resultado que el nivel de registro se verifique dos veces; una vez en la llamada a isDebugEnabled y una vez en el método de depuración. Una mejor alternativa sería :

logger.debug("Logging in user {} with id {}", user.getName(), user.getId()); 

Con el código anterior el nivel de registro solamente se comprobará una vez y la construcción de cuerdas sólo ocurrirá cuando se habilita el registro de depuración.

+1

Su ejemplo es un poco engañoso en cuanto a que hay poca diferencia entre su ejemplo sin formatear y el formato; es decir, no hay ningún valor real en el formateador para las operaciones user.getName() o user.getId(), ya que se llaman inmediatamente y sus valores pasan al método logger.debug. El póster original estaba pasando dos instancias de Objeto contando con el hecho de que el método toString() en esas instancias no se llamaría a menos que fueran necesarias. Publiqué una respuesta que captura con mayor precisión este estado de "solo llamar para los datos si se usará". – chaotic3quilibrium

+0

Log4j 2.4 [agrega soporte para lambdas] (http://logging.apache.org/log4j/2.x/manual/api.html#LambdaSupport). –

5

NOTA IMPORTANTE: Se recomienda encarecidamente que todo el código de registro puede mover a utilizar SLF4J (especialmente 1.x log4j). Le protege de estar atascado con cualquier tipo de problemas idiosincrásicos (es decir, errores) con implementaciones de registro específicas. No solo tiene "soluciones" para problemas de implementación de backend bien conocidos, sino que también funciona con implementaciones más rápidas y nuevas que han surgido a lo largo de los años.


En respuesta directa a su pregunta, aquí lo que se vería como el uso de SLF4J:

LOGGER.debug("some texts {} with patterns {}", object1, object2); 

El bit más importante de lo que usted ha proporcionado es el hecho de que está pasando dos instancias de Object. Los métodos object1.toString() y object2.toString() no se evalúan inmediatamente. Lo que es más importante, los métodos toString() solo se evalúan si los datos que devuelven se van a utilizar; es decir, el verdadero significado de la evaluación perezosa.

Traté de pensar en un patrón más general que podría usar que no requiriera que tuviera que anular toString() en toneladas de clases (y hay clases en las que no tengo acceso para hacer la anulación). Se me ocurrió una solución sencilla de colocar en el lugar. Nuevamente, usando SLF4J, compongo la cadena solo si/cuando el registro para el nivel está habilitado. Aquí está mi código:

class SimpleSfl4jLazyStringEvaluation { 
     private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class); 

     ... 

     public void someCodeSomewhereInTheClass() { 
//all the code between here 
     LOGGER.debug(
      "{}" 
      , new Object() { 
       @Override 
       public String toString() { 
       return "someExpensiveInternalState=" + getSomeExpensiveInternalState(); 
       } 
      } 
//and here can be turned into a one liner 
     ); 
     } 

     private String getSomeExpensiveInternalState() { 
     //do expensive string generation/concatenation here 
     } 
    } 

Y para simplificar en el de una sola línea, se puede acortar la línea del maderero en someCodeSomewhereInTheClass() para ser:

LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}}); 

Ahora he rediseñado toda mi código de registro para seguir este modelo simple. Ha arreglado las cosas considerablemente. Y ahora, cuando veo algún código de registro que no utiliza esto, refactorizo ​​el código de registro para usar este nuevo patrón, incluso si aún es necesario. De esta forma, si/cuando se realiza un cambio más tarde para necesitar agregar alguna operación "cara", la repetición de la infraestructura ya está ahí simplificando la tarea para simplemente agregar la operación.

2

Introducido en Log4j 1.2.16 hay dos clases que lo harán por usted.

org.apache.log4j.LogMF que utiliza un formato que java.text.MessageFormat de mensajes y org.apache.log4j.LogSF que utiliza el "patrón de sintaxis SLF4J" y se dice que es más rápido.

Estos son algunos ejemplos:

LogSF.debug(log, "Processing request {}", req); 

y

LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
0

Si te gusta el String.Format Sintaxis mejor que el {0} Sintaxis y puede utilizar Java 8/8 JDK que pueda utilizar lambdas/Proveedores:

logger.log(Level.FINER,() -> String.format("SomeOperation %s took %04dms to complete", name, duration));

()->... actúa como un proveedor aquí y será evaluado perezosamente.

Cuestiones relacionadas