Enregistrement Flogger Fluent

1. Vue d'ensemble

Dans ce tutoriel, nous allons parler du framework Flogger, une API de journalisation fluide pour Java conçue par Google.

2. Pourquoi utiliser Flogger?

Avec tous les cadres de journalisation actuellement sur le marché, comme Log4j et Logback, pourquoi avons-nous besoin d'un autre cadre de journalisation?

Il s'avère que Flogger présente plusieurs avantages par rapport aux autres frameworks - jetons un coup d'œil.

2.1. Lisibilité

La nature fluide de l'API de Flogger contribue grandement à la rendre plus lisible.

Regardons un exemple où nous voulons enregistrer un message toutes les dix itérations.

Avec un cadre de journalisation traditionnel, nous verrions quelque chose comme:

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

Mais maintenant, avec Flogger, ce qui précède peut être simplifié pour:

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

Alors que l'on dirait que la version Flogger de l'instruction logger semble un peu plus verbeuse que les versions traditionnelles, elle permet une plus grande fonctionnalité et conduit finalement à des instructions de journal plus lisibles et plus expressives .

2.2. Performance

Les objets de journalisation sont optimisés tant que nous évitons d'appeler toString sur les objets journalisés:

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

Si nous nous connectons, comme indiqué ci-dessus, le backend a la possibilité d'optimiser la journalisation. En revanche, si nous appelons toString directement ou concaténons les chaînes, cette opportunité est perdue:

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

2.3. Extensibilité

Le framework Flogger couvre déjà la plupart des fonctionnalités de base que nous attendons d'un framework de journalisation.

Cependant, il y a des cas où nous aurions besoin d'ajouter des fonctionnalités. Dans ces cas, il est possible d'étendre l'API.

Actuellement, cela nécessite une classe de support distincte. Nous pourrions, par exemple, étendre l'API Flogger en écrivant une classe UserLogger :

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

Cela pourrait être utile dans les cas où nous souhaitons formater le message de manière cohérente. Le UserLogger fournirait alors l'implémentation des méthodes personnalisées forUserId (String id) et withUsername (String username).

Pour ce faire, la classe UserLogger devra étendre la classe AbstractLogger et fournir une implémentation pour l'API . Si nous regardons FluentLogger , c'est juste un enregistreur sans méthodes supplémentaires, nous pouvons donc commencer par copier cette classe telle quelle , puis construire à partir de cette base en y ajoutant des méthodes.

2.4. Efficacité

Les frameworks traditionnels utilisent largement les varargs. Ces méthodes nécessitent qu'un nouvel Object [] soit alloué et rempli avant que la méthode puisse être appelée. En outre, tous les types fondamentaux transmis doivent être automatiquement encadrés.

Tout cela coûte un bytecode et une latence supplémentaires sur le site d'appel. C'est particulièrement malheureux si l'instruction de journal n'est pas réellement activée. Le coût devient plus apparent dans les journaux de niveau de débogage qui apparaissent souvent dans des boucles. Flogger évite ces coûts en évitant totalement les varargs.

Flogger contourne ce problème en utilisant une chaîne d'appels fluide à partir de laquelle les instructions de journalisation peuvent être construites. Cela permet au framework de n'avoir qu'un petit nombre de substitutions à la méthode de journalisation , et ainsi d'être en mesure d'éviter des choses comme les varargs et l'auto-boxing. Cela signifie que l'API peut accueillir une variété de nouvelles fonctionnalités sans explosion combinatoire.

Un cadre de journalisation typique aurait ces méthodes:

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

level peut être l'un des sept noms de niveau de journal ( sévère par exemple), ainsi qu'une méthode de journal canonique qui accepte un niveau de journal supplémentaire:

log(Level, Object...)

En plus de cela, il existe généralement des variantes des méthodes qui prennent une cause (une instance Throwable ) associée à l'instruction de journal:

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

Il est clair que l'API associe trois préoccupations en un seul appel de méthode:

  1. Il essaie de spécifier le niveau de journalisation (choix de la méthode)
  2. Tentative d'attachement de métadonnées à l'instruction de journal ( cause pouvant être rejetée )
  3. Et aussi, en spécifiant le message et les arguments du journal.

Cette approche multiplie rapidement le nombre de méthodes d'exploitation différentes nécessaires pour répondre à ces préoccupations indépendantes.

Nous pouvons maintenant voir pourquoi il est important d'avoir deux méthodes dans la chaîne:

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

Voyons maintenant comment nous pouvons l'utiliser dans notre base de code.

3. Dépendances

C'est assez simple de configurer Flogger. Nous avons juste besoin d'ajouter Flogger et Flogger-system-backend à notre pom:

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

Une fois ces dépendances mises en place, nous pouvons maintenant explorer l'API qui est à notre disposition.

4. Explorer l'API Fluent

Tout d'abord, déclarons une instance statique pour notre enregistreur:

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

Et maintenant, nous pouvons commencer la journalisation. Nous allons commencer par quelque chose de simple:

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

Les messages du journal peuvent utiliser n'importe quel spécificateur de format printf de Java , tel que % s,% d ou % 016x .

4.1. Éviter le travail sur les sites de journaux

Les créateurs de Flogger recommandent d'éviter de travailler sur le site du journal.

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

Nous suivons des étapes similaires pour configurer le back-end Log4j. Ajoutons la dépendance flogger-log4j-backend à notre 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 

Nous devons également enregistrer une usine d'arrière-plan Flogger pour Log4j:

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

Et c'est tout, notre application est désormais configurée pour utiliser les configurations Log4j existantes!

6. Conclusion

Dans ce tutoriel, nous avons vu comment utiliser le framework Flogger comme alternative aux frameworks de journalisation traditionnels. Nous avons vu quelques fonctionnalités puissantes dont nous pouvons bénéficier lors de l'utilisation du framework.

Nous avons également vu comment nous pouvons tirer parti de nos configurations existantes en enregistrant différents back-ends comme Slf4j et Log4j.

Comme d'habitude, le code source de ce tutoriel est disponible à l'adresse over sur GitHub.