Flogger Registrazione fluente

1. Panoramica

In questo tutorial, parleremo del framework Flogger, un'API di registrazione fluente per Java progettata da Google.

2. Perché utilizzare Flogger?

Con tutti i framework di registrazione attualmente sul mercato, come Log4j e Logback, perché abbiamo bisogno di un altro framework di registrazione?

Si scopre che Flogger ha diversi vantaggi rispetto ad altri framework: diamo un'occhiata.

2.1. Leggibilità

La natura fluida dell'API di Flogger contribuisce notevolmente a renderlo più leggibile.

Diamo un'occhiata a un esempio in cui vogliamo registrare un messaggio ogni dieci iterazioni.

Con un framework di registrazione tradizionale, vedremmo qualcosa di simile:

int i = 0; // ... if (i % 10 == 0) { logger.info("This log shows every 10 iterations"); i++; }

Ma ora, con Flogger, quanto sopra può essere semplificato per:

logger.atInfo().every(10).log("This log shows every 10 iterations");

Mentre si potrebbe sostenere che la versione Flogger dell'istruzione logger sembra un po 'più prolissa rispetto alle versioni tradizionali, consente una maggiore funzionalità e alla fine porta a dichiarazioni di registro più leggibili ed espressive .

2.2. Prestazione

Gli oggetti di registrazione sono ottimizzati fintanto che evitiamo di chiamare toString sugli oggetti registrati:

User user = new User(); logger.atInfo().log("The user is: %s", user);

Se effettuiamo il log, come mostrato sopra, il backend ha la possibilità di ottimizzare il logging. D'altra parte, se chiamiamo toString direttamente o concateniamo le stringhe, questa opportunità viene persa:

logger.atInfo().log("Ths user is: %s", user.toString()); logger.atInfo().log("Ths user is: %s" + user);

2.3. Estensibilità

Il framework Flogger copre già la maggior parte delle funzionalità di base che ci aspetteremmo da un framework di registrazione.

Tuttavia, ci sono casi in cui avremmo bisogno di aggiungere alla funzionalità. In questi casi è possibile estendere l'API.

Attualmente, ciò richiede una classe di supporto separata. Potremmo, ad esempio, estendere l'API di Flogger scrivendo una classe UserLogger :

logger.at(INFO).forUserId(id).withUsername(username).log("Message: %s", param);

Questo potrebbe essere utile nei casi in cui desideriamo formattare il messaggio in modo coerente. L'UserLogger sarebbe quindi fornire l'implementazione per l'metodi personalizzati forUserId (String id) e withUsername (String username).

Per fare ciò, la classe UserLogger dovrà estendere la classe AbstractLogger e fornire un'implementazione per l'API . Se guardiamo FluentLogger , è solo un logger senza metodi aggiuntivi, possiamo, quindi, iniziare copiando questa classe così com'è, e poi costruire da questa base aggiungendo metodi ad essa.

2.4. Efficienza

I framework tradizionali utilizzano ampiamente i vararg. Questi metodi richiedono l' allocazione e il riempimento di un nuovo Object [] prima che il metodo possa essere richiamato. Inoltre, tutti i tipi fondamentali passati devono essere inseriti automaticamente.

Tutto questo costa bytecode e latenza aggiuntivi nel sito della chiamata. È particolarmente sfortunato se l'istruzione log non è effettivamente abilitata. Il costo diventa più evidente nei log a livello di debug che appaiono spesso nei loop. Flogger abbassa questi costi evitando totalmente i vararg.

Flogger aggira questo problema utilizzando una catena di chiamate fluida da cui è possibile creare istruzioni di registrazione. Ciò consente al framework di avere solo un piccolo numero di sostituzioni al metodo di log , e quindi essere in grado di evitare cose come vararg e auto-boxing. Ciò significa che l'API può ospitare una varietà di nuove funzionalità senza un'esplosione combinatoria.

Un tipico framework di registrazione avrebbe questi metodi:

level(String, Object) level(String, Object...)

dove level può essere uno dei sette nomi dei livelli di log ( severo ad esempio), oltre ad avere un metodo di log canonico che accetta un livello di log aggiuntivo:

log(Level, Object...)

Oltre a questo, di solito ci sono varianti dei metodi che accettano una causa ( un'istanza Throwable ) associata all'istruzione log:

level(Throwable, String, Object) level(Throwable, String, Object...)

È chiaro che l'API sta accoppiando tre preoccupazioni in una chiamata al metodo:

  1. Sta cercando di specificare il livello di log (scelta del metodo)
  2. Tentativo di allegare metadati all'istruzione log ( causa Throwable )
  3. E inoltre, specificando il messaggio di log e gli argomenti.

Questo approccio moltiplica rapidamente il numero di diversi metodi di registrazione necessari per soddisfare queste preoccupazioni indipendenti.

Ora possiamo vedere perché è importante avere due metodi nella catena:

logger.atInfo().withCause(e).log("Message: %s", arg);

Diamo ora un'occhiata a come possiamo usarlo nella nostra base di codice.

3. Dipendenze

È abbastanza semplice configurare Flogger. Dobbiamo solo aggiungere flogger e flogger-system-backend al nostro pom:

  com.google.flogger flogger 0.4   com.google.flogger flogger-system-backend 0.4 runtime  

Con queste dipendenze configurate, ora possiamo continuare ad esplorare l'API che è a nostra disposizione.

4. Esplorazione dell'API Fluent

Prima di tutto, dichiariamo un'istanza statica per il nostro logger:

private static final FluentLogger logger = FluentLogger.forEnclosingClass();

E ora possiamo iniziare a registrare. Inizieremo con qualcosa di semplice:

int result = 45 / 3; logger.atInfo().log("The result is %d", result);

I messaggi di registro possono utilizzare qualsiasi identificatore di formato printf di Java , come % s,% d o % 016x .

4.1. Evitare il lavoro nei siti di registro

I creatori di flogger consigliano di evitare di lavorare sul sito di log.

Let's say we have the following long-running method for summarising the current state of a component:

public static String collectSummaries() { longRunningProcess(); int items = 110; int s = 30; return String.format("%d seconds elapsed so far. %d items pending processing", s, items); }

It's tempting to call collectSummaries directly in our log statement:

logger.atFine().log("stats=%s", collectSummaries());

Regardless of the configured log levels or rate-limiting, though, the collectSummaries method will now be called every time.

Making the cost of disabled logging statements virtually free is at the core of the logging framework. This, in turn, means that more of them can be left intact in the code without harm. Writing the log statement like we just did takes away this advantage.

Instead, we should do use the LazyArgs.lazy method:

logger.atFine().log("stats=%s", LazyArgs.lazy(() -> collectSummaries()));

Now, almost no work is done at the log site — just instance creation for the lambda expression. Flogger will only evaluate this lambda if it intends to actually log the message.

Although it's allowed to guard log statements using isEnabled:

if (logger.atFine().isEnabled()) { logger.atFine().log("summaries=%s", collectSummaries()); }

This is not necessary and we should avoid it because Flogger does these checks for us. This approach also only guards log statements by level and does not help with rate-limited log statements.

4.2. Dealing With Exceptions

How about exceptions, how do we handle them?

Well, Flogger comes with a withStackTrace method that we can use to log a Throwable instance:

try { int result = 45 / 0; } catch (RuntimeException re) { logger.atInfo().withStackTrace(StackSize.FULL).withCause(re).log("Message"); }

Where withStackTrace takes as an argument the StackSize enum with constant values SMALL, MEDIUM, LARGE or FULL. A stack trace generated by withStackTrace() will show up as a LogSiteStackTrace exception in the default java.util.logging backend. Other backends may choose to handle this differently though.

4.3. Logging Configuration and Levels

So far we've been using logger.atInfo in most of our examples, but Flogger does support many other levels. We'll look at these, but first, let's introduce how to configure the logging options.

To configure logging, we use the LoggerConfig class.

For example, when we want to set the logging level to FINE:

LoggerConfig.of(logger).setLevel(Level.FINE);

And Flogger supports various logging levels:

logger.atInfo().log("Info Message"); logger.atWarning().log("Warning Message"); logger.atSevere().log("Severe Message"); logger.atFine().log("Fine Message"); logger.atFiner().log("Finer Message"); logger.atFinest().log("Finest Message"); logger.atConfig().log("Config Message");

4.4. Rate Limiting

How about the issue of rate-limiting? How do we handle the case where we don't want to log every iteration?

Flogger comes to our rescue with the every(int n) method:

IntStream.range(0, 100).forEach(value -> { logger.atInfo().every(40).log("This log shows every 40 iterations => %d", value); });

We get the following output when we run the code above:

Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0 INFO: This log shows every 40 iterations => 0 [CONTEXT ratelimit_count=40 ] Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0 INFO: This log shows every 40 iterations => 40 [CONTEXT ratelimit_count=40 ] Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0 INFO: This log shows every 40 iterations => 80 [CONTEXT ratelimit_count=40 ]

What if we want to log say every 10 seconds? Then, we can use atMostEvery(int n, TimeUnit unit):

IntStream.range(0, 1_000_0000).forEach(value -> { logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value); });

With this, the outcome now becomes:

Sep 18, 2019 5:08:06 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1 INFO: This log shows [every 10 seconds] => 0 [CONTEXT ratelimit_period="10 SECONDS" ] Sep 18, 2019 5:08:16 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1 INFO: This log shows [every 10 seconds] => 3545373 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3545372]" ] Sep 18, 2019 5:08:26 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1 INFO: This log shows [every 10 seconds] => 7236301 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3690927]" ]

5. Using Flogger With Other Backends

So, what if we would like to add Flogger to our existing application that is already using say Slf4j or Log4j for example? This could be useful in cases where we would want to take advantage of our existing configurations. Flogger supports multiple backends as we'll see.

5.1 Flogger With Slf4j

It's simple to configure an Slf4j back-end. First, we need to add the flogger-slf4j-backend dependency to our pom:

 com.google.flogger flogger-slf4j-backend 0.4 

Next, we need to tell Flogger that we would like to use a different back-end from the default one. We do this by registering a Flogger factory through system properties:

System.setProperty( "flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");

And now our application will use the existing configuration.

5.1 Flogger With Log4j

Seguiamo passaggi simili per la configurazione del back-end di Log4j. Aggiungiamo la dipendenza flogger-log4j-backend al nostro pom :

 com.google.flogger flogger-log4j-backend 0.4   com.sun.jmx jmxri   com.sun.jdmk jmxtools   javax.jms jms     log4j log4j 1.2.17   log4j apache-log4j-extras 1.2.17 

Dobbiamo anche registrare una factory back-end di Flogger per Log4j:

System.setProperty( "flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");

E questo è tutto, la nostra applicazione è ora configurata per utilizzare le configurazioni Log4j esistenti!

6. Conclusione

In questo tutorial, abbiamo visto come utilizzare il framework Flogger come alternativa ai framework di registrazione tradizionali. Abbiamo visto alcune potenti funzionalità di cui possiamo trarre vantaggio quando usiamo il framework.

Abbiamo anche visto come possiamo sfruttare le nostre configurazioni esistenti registrando diversi back-end come Slf4j e Log4j.

Come al solito, il codice sorgente di questo tutorial è disponibile su GitHub.