org - ¿Por qué no usar java.util.logging?
slf4j log4j2 (5)
java.util.logging
fue introducido en Java 1.4. Hubo usos para el registro antes de eso, por eso existen muchas otras API de registro. Esas API se usaron mucho antes de Java 1.4 y, por lo tanto, tenían una gran participación de mercado que no se reducía a 0 cuando se lanzó la versión 1.4.JUL no comenzó tan bien, muchas de las cosas que mencionaste fueron mucho peores en 1.4 y solo mejoraron en 1.5 (y creo que en 6 también, pero no estoy muy seguro).
JUL no es adecuado para múltiples aplicaciones con diferentes configuraciones en la misma JVM (piense en múltiples aplicaciones web que no deberían interactuar). Tomcat necesita saltar a través de algunos aros para que funcione (re-implementando efectivamente JUL si lo entendí correctamente).
No siempre se puede influir en qué marco de registro utilizan sus bibliotecas. Por lo tanto, el uso de SLF4J (que en realidad es solo una capa API muy delgada sobre otras bibliotecas) ayuda a mantener una imagen algo consistente de todo el mundo de registro (para que pueda decidir el marco de registro subyacente mientras sigue teniendo el registro de biblioteca en el mismo sistema).
Las bibliotecas no pueden cambiar fácilmente. Si una versión anterior de una biblioteca solía usar logging-library-X, no se puede cambiar fácilmente a logging-library-Y (por ejemplo, JUL), incluso si este último es claramente superioso: cualquier usuario de esa biblioteca debería aprender El nuevo marco de registro y (al menos) reconfigurar su registro. Eso es un gran no-no, especialmente cuando no aporta ganancia aparente a la mayoría de las personas.
Habiendo dicho todo lo que creo, JUL es al menos una alternativa válida a otros marcos de trabajo de registro en estos días.
Por primera vez en mi vida me encuentro en una posición en la que estoy escribiendo una API de Java que será de código abierto. Esperemos que sea incluido en muchos otros proyectos.
Para el registro, yo (y, de hecho, las personas con las que trabajo) siempre usé JUL (java.util.logging) y nunca tuve ningún problema con él. Sin embargo, ahora debo entender con más detalle qué debo hacer para mi desarrollo de API. He investigado sobre esto y con la información que tengo, me confundo más. De ahí este post.
Desde que vengo de julio estoy predispuesto a eso. Mi conocimiento del resto no es tan grande.
A partir de la investigación que he realizado, he descubierto estas razones por las que a la gente no le gusta JUL:
"Comencé a desarrollar en Java mucho antes de que Sun lanzara JUL y fue más fácil para mí continuar con logging-framework-X que aprender algo nuevo" . Hmm No estoy bromeando, esto es en realidad lo que dice la gente. Con este argumento todos podríamos estar haciendo COBOL. (Sin embargo, ciertamente puedo relacionarme con este ser un tipo perezoso)
"No me gustan los nombres de los niveles de registro en JUL" . Ok, en serio, esto no es motivo suficiente para introducir una nueva dependencia.
"No me gusta el formato estándar de la salida de JUL" . Hmm Esto es sólo la configuración. Ni siquiera tienes que hacer nada en código. (es cierto, en los viejos tiempos es posible que haya tenido que crear su propia clase de Formatter para hacerlo bien).
"Uso otras bibliotecas que también usan logging-framework-X, por lo que pensé que era más fácil usar esa" . Este es un argumento cíclico, ¿no? ¿Por qué ''todos'' usan logging-framework-X y no JUL?
"Todos los demás están usando logging-framework-X" . Esto para mí es sólo un caso especial de lo anterior. La mayoría no siempre tiene la razón.
Entonces la gran pregunta es ¿por qué no JUL? . ¿Qué es lo que me he perdido? La razón de ser de las fachadas de registro (SLF4J, JCL) es que históricamente han existido múltiples implementaciones de registro y la razón de esto realmente se remonta a la era anterior a JUL tal como lo veo. Si JUL fuera perfecto, entonces las fachadas de registro no existirían, ¿o qué? En lugar de abrazarlos, ¿no deberíamos cuestionarnos por qué eran necesarios en primer lugar? (y ver si esas razones aún existen)
Ok, mi investigación hasta el momento ha llevado a un par de cosas que puedo ver que pueden ser problemas reales con JUL:
Rendimiento Algunos dicen que el rendimiento en SLF4J es superior al resto. Esto me parece ser un caso de optimización prematura. Si necesita registrar cientos de megabytes por segundo, no estoy seguro de que esté en el camino correcto de todos modos. JUL también ha evolucionado y es posible que las pruebas que hiciste en Java 1.4 ya no sean ciertas. Puede leer sobre esto here y esta solución se ha convertido en Java 7. Muchos también hablan de la sobrecarga de concatenación de cadenas en los métodos de registro. Sin embargo, el registro basado en plantillas evita este costo y existe también en JULIO. Personalmente nunca escribo el registro basado en plantillas. Demasiado perezoso para eso. Por ejemplo si hago esto con JUL:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
mi IDE me avisará y solicitará permiso para cambiarlo a:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
.. que por supuesto voy a aceptar. Permiso concedido ! Gracias por tu ayuda.
Así que en realidad no escribo esas declaraciones, eso lo hace el IDE.
En conclusión, sobre el tema del desempeño, no he encontrado nada que sugiera que el desempeño de JUL no esté bien comparado con la competencia.
Configuración desde classpath . Fuera de la caja JUL no puede cargar un archivo de configuración desde la ruta de clase. Se trata de unas pocas líneas de código para hacerlo hacerlo. Puedo ver por qué esto puede ser molesto, pero la solución es breve y simple.
Disponibilidad de manejadores de salida . JUL viene con 5 controladores de salida listos para usar: consola, flujo de archivos, socket y memoria. Estos pueden ser extendidos o nuevos pueden ser escritos. Esto puede, por ejemplo, estar escribiendo en UNIX / Linux Syslog y en el Registro de eventos de Windows. Personalmente nunca he tenido este requisito ni lo he visto usado, pero ciertamente puedo relacionarme con por qué puede ser una característica útil. Logback viene con un appender para Syslog, por ejemplo. Aun así yo diría que
- El 99.5% de las necesidades de los destinos de salida están cubiertos por lo que se encuentra en JUL out out of the box.
- Las necesidades especiales pueden ser atendidas por los manipuladores personalizados en la parte superior de JUL en lugar de sobre otra cosa. No hay nada para mí que sugiera que se necesita más tiempo para escribir un controlador de salida Syslog para JUL que para otro marco de registro.
Estoy realmente preocupado de que hay algo que he pasado por alto. El uso de las fachadas de registro y las implementaciones de registro distintas de JUL está tan extendido que tengo que llegar a la conclusión de que soy yo quien simplemente no entiende. Esa no sería la primera vez, me temo. :-)
Entonces, ¿qué debo hacer con mi API? Quiero que tenga éxito. Por supuesto, puedo simplemente "seguir el flujo" e implementar SLF4J (que parece ser el más popular en estos días), pero por mi propio bien, ¿todavía tengo que entender exactamente qué es lo que está mal con el JUL de hoy que justifica todo el alboroto? ¿Me sabotearé eligiendo JUL para mi biblioteca?
Rendimiento de prueba
(Sección agregada por nolan600 el 07-JUL-2012)
A continuación hay una referencia de Ceki acerca de que la parametrización de SLF4J es 10 veces más rápida que la de JUL. Así que he comenzado a hacer algunas pruebas simples. A primera vista, la afirmación es ciertamente correcta. Aquí están los resultados preliminares (¡pero sigue leyendo!):
- Tiempo de ejecución SLF4J, Backback Logback: 1515
- Tiempo de ejecución SLF4J, backend JUL: 12938
- Tiempo de ejecución JUL: 16911
Los números anteriores son msecs por lo que menos es mejor. Por lo tanto, 10 veces la diferencia de rendimiento es en realidad bastante cerca. Mi reacción inicial: ¡Eso es mucho!
Aquí está el núcleo de la prueba. Como puede verse, un entero y una cadena se construyen en un bucle que luego se usa en la declaración de registro:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Quería que la declaración de registro tuviera tanto un tipo de datos primitivo (en este caso, un int) como un tipo de datos más complejo (en este caso, un String). No estoy seguro de que importe, pero ahí está.)
La declaración de registro para SLF4J:
logger.info("Logging {} and {} ", i, someString);
La declaración de registro para julio:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
La JVM se ''calentó'' con la misma prueba ejecutada una vez antes de que se realizara la medición real. Java 1.7.03 se utilizó en Windows 7. Se utilizaron las últimas versiones de SLF4J (v1.6.6) y Logback (v1.0.6). Stdout y stderr se redirigieron al dispositivo nulo.
Sin embargo, tenga cuidado ahora, resulta que JUL pasa la mayor parte del tiempo en getSourceClassName()
porque JUL de forma predeterminada imprime el nombre de la clase de origen en la salida, mientras que Logback no lo hace. Así que estamos comparando manzanas y naranjas. Tengo que hacer la prueba de nuevo y configurar las implementaciones de registro de una manera similar para que realmente produzcan lo mismo. Sin embargo, sí sospecho que SLF4J + Logback todavía saldrá en la parte superior, pero lejos de los números iniciales como se indicó anteriormente. Manténganse al tanto.
Por cierto: la prueba fue la primera vez que realmente trabajé con SLF4J o Logback. Una experiencia agradable. JUL es ciertamente mucho menos acogedor cuando estás empezando.
Rendimiento de la prueba (parte 2)
(Sección agregada por nolan600 el 08-JUL-2012)
Resulta que en realidad no importa para el rendimiento cómo configura su patrón en JUL, es decir, si incluye o no el nombre de la fuente. Lo intenté con un patrón muy simple:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
y eso no cambió los tiempos anteriores en absoluto. Mi generador de perfiles reveló que el registrador aún pasaba mucho tiempo en llamadas a getSourceClassName()
incluso si esto no era parte de mi patrón. El patrón no importa.
Por lo tanto, estoy concluyendo sobre el tema del rendimiento que, al menos para la declaración de registro basada en la plantilla probada, parece haber aproximadamente un factor de 10 en la diferencia de rendimiento real entre JUL (lento) y SLF4J + Logback (rápido). Justo como dijo Ceki.
También puedo ver otra cosa, a saber, que la llamada getLogger()
SLF4J es mucho más costosa que el ítem de JUL. (95 ms frente a 0,3 ms si mi perfilador es exacto). Esto tiene sentido. SLF4J tiene que hacer algo de tiempo en el enlace de la implementación de registro subyacente. Esto no me asusta. Estas llamadas deben ser algo raras en la vida de una aplicación. La rapidez debe estar en las llamadas de registro reales.
Conclusión final
(Sección agregada por nolan600 el 08-JUL-2012)
Gracias por todas tus respuestas. Al contrario de lo que inicialmente pensé, decidí usar SLF4J para mi API. Esto se basa en una serie de cosas y su entrada:
Da flexibilidad para elegir la implementación del registro en el momento del despliegue.
Problemas con la falta de flexibilidad de la configuración de JUL cuando se ejecuta dentro de un servidor de aplicaciones.
SLF4J es sin duda mucho más rápido como se detalla anteriormente, en particular si lo acopla con Logback. Incluso si esto fue solo una prueba aproximada, tengo razones para creer que se ha invertido mucho más esfuerzo en la optimización en SLF4J + Logback que en JUL.
Documentación. La documentación para SLF4J es simplemente mucho más completa y precisa.
Patrón de flexibilidad. Cuando hice las pruebas, me propuse que JUL imitara el patrón predeterminado de Logback. Este patrón incluye el nombre del hilo. Resulta que JUL no puede hacer esto fuera de la caja. Ok, no lo he perdido hasta ahora, pero no creo que sea una cosa que deba faltar en un marco de registro. ¡Período!
La mayoría (o muchos) de los proyectos Java actuales utilizan Maven, por lo que agregar una dependencia no es algo tan grande, especialmente si esa dependencia es bastante estable, es decir, no cambia constantemente su API. Esto parece ser cierto para SLF4J. También el frasco SLF4J y sus amigos son pequeños.
Así que lo extraño que sucedió fue que me enojé bastante con JUL después de haber trabajado un poco con SLF4J. Todavía lamento que tenga que ser así con JUL. JUL está lejos de ser perfecto, pero hace un buen trabajo. Simplemente no lo suficientemente bien. Lo mismo se puede decir acerca de las Properties
como ejemplo, pero no pensamos en abstraer eso para que las personas puedan conectar su propia biblioteca de configuración y lo que sea. Creo que la razón es que Properties
viene justo por encima de la barra, mientras que lo contrario es cierto para JUL de hoy ... y en el pasado llegó a cero porque no existía.
Comencé, como tú, sospecho, usando JUL porque era el más fácil de comenzar de inmediato. A lo largo de los años, sin embargo, he llegado a desear haber pasado un poco más de tiempo eligiendo.
Mi principal problema ahora es que tenemos una cantidad sustancial de código de "biblioteca" que se usa en muchas aplicaciones y que todos usan JUL. Cada vez que uso estas herramientas en una aplicación de tipo de servicio web, el registro simplemente desaparece o va a algún lugar impredecible o extraño.
Nuestra solución fue agregar una fachada al código de la biblioteca, lo que significaba que las llamadas al registro de la biblioteca no cambiaban, pero se redirigían dinámicamente a cualquier mecanismo de registro disponible. Cuando se incluyen en una herramienta POJO, se dirigen a JUL, pero cuando se implementan como aplicaciones web, se redirigen a LogBack.
Nuestro arrepentimiento, por supuesto, es que el código de la biblioteca no utiliza el registro parametrizado, pero ahora se puede adaptar cuando sea necesario.
Utilizamos slf4j para construir la fachada.
Corrí jul contra slf4j-1.7.21 sobre logback-1.1.7, salida a un SSD, Java 1.8, Win64
Jul corrió 48449 ms, logback 27185 ms para un bucle 1M.
Aún así, un poco más de velocidad y un poco más de API no vale 3 bibliotecas y 800K para mí.
package log;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LogJUL
{
final static Logger logger = Logger.getLogger(LogJUL.class.getSimpleName());
public static void main(String[] args)
{
int N = 1024*1024;
long l = System.currentTimeMillis();
for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();
Object[] o = { lc };
logger.log(Level.INFO,"Epoch time {0}", o);
}
l = System.currentTimeMillis() - l;
System.out.printf("time (ms) %d%n", l);
}
}
y
package log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogSLF
{
static Logger logger = LoggerFactory.getLogger(LogSLF.class);
public static void main(String[] args)
{
int N = 1024*1024;
long l = System.currentTimeMillis();
for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();
logger.info("Epoch time {}", lc);
}
l = System.currentTimeMillis() - l;
System.out.printf("time (ms) %d%n", l);
}
}
En mi humilde opinión, la principal ventaja de usar una fachada de registro como slf4j es que le permite al usuario final de la biblioteca elegir qué implementación concreta de registro desea, en lugar de imponer su elección al usuario final.
Tal vez ha invertido tiempo y dinero en Log4j o LogBack (formateadores especiales, agregadores, etc.) y prefiere seguir utilizando Log4j o LogBack, en lugar de configurar jul. No hay problema: slf4j permite eso. ¿Es una sabia elección usar Log4j en julio? Tal vez tal vez no. Pero a ti no te importa. Deje que el usuario final elija lo que prefiera.
Descargo de responsabilidad : Soy el fundador de los proyectos log4j, SLF4J y logback.
Hay razones objetivas para preferir SLF4J. Por un lado, otorga al usuario final la libertad de elegir el marco de registro subyacente. Además, los usuarios más expertos tienden a preferir el inicio de sesión que ofrece capacidades más allá de log4j , con julio quedando muy por detrás. En lo que respecta a las características, el julio puede ser suficiente para algunos usuarios, pero para muchos otros no lo es. En pocas palabras, si el registro es importante para usted, desearía usar SLF4J con logback como la implementación subyacente. Si el registro no es importante, jul está bien.
Sin embargo, como desarrollador oss, debe tener en cuenta las preferencias de sus usuarios y no solo las suyas. Se deduce que debe adoptar SLF4J no porque esté convencido de que SLF4J es mejor que julio, sino porque la mayoría de los desarrolladores de Java actualmente (julio de 2012) prefieren SLF4J como su API de registro. Si finalmente decide no preocuparse por la opinión popular, considere los siguientes hechos:
- los que prefieren julio lo hacen por conveniencia porque julio está incluido con el JDK. Que yo sepa, no hay otros argumentos objetivos a favor de julio
- tu propia preferencia por jul es solo eso, una preferencia .
Por lo tanto, mantener los "hechos concretos" por encima de la opinión pública, aunque parece ser valiente, es una falacia lógica en este caso.
Si aún no está convencido, JB Nizet hace un argumento adicional y potente:
Excepto que el usuario final ya pudo haber hecho esta personalización para su propio código u otra biblioteca que use log4j o logback. jul es extensible, pero tener que extender logback, jul, log4j y solo Dios sabe qué otro marco de registro porque utiliza cuatro bibliotecas que usan cuatro marcos de registro diferentes es engorroso. Al utilizar SLF4J, le permite configurar los marcos de trabajo de registro que desea, no el que ha elegido. Recuerde que un proyecto típico utiliza miles de bibliotecas, y no solo la suya .
Si por alguna razón odia la API de SLF4J y usarla eliminará la diversión de su trabajo, entonces, por supuesto, vaya para julio. Después de todo, hay medios para redirigir a julio a SLF4J .
Por cierto, la parametrización de julio es al menos 10 veces más lenta que la de SLF4J, que termina marcando una diferencia notable.