Pembalakan Flogger Fluent

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan membincangkan kerangka Flogger, API logging yang lancar untuk Java yang direka oleh Google.

2. Mengapa Menggunakan Flogger?

Dengan semua kerangka kerja pembalakan yang kini ada di pasaran, seperti Log4j dan Logback, mengapa kita memerlukan kerangka pembalakan yang lain?

Ternyata Flogger mempunyai beberapa kelebihan berbanding kerangka kerja lain - mari kita lihat.

2.1. Kebolehbacaan

Sifat lancar Flogger's API banyak membantu menjadikannya lebih mudah dibaca.

Mari kita lihat contoh di mana kita mahu mencatat mesej setiap sepuluh lelaran.

Dengan kerangka pembalakan tradisional, kami akan melihat seperti:

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

Tetapi sekarang, dengan Flogger, perkara di atas dapat dipermudahkan untuk:

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

Walaupun seseorang berpendapat bahawa versi Flogger dari penyata logger kelihatan sedikit lebih verbose daripada versi tradisional, ia membenarkan fungsi yang lebih besar dan akhirnya membawa kepada pernyataan log yang lebih mudah dibaca dan ekspresif .

2.2. Persembahan

Objek pembalakan dioptimumkan selagi kita mengelakkan panggilan keString pada objek yang dicatat:

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

Sekiranya kita log, seperti yang ditunjukkan di atas, backend mempunyai peluang untuk mengoptimumkan pembalakan. Sebaliknya, jika kita memanggil ke String secara langsung, atau menggabungkan rentetan, maka peluang ini akan hilang:

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

2.3. Kebolehpanjangan

Kerangka Flogger sudah merangkumi sebahagian besar fungsi asas yang kami harapkan dari rangka kerja pembalakan.

Walau bagaimanapun, ada kes di mana kita perlu menambah fungsi. Dalam kes ini, mungkin untuk memperluas API.

Pada masa ini, ini memerlukan kelas sokongan yang berasingan. Sebagai contoh, kami dapat memperluas Flogger API dengan menulis kelas UserLogger :

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

Ini boleh berguna sekiranya kita ingin memformat mesej secara konsisten. The UserLogger kemudian akan menyediakan pelaksanaan untuk kaedah adat forUserId (String id) dan withUsername (username String).

Untuk melakukan ini, yang UserLogger kelas akan mempunyai untuk melanjutkan AbstractLogger kelas dan menyediakan pelaksanaan bagi API . Sekiranya kita melihat FluentLogger , itu hanya logger tanpa kaedah tambahan, oleh itu kita boleh memulakan dengan menyalin kelas ini sebagaimana adanya , dan kemudian membina dari asas ini dengan menambahkan kaedah padanya.

2.4. Kecekapan

Kerangka tradisional menggunakan varargs secara meluas. Kaedah-kaedah ini memerlukan Objek baru [] dialokasikan dan diisi sebelum kaedah tersebut dapat digunakan. Selain itu, sebarang jenis asas yang mesti dilampirkan mestilah kotak automatik.

Ini semua memerlukan kod bytec dan latensi tambahan di laman web panggilan. Amat malang jika penyataan log sebenarnya tidak diaktifkan. Kos menjadi lebih jelas pada log tahap debug yang sering muncul dalam gelung. Flogger menanggung kos ini dengan mengelakkan varargs sepenuhnya.

Flogger menyelesaikan masalah ini dengan menggunakan rangkaian panggilan yang lancar dari mana penyataan log dapat dibina. Ini membolehkan rangka kerja hanya mempunyai sebilangan kecil penggantian kaedah log , dan dengan itu dapat menghindari perkara seperti varargs dan auto-boxing. Ini bermaksud bahawa API dapat menampung pelbagai ciri baru tanpa ledakan gabungan.

Kerangka pembalakan biasa mempunyai kaedah berikut:

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

di mana level boleh menjadi salah satu daripada kira-kira tujuh nama tahap log ( parah misalnya), serta mempunyai kaedah log kanonik yang menerima tahap log tambahan:

log(Level, Object...)

Di samping itu, biasanya ada varian kaedah yang menyebabkan ( contoh Throwable ) yang dikaitkan dengan pernyataan log:

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

Sudah jelas bahawa API menggabungkan tiga masalah menjadi satu kaedah panggilan:

  1. Ia cuba menentukan tahap log (pilihan kaedah)
  2. Mencuba melampirkan metadata ke pernyataan log (Sebab dilemparkan )
  3. Dan juga, menentukan mesej log dan argumen.

Pendekatan ini dengan cepat melipatgandakan bilangan kaedah pembalakan yang diperlukan untuk memenuhi masalah bebas ini.

Kita sekarang dapat melihat mengapa penting untuk mempunyai dua kaedah dalam rantaian:

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

Sekarang mari kita lihat bagaimana kita boleh menggunakannya di pangkalan data kita.

3. Kebergantungan

Cukup mudah untuk menubuhkan Flogger. Kami hanya perlu menambahkan backend flogger dan sistem flogger ke pom kami :

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

Dengan adanya pergantungan ini, kita sekarang dapat terus meneroka API yang ada pada kita.

4. Meneroka Fluent API

Pertama, mari nyatakan contoh statik untuk pembalak kami:

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

Dan sekarang kita boleh mula membuat pembalakan. Kami akan mulakan dengan sesuatu yang mudah:

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

Mesej log boleh menggunakan mana-mana penentu format printf Java , seperti % s,% d atau % 016x .

4.1. Mengelakkan Kerja di Laman Log

Pencipta Flogger mengesyorkan agar kita tidak melakukan kerja di laman 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

Kami mengikuti langkah serupa untuk mengkonfigurasi bahagian belakang Log4j. Mari tambahkan pergantungan flogger-log4j-backend ke pom kami :

 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 

Kami juga perlu mendaftarkan kilang belakang Flogger untuk Log4j:

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

Dan hanya itu, aplikasi kami kini disiapkan untuk menggunakan konfigurasi Log4j yang ada!

6. Kesimpulannya

Dalam tutorial ini, kita telah melihat bagaimana menggunakan kerangka Flogger sebagai alternatif untuk kerangka pembalakan tradisional. Kami telah melihat beberapa ciri hebat yang dapat kami manfaatkan semasa menggunakan rangka kerja.

Kami juga telah melihat bagaimana kami dapat memanfaatkan konfigurasi yang ada dengan mendaftarkan bahagian belakang yang berbeza seperti Slf4j dan Log4j.

Seperti biasa, kod sumber untuk tutorial ini boleh didapati di GitHub.