Flogger Fluent Logging

1. Общ преглед

В този урок ще говорим за рамката на Flogger, API за плавно регистриране за Java, проектиран от Google.

2. Защо да използвам Flogger?

С всички регистриращи рамки, които в момента са на пазара, като Log4j и Logback, защо се нуждаем от още една рамка за регистриране?

Оказва се, че Flogger има няколко предимства пред други рамки - нека да разгледаме.

2.1. Четливост

Плавният характер на API на Flogger изминава дълъг път, за да го направи по-четлив.

Нека разгледаме пример, в който искаме да регистрираме съобщение на всеки десет итерации.

С традиционната рамка за регистриране ще видим нещо като:

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

Но сега, с Flogger, горното може да бъде опростено до:

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

Макар че би се твърдяло, че версията на оператора за регистрация на Flogger изглежда малко по-подробна от традиционните версии, тя позволява по-голяма функционалност и в крайна сметка води до по-четливи и изразителни протоколи .

2.2. производителност

Регистрираните обекти се оптимизират, докато избягваме извикването на toString на регистрираните обекти:

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

Ако регистрираме, както е показано по-горе, бекендът има възможност да оптимизира регистрирането. От друга страна, ако извикаме директно toString или обединим низовете, тази възможност се губи:

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

2.3. Разширяемост

Рамката на Flogger вече покрива повечето от основните функционалности, които бихме очаквали от рамка за регистриране.

Има обаче случаи, в които ще трябва да добавим към функционалността. В тези случаи е възможно да се разшири API.

Понастоящем това изисква отделен поддържащ клас. Можем например да разширим API на Flogger, като напишем клас UserLogger :

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

Това може да бъде полезно в случаите, когато искаме да форматираме съобщението последователно. След това UserLogger ще осигури изпълнението на персонализираните методи заUserId (String id) и withUsername (String username).

За да направите това, на UserLogger класа ще трябва да се разшири AbstractLogger класа и да осигури изпълнението на API . Ако разгледаме FluentLogger , това е просто регистратор без допълнителни методи, следователно можем да започнем с копиране на този клас такъв, какъвто е, и след това да изградим от тази основа, като добавим методи към него.

2.4. Ефективност

Традиционните рамки широко използват varargs. Тези методи изискват нов обект [] да бъде разпределен и попълнен, преди методът да може да бъде извикан. Освен това всички подадени основни типове трябва да бъдат автоматично поставени в кутия.

Всичко това струва допълнителен байт код и латентност на сайта за повикване. Особено жалко е, че операторът на дневника всъщност не е активиран. Цената става по-очевидна при регистрационни файлове на ниво отстраняване на грешки, които често се появяват в цикли. Flogger зарязва тези разходи, като избягва напълно вараргите.

Flogger заобикаля този проблем, като използва плавна верига за обаждания, от която могат да бъдат изградени оператори за регистриране. Това позволява на рамката да има само малък брой замествания на метода на дневника и по този начин да може да избягва неща като varargs и автоматично боксиране. Това означава, че API може да побере разнообразие от нови функции без комбинаторна експлозия.

Типична рамка за регистриране би имала следните методи:

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

където нивото може да бъде едно от около седем имена на ниво дневник ( тежко например), както и да има каноничен метод на дневник, който приема допълнително ниво на дневник:

log(Level, Object...)

В допълнение към това, обикновено има варианти на методите, които приемат причина ( екземпляр на Throwable ), който е свързан с операторския дневник:

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

Ясно е, че API свързва три опасения в едно извикване на метод:

  1. Опитва се да посочи нивото на регистрационния файл (избор на метод)
  2. Опит за прикачване на метаданни към оператора на регистрационния файл ( кауза за качване )
  3. И също така, посочване на дневника и аргументите.

Този подход бързо умножава броя на различните методи за регистриране, необходими за задоволяване на тези независими проблеми.

Сега можем да разберем защо е важно да имаме два метода във веригата:

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

Нека сега разгледаме как можем да го използваме в нашата кодова база.

3. Зависимости

Доста е просто да настроите Flogger. Просто трябва да добавим flogger и flogger-system-backend към нашия pom:

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

С тези настройки, вече можем да продължим да изследваме API, който е на наше разположение.

4. Проучване на Fluent API

Първо, нека декларираме статичен екземпляр за нашия регистратор:

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

И сега можем да започнем да регистрираме. Ще започнем с нещо просто:

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

Регистрационните съобщения могат да използват всеки от спецификаторите на формат printf на Java , като % s,% d или % 016x .

4.1. Избягване на работа на сайтове за регистрация

Създателите на Flogger препоръчват да избягваме извършването на работа на сайта на регистрационния файл.

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

Следваме подобни стъпки за конфигуриране на Log4j back-end. Нека добавим зависимостта flogger-log4j-backend към нашия 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 

Също така трябва да регистрираме фабрична фабрика на Flogger за Log4j:

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

И това е, нашето приложение вече е настроено да използва съществуващите конфигурации Log4j!

6. Заключение

В този урок видяхме как да използваме рамката на Flogger като алтернатива на традиционните рамки за регистриране. Видяхме някои мощни функции, от които можем да се възползваме, когато използваме рамката.

Също така видяхме как можем да използваме съществуващите си конфигурации, като регистрираме различни бек-ендове като Slf4j и Log4j.

Както обикновено, изходният код за този урок е достъпен в GitHub.