java - lazy - String.format con evaluación perezosa
lazy execution (10)
Necesito algo similar al String.format(...) , pero con una 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 lo ha hecho. ¿Está esto disponible en cualquier biblioteca?
Quiero reemplazar esto (el logger es una instancia de log4j):
if(logger.isDebugEnabled() ) {
logger.debug(String.format("some texts %s with patterns %s", object1, object2));
}
con este:
logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2));
Necesito lazyFormat para formatear la cadena solo si el registro de depuración está habilitado.
Introducidas en Log4j 1.2.16 hay dos clases que lo harán por usted.
org.apache.log4j.LogMF
que usa un java.text.MessageFormat
para el formato de los mensajes y org.apache.log4j.LogSF
que usa la "sintaxis de patrón SLF4J" y se dice que es más rápido.
Aquí hay algunos ejemplos:
LogSF.debug(log, "Processing request {}", req);
y
LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5);
O podrías escribirlo 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));
}
Podría ajustar la instancia del registrador Log4J dentro de su propia clase compatible con Java5 / String.format. Algo 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 usar el contenedor, cambie la declaración de campo de su registrador a:
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class);
La clase contenedora necesitaría algunos métodos adicionales, por ejemplo, actualmente no maneja las excepciones de registro (es decir, logger.debug (mensaje, excepción)), pero esto no debería ser difícil de agregar.
Usar la clase sería casi idéntico a log4j, excepto que las cadenas están formateadas:
logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)
Podría definir una envoltura para llamar a String.format()
solo si es necesario.
Vea esta pregunta para un ejemplo de código detallado.
La misma pregunta también tiene un ejemplo de función variable , como se sugiere en la respuesta de Andreas.
Se puede hacer usando 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 propósito del registro es proporcionar información sobre lo que está sucediendo en el sistema, lo que requiere incluir información sobre los objetos que se están manipulando. 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 repetidamente tiene el efecto de hacer que el código parezca que se trata más de un registro que de la tarea real en cuestión. Además, hace 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 solo se verificará una vez y la construcción de String solo ocurrirá cuando el registro de depuración esté habilitado.
Si está buscando una concatenación perezosa para un registro eficiente, eche un vistazo a Slf4J esto le permite escribir:
LOGGER.debug("this is my long string {}", fatObject);
la concatenación de cadenas solo tendrá lugar si se establece el nivel de depuración.
Si 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 a lo largo de la cadena con patrones otra cadena loooong
por supuesto, puede agregar cualquier declaración isDebugEnabled()
dentro de lazyFormat si lo desea.
Si le gusta la Sintaxis String.format mejor que la Sintaxis {0} y puede usar Java 8 / JDK 8 , puede usar lambdas / Proveedores:
logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));
()->...
actúa como proveedor aquí y será evaluado perezosamente.
Sobre la base de la respuesta de Andreas , puedo pensar en un par de enfoques para el problema de solo realizar el formateo si Logger.isDebugEnabled
devuelve true
:
Opción 1: pasar en un indicador de "hacer formato"
Una opción es tener un argumento de método que indique si se debe 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, String.format
solo se ejecuta cuando el indicador de format
se establece en true
y, si se establece en false
, devolverá un null
. Esto detendría el formateo del mensaje de registro y solo 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 al formato que se solicita en la pregunta.
Opción 2: comprobar el registrador
Otro enfoque es preguntar directamente al registrador si es 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 el logger
sea visible en el método lazyFormat
. Y el beneficio de este enfoque es que la persona que llama no tendrá que verificar el método lazyFormat
cuando se llama a lazyFormat
, por lo que el uso típico puede ser:
logger.debug(lazyFormat("Debug message is %s", someMessage));
NOTA IMPORTANTE: Se recomienda encarecidamente que se mueva todo el código de registro para utilizar Slf4J (especialmente log4j 1.x). Lo protege contra atascos con cualquier tipo de problemas idiosincrásicos (es decir, errores) con implementaciones de registro específicas. No solo tiene "arreglos" para problemas de implementación de back-end bien conocidos, sino que también funciona con implementaciones más rápidas que han surgido a lo largo de los años.
En respuesta directa a su pregunta, aquí se vería el uso de Slf4J :
LOGGER.debug("some texts {} with patterns {}", object1, object2);
Lo más importante de lo que ha proporcionado es el hecho de que está pasando dos instancias de Objeto. Los object1.toString()
y object2.toString()
no se evalúan inmediatamente. Más importante aún, los métodos toString()
solo se evalúan si los datos que devuelven se van a utilizar; Es decir, el significado real de la evaluación perezosa.
Intenté pensar en un patrón más general que podría usar que no requería que tuviera que anular toString()
en toneladas de clases (y hay clases a las que no tengo acceso para hacer la anulación). Se me ocurrió una solución simple para colocar en el lugar. Nuevamente, utilizando Slf4J , compongo la cadena solo si / cuando se habilita el registro para el nivel. 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 simplificarlo en una sola línea , puede acortar la línea LOGGER en someCodeSomewhereInTheClass () para que sea:
LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}});
Ahora he refaccionado todo mi código de registro para seguir este modelo simple. Ha ordenado las cosas considerablemente. Y ahora, cuando veo cualquier código de registro que no usa esto, refactorizo el código de registro para usar este nuevo patrón, incluso si es necesario todavía. De esa manera, si / cuando se hace un cambio más tarde para agregar alguna operación "costosa", la infraestructura de la infraestructura ya está allí simplificando la tarea simplemente agregando la operación.