Por primera vez en mi vida me encuentro en una posición en la que escribo una API de Java que será de código abierto. Con suerte para ser incluido en muchos otros proyectos.
Para iniciar sesión, yo (y de hecho las personas con las que trabajo) siempre he usado JUL (java.util.logging) y nunca he tenido ningún problema. Sin embargo, ahora necesito entender con más detalle lo que debo hacer para mi desarrollo de API. He investigado un poco sobre esto y con la información que tengo me confundo más. De ahí esta publicación.
Desde que vengo de JUL, soy parcial en eso. Mi conocimiento del resto no es tan grande.
De la investigación que he hecho, he llegado a estas razones por las cuales 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 en lugar de aprender algo nuevo" . Hmm No estoy bromeando, esto es realmente lo que dice la gente. Con este argumento todos podríamos estar haciendo COBOL. (Sin embargo, ciertamente puedo relacionarme con esto siendo un tipo perezoso)
"No me gustan los nombres de los niveles de registro en JUL" . Ok, en serio, esta no es razón suficiente para introducir una nueva dependencia.
"No me gusta el formato estándar de la salida de JUL" . Hmm Esto es solo configuración. Ni siquiera tiene que hacer nada en cuanto a código. (Es cierto, en los viejos tiempos es posible que haya tenido que crear su propia clase de formateador para hacerlo bien).
"Uso otras bibliotecas que también usan logging-framework-X, así que pensé que era más fácil usar esa" . Este es un argumento circular, ¿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 solo un caso especial de lo anterior. La mayoría no siempre tiene la razón.
Entonces, la verdadera gran pregunta es ¿por qué no JUL? . ¿Qué es lo que me he perdido? La razón de ser para las fachadas de registro (SLF4J, JCL) es que las implementaciones de registro múltiples han existido históricamente y la razón de eso 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é? Para hacer las cosas más confusas, JUL es, en cierta medida, una fachada en sí misma, permitiendo intercambiar manipuladores, formateadores e incluso el LogManager.
En lugar de adoptar múltiples formas de hacer lo mismo (iniciar sesión), ¿no deberíamos preguntarnos por qué fueron necesarias en primer lugar? (y ver si esas razones aún existen)
Ok, mi investigación hasta ahora 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 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 las pruebas que realizó en Java 1.4 pueden no ser ciertas. Puede leer sobre esto aquí y esta solución ha llegado a Java 7. Muchos también hablan sobre la sobrecarga de la 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 JUL. Personalmente, nunca escribo un 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 que lo cambie a:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
.. que por supuesto aceptaré. Permiso concedido ! Gracias por tu ayuda.
Entonces, en realidad no escribo tales declaraciones, eso lo hace el IDE.
En conclusión sobre el tema del rendimiento, no he encontrado nada que sugiera que el rendimiento de JUL no está bien en comparación con la competencia.
Configuración desde classpath . El JUL listo para usar no puede cargar un archivo de configuración desde el classpath. Son unas pocas líneas de código para 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 se pueden ampliar o se pueden escribir otros nuevos. Esto puede, por ejemplo, estar escribiendo en UNIX / Linux Syslog y Windows Event Log. Personalmente nunca he tenido este requisito ni lo he visto utilizado, pero ciertamente puedo relacionarme por qué puede ser una característica útil. Logback viene con un apéndice para Syslog, por ejemplo. Aún así argumentaría que
- El 99.5% de las necesidades de destinos de salida están cubiertos por lo que está en JUL listo para usar.
- Las necesidades especiales podrían ser atendidas por manejadores personalizados en la parte superior de JUL en lugar de otra cosa. No hay nada para mí que sugiera que se necesita más tiempo para escribir un controlador de salida de Syslog para JUL que para otro marco de registro.
Me preocupa mucho que haya algo que he pasado por alto. El uso de fachadas de registro e implementaciones de registro que no sean JUL está tan extendido que tengo que llegar a la conclusión de que soy yo quien simplemente no comprende. 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 la corriente" e implementar SLF4J (que parece ser el más popular en estos días), pero por mi propio bien, todavía necesito entender exactamente qué está mal con el JUL de hoy que justifica toda la confusión. ¿Me sabotearé eligiendo JUL para mi biblioteca?
Prueba de rendimiento
(sección agregada por nolan600 el 07-JUL-2012)
Hay una referencia a continuación de Ceki acerca de que la parametrización de SLF4J es 10 veces o más rápida que la de JUL. Así que comencé 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, back-end Logback: 1515
- Tiempo de ejecución SLF4J, backend JUL: 12938
- Tiempo de ejecución JUL: 16911
Los números anteriores son ms, por lo que menos es mejor. Entonces, la diferencia de rendimiento 10 veces es realmente bastante cercana. Mi reacción inicial: ¡eso es mucho!
Aquí está el núcleo de la prueba. Como se puede ver, 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 instrucción de registro tuviera un tipo de datos primitivo (en este caso, un int) y un tipo de datos más complejo (en este caso, una Cadena). No estoy seguro de que sea importante, pero ahí lo tiene.
La declaración de registro para SLF4J:
logger.info("Logging {} and {} ", i, someString);
La declaración de registro para JUL:
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. Se utilizó Java 1.7.03 en Windows 7. Se utilizaron las últimas versiones de SLF4J (v1.6.6) y Logback (v1.0.6). Stdout y stderr se redirigieron a un dispositivo nulo.
Sin embargo, con cuidado ahora, resulta que JUL pasa la mayor parte de su tiempo getSourceClassName()
porque JUL imprime de manera predeterminada el nombre de la clase de origen en la salida, mientras que Logback no lo hace. Entonces estamos comparando manzanas y naranjas. Tengo que volver a hacer la prueba y configurar las implementaciones de registro de manera similar para que realmente produzcan lo mismo. Sin embargo, sospecho que SLF4J + Logback seguirá apareciendo 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 placentera. JUL es ciertamente mucho menos acogedor cuando estás comenzando.
Prueba de rendimiento (parte 2)
(sección agregada por nolan600 el 08-JUL-2012)
Como resultado, realmente 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ó en absoluto los tiempos anteriores. Mi generador de perfiles reveló que el registrador aún pasaba mucho tiempo en llamadas, 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 getLogger()
llamada de SLF4J es mucho más cara que el ídem de JUL. (95 ms frente a 0,3 ms si mi generador de perfiles es preciso). Esto tiene sentido. SLF4J tiene que dedicar un tiempo a la vinculación de la implementación de registro subyacente. Esto no me asusta. Estas llamadas deberían ser algo raras en la vida útil de una aplicación. La solidez 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é, terminé decidiendo usar SLF4J para mi API. Esto se basa en una serie de cosas y su aporte:
Ofrece flexibilidad para elegir la implementación del registro en el momento de la implementación.
Problemas con la falta de flexibilidad de la configuración de JUL cuando se ejecuta dentro de un servidor de aplicaciones.
SLF4J es ciertamente mucho más rápido como se detalla anteriormente en particular si lo combina con Logback. Incluso si esto fuera solo una prueba aproximada, tengo razones para creer que se ha dedicado mucho más esfuerzo a 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 me lo he perdido hasta ahora, pero no creo que sea algo que deba faltar en un marco de registro. ¡Período!
La mayoría (o muchos) proyectos de Java hoy en día usan Maven, por lo que agregar una dependencia no es tan importante, especialmente si esa dependencia es bastante estable, es decir, no cambia constantemente su API. Esto parece ser cierto para SLF4J. Además, el frasco SLF4J y sus amigos son de tamaño pequeño.
Entonces, lo extraño que sucedió fue que realmente me molesté 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 el trabajo. Simplemente no lo suficientemente bien. Se puede decir lo mismo Properties
como un ejemplo, pero no pensamos en abstraerlo para que las personas puedan conectar su propia biblioteca de configuración y lo que tengan. 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.
InternalLoggerFactory.java
.java.lang.System.Logger
, que es una interfaz , que se puede redirigir a cualquier marco de registro real que desee, siempre que ese marco se ponga al día y proporcione una implementación de esa interfaz. En combinación con la modularización, incluso podría implementar una aplicación con un JRE incluido que no contengajava.util.logging
, si prefiere un marco diferente.Respuestas:
Descargo de responsabilidad : soy el fundador de los proyectos log4j, SLF4J y logback.
Hay razones objetivas para preferir SLF4J. Por un lado, SLF4J permite al usuario final la libertad de elegir el marco de registro subyacente . Además, los usuarios más inteligentes tienden a preferir el inicio de sesión que ofrece capacidades más allá de log4j , con julio muy por detrás. Julio de funciones puede ser suficiente para algunos usuarios, pero para muchos otros simplemente no lo es. En pocas palabras, si el registro es importante para usted, querrá usar SLF4J con logback como la implementación subyacente. Si el registro no es importante, julio está bien.
Sin embargo, como desarrollador de 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:
Por lo tanto, mantener "hechos concretos" por encima de la opinión pública, aunque aparentemente valiente, es una falacia lógica en este caso.
Si aún no está convencido, JB Nizet hace un argumento adicional y potente:
Si por alguna razón odias la API SLF4J y usarla te quitará la diversión de tu trabajo, entonces ve por julio Después de todo, hay medios para redirigir julio a SLF4J .
Por cierto, la parametrización de julio es al menos 10 veces más lenta que la SLF4J, lo que termina marcando una diferencia notable.
fuente
java.util.logging
fue introducido en Java 1.4. Hubo usos para el registro antes de eso, es por eso que existen muchas otras API de registro. Esas API se usaron mucho antes de Java 1.4 y, por lo tanto, tenían una gran cuota de mercado que no solo cayó a 0 cuando se lanzó 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 supongo que también en 6, 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 (volver a implementar efectivamente JUL si lo entendí correctamente).
No siempre puede influir en el marco de registro que usan sus bibliotecas. Por lo tanto, el uso de SLF4J (que en realidad es solo una capa API muy delgada por encima de otras bibliotecas) ayuda a mantener una imagen algo coherente de todo el mundo de registro (para que pueda decidir el marco de registro subyacente mientras todavía tiene 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 puede cambiar fácilmente a logging-library-Y (por ejemplo, JUL), incluso si esta última es claramente soberbia: cualquier usuario de esa biblioteca necesitaría aprender el nuevo marco de registro y (al menos) reconfigurar su registro. Eso es un gran no-no, especialmente cuando no trae ganancias aparentes para la mayoría de las personas.
Dicho todo esto, creo que JUL es al menos una alternativa válida a otros marcos de registro en estos días.
fuente
En mi humilde opinión, la principal ventaja de utilizar una fachada de registro como slf4j es que permite que el usuario final de la biblioteca elija 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, apéndices, etc.) y prefiere continuar usando Log4j o LogBack, en lugar de configurar jul. No hay problema: slf4j lo permite. ¿Es una buena elección usar Log4j en julio? Tal vez tal vez no. Pero no te importa. Deje que el usuario final elija lo que prefiera.
fuente
Comencé, como usted sospecho, a usar JUL porque era el más fácil de poner en marcha de inmediato. Con 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 todas usan JUL. Cada vez que uso estas herramientas en una aplicación de tipo de servicio web, el registro simplemente desaparece o va a un lugar impredecible o extraño.
Nuestra solución fue agregar una fachada al código de la biblioteca que significara que las llamadas de registro de la biblioteca no cambiaron sino que se redirigieron dinámicamente a cualquier mecanismo de registro disponible. Cuando se incluyen en una herramienta POJO, se dirigen a JUL, pero cuando se implementan como una aplicación web, se redirigen a LogBack.
Lamentamos, por supuesto, es que el código de la biblioteca no utilice el registro parametrizado, pero ahora se puede adaptar cuando sea necesario.
Utilizamos slf4j para construir la fachada.
fuente
Ejecuté jul contra slf4j-1.7.21 sobre logback-1.1.7, salida a un SSD, Java 1.8, Win64
jul ejecutó 48449 ms, inicio de sesión 27185 ms para un bucle de 1M.
Aún así, un poco más de velocidad y una API un poco más agradable no valen 3 bibliotecas y 800K para mí.
y
fuente
logger.info()
. Por lo tanto, está paralizando deliberadamente el rendimiento de julio para compensar una deficiencia en la interfaz de slf4j. En su lugar, debe codificar ambos métodos de la forma en que están codificados idiomáticamente.