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.
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 contenga java.util.logging
, si prefiere un marco diferente.