Per la prima volta nella mia vita mi trovo in una posizione in cui sto scrivendo un'API Java che sarà di provenienza aperta. Spero di essere incluso in molti altri progetti.
Per la registrazione io (e in effetti le persone con cui lavoro) ho sempre usato JUL (java.util.logging) e non ho mai avuto problemi con esso. Tuttavia ora devo capire più dettagliatamente cosa dovrei fare per il mio sviluppo API. Ho fatto qualche ricerca su questo e con le informazioni che ho sono solo più confuso. Da qui questo post.
Da quando vengo da LUG sono di parte. La mia conoscenza del resto non è così grande.
Dalla ricerca che ho fatto ho scoperto questi motivi per cui alla gente non piace JUL:
"Ho iniziato a sviluppare in Java molto prima che Sun pubblicasse JUL ed era semplicemente più facile per me continuare con logging-framework-X piuttosto che imparare qualcosa di nuovo" . Hmm. Non sto scherzando, questo è ciò che la gente dice. Con questo argomento potremmo tutti fare COBOL. (tuttavia posso certamente relazionarmi con questo essere un tizio pigro me stesso)
"Non mi piacciono i nomi dei livelli di registrazione in LUG" . Ok, sul serio, questo non è solo un motivo sufficiente per introdurre una nuova dipendenza.
"Non mi piace il formato standard dell'output di JUL" . Hmm. Questa è solo configurazione. Non devi nemmeno fare nulla per quanto riguarda il codice. (vero, ai vecchi tempi potresti aver dovuto creare la tua classe Formatter per farlo bene).
"Uso altre librerie che usano anche logging-framework-X, quindi ho pensato che fosse più semplice usare quella" . Questo è un argomento circolare, no? Perché "tutti" usano logging-framework-X e non JUL?
"Tutti gli altri usano logging-framework-X" . Questo per me è solo un caso speciale di quanto sopra. La maggioranza non ha sempre ragione.
Quindi la vera grande domanda è perché non luglio?. Cosa mi sono perso? La ragion d'essere per le facciate di logging (SLF4J, JCL) è che le implementazioni di logging multiple sono esistite storicamente e la ragione di ciò risale all'era precedente a JUL come la vedo io. Se JUL fosse perfetto, allora non esisterebbero le facciate disboscate, o cosa? Per rendere le cose più confuse, JUL è in una certa misura una facciata stessa, che consente a Handlers, Formatters e persino LogManager di essere scambiati.
Piuttosto che abbracciare diversi modi di fare la stessa cosa (registrazione), non dovremmo chiederci perché fossero necessari in primo luogo? (e vedi se questi motivi esistono ancora)
Ok, la mia ricerca finora ha portato a un paio di cose che posso vedere potrebbero essere problemi reali con JUL:
Prestazioni . Alcuni dicono che le prestazioni in SLF4J sono superiori alle altre. Questo mi sembra un caso di ottimizzazione prematura. Se devi registrare centinaia di megabyte al secondo, non sono sicuro che tu sia sulla buona strada comunque. Anche JUL si è evoluto e i test eseguiti su Java 1.4 potrebbero non essere più veri. Puoi leggerlo qui e questa correzione è diventata Java 7. Molti parlano anche del sovraccarico della concatenazione di stringhe nei metodi di registrazione. Tuttavia, la registrazione basata su modelli evita questo costo ed esiste anche in luglio. Personalmente non scrivo mai realmente la registrazione basata su template. Troppo pigro per quello. Ad esempio se lo faccio con JUL:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
il mio IDE mi avvertirà e chiederà il permesso di cambiarlo in:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
.. che accetterò ovviamente. Permesso accordato ! Grazie per l'aiuto.
Quindi in realtà non scrivo queste dichiarazioni da solo, fatto dall'IDE.
In conclusione sul tema della performance non ho trovato nulla che suggerisca che la performance di JUL non è ok rispetto alla concorrenza.
Configurazione da classpath . JUL pronto all'uso non può caricare un file di configurazione dal percorso di classe. Sono poche righe di codice per farlo. Posso capire perché questo può essere fastidioso, ma la soluzione è breve e semplice.
Disponibilità dei gestori di output . JUL viene fornito con 5 gestori di output pronti all'uso: console, flusso di file, socket e memoria. Questi possono essere estesi o nuovi possono essere scritti. Questo può ad esempio scrivere su Syslog UNIX / Linux e sul registro eventi di Windows. Personalmente non ho mai avuto questo requisito, né l'ho visto usato, ma posso certamente capire perché potrebbe essere una caratteristica utile. Ad esempio, Logback viene fornito con un appender per Syslog. Lo direi comunque
- Il 99,5% del fabbisogno di destinazioni di output è coperto da ciò che è pronto per l'uso.
- Le esigenze speciali potrebbero essere soddisfatte dai gestori personalizzati sopra JUL piuttosto che su qualcos'altro. Non c'è nulla per me che suggerisce che ci vuole più tempo per scrivere un gestore di output Syslog per JUL di quanto non faccia per un altro framework di registrazione.
Sono davvero preoccupato che ci sia qualcosa che ho trascurato. L'uso delle facciate di registrazione e delle implementazioni di registrazione diverse da JUL è così diffuso che devo giungere alla conclusione che sono io che non capisco. Non sarebbe la prima volta, temo. :-)
Quindi cosa devo fare con la mia API? Voglio che abbia successo. Ovviamente posso solo "seguire il flusso" e implementare SLF4J (che sembra il più popolare in questi giorni) ma per il mio bene ho ancora bisogno di capire esattamente cosa c'è di sbagliato nel luglio di oggi che garantisce tutto il fuzz? Mi saboterò scegliendo JUL per la mia biblioteca?
Test delle prestazioni
(sezione aggiunta da nolan600 il 07-LUG-2012)
Di seguito c'è un riferimento di Ceki sul fatto che la parametrizzazione di SLF4J sia 10 volte o più veloce di quella di JUL. Quindi ho iniziato a fare alcuni semplici test. A prima vista l'affermazione è certamente corretta. Ecco i risultati preliminari (ma continua a leggere!):
- Tempo di esecuzione SLF4J, backend Logback: 1515
- Tempo di esecuzione SLF4J, backend LUG: 12938
- Tempo di esecuzione LUG: 16911
I numeri sopra sono in msec, quindi meno è meglio. Quindi la differenza di prestazioni 10 volte è in realtà piuttosto vicina. La mia reazione iniziale: è molto!
Ecco il nucleo del test. Come si può vedere un numero intero e una stringa viene costruita in un ciclo che viene quindi utilizzato nell'istruzione log:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Volevo che l'istruzione di registro avesse sia un tipo di dati primitivo (in questo caso un int) sia un tipo di dati più complesso (in questo caso una stringa). Non sono sicuro che sia importante, ma il gioco è fatto.)
L'istruzione di registro per SLF4J:
logger.info("Logging {} and {} ", i, someString);
L'istruzione di registro per JUL:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
La JVM è stata "riscaldata" con lo stesso test eseguito una volta prima che fosse effettuata la misurazione effettiva. Java 1.7.03 è stato utilizzato su Windows 7. Sono state utilizzate le ultime versioni di SLF4J (v1.6.6) e Logback (v1.0.6). Stdout e stderr sono stati reindirizzati al dispositivo null.
Tuttavia, ora attento, risulta che JUL sta trascorrendo la maggior parte del tempo getSourceClassName()
perché JUL di default stampa il nome della classe di origine nell'output, mentre Logback no. Quindi stiamo confrontando mele e arance. Devo ripetere il test e configurare le implementazioni di registrazione in un modo simile in modo che producano effettivamente le stesse cose. Tuttavia, sospetto che SLF4J + Logback uscirà ancora in cima ma lontano dai numeri iniziali come indicato sopra. Rimanete sintonizzati.
A proposito: il test è stato la prima volta che ho effettivamente lavorato con SLF4J o Logback. Un'esperienza piacevole. LUG è sicuramente molto meno accogliente quando inizi.
Test delle prestazioni (parte 2)
(sezione aggiunta da nolan600 l'8-LUG-2012)
A quanto pare, per le prestazioni non importa davvero come si configura il modello in JUL, ovvero se include o meno il nome della sorgente. Ho provato con un modello molto semplice:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
e ciò non ha modificato affatto i tempi sopra indicati. Il mio profiler ha rivelato che il logger impiegava ancora molto tempo nelle chiamate getSourceClassName()
anche se questo non faceva parte del mio schema. Lo schema non ha importanza.
Sto quindi concludendo sul problema delle prestazioni che almeno per l'istruzione di registro testata basata su modello sembra esserci all'incirca un fattore 10 nella differenza di prestazioni reale tra JUL (lento) e SLF4J + Logback (rapido). Proprio come ha detto Ceki.
Vedo anche un'altra cosa: la getLogger()
chiamata di SLF4J è molto più costosa di quella di JUL. (95 ms vs 0,3 ms se il mio profiler è preciso). Questo ha senso. SLF4J deve fare un po 'di tempo sull'associazione dell'implementazione di registrazione sottostante. Questo non mi spaventa. Queste chiamate dovrebbero essere piuttosto rare nel corso della vita di un'applicazione. La solidità dovrebbe essere nelle chiamate di registro effettive.
Conclusione finale
(sezione aggiunta da nolan600 l'8-LUG-2012)
Grazie per tutte le tue risposte. Contrariamente a quanto inizialmente pensavo di aver deciso di utilizzare SLF4J per la mia API. Questo si basa su una serie di cose e sul tuo contributo:
Offre flessibilità nella scelta dell'implementazione dei log al momento della distribuzione.
Problemi con mancanza di flessibilità della configurazione di JUL quando eseguito all'interno di un server delle applicazioni.
SLF4J è sicuramente molto più veloce come descritto sopra in particolare se lo abbini a Logback. Anche se questo è stato solo un test approssimativo, ho motivo di credere che sono stati fatti molti più sforzi nell'ottimizzazione su SLF4J + Logback che su JUL.
Documentazione. La documentazione per SLF4J è semplicemente molto più completa e precisa.
Flessibilità del modello. Come ho fatto i test ho deciso di fare in modo che JUL imitasse il modello predefinito da Logback. Questo modello include il nome del thread. Si scopre che JUL non può farlo immediatamente. Ok, non l'ho perso fino ad ora, ma non credo sia una cosa che dovrebbe mancare in un framework di log. Periodo!
La maggior parte (o molti) progetti Java oggi usano Maven, quindi l'aggiunta di una dipendenza non è così importante soprattutto se tale dipendenza è piuttosto stabile, cioè non cambia costantemente la sua API. Questo sembra essere vero per SLF4J. Anche il vaso SLF4J e gli amici sono di piccole dimensioni.
Quindi la cosa strana che è successa è che mi sono davvero arrabbiato con JUL dopo aver lavorato un po 'con SLF4J. Mi dispiace ancora che debba essere così con JUL. LUG è tutt'altro che perfetto ma in qualche modo fa il lavoro. Solo non abbastanza bene. Lo stesso si può dire Properties
come esempio, ma non pensiamo all'astrattismo in modo che le persone possano collegare la propria libreria di configurazione e cosa hai. Penso che il motivo sia che Properties
arriva appena sopra la barra mentre è vero il contrario per JUL di oggi ... e in passato è arrivato a zero perché non esisteva.
java.lang.System.Logger
, che è un'interfaccia , che può essere reindirizzata a qualsiasi framework di registrazione che si desidera, purché tale framework raggiunga e fornisca un'implementazione di tale interfaccia. In combinazione con la modularizzazione, è anche possibile distribuire un'applicazione con un JRE in bundle non contenente java.util.logging
, se si preferisce un framework diverso.