Flogger Fasih Logging

1. Ikhtisar

Dalam tutorial ini, kita akan berbicara tentang kerangka kerja Flogger, API pencatatan yang lancar untuk Java yang dirancang oleh Google.

2. Mengapa Menggunakan Flogger?

Dengan semua kerangka kerja logging yang saat ini ada di pasaran, seperti Log4j dan Logback, mengapa kita membutuhkan framework logging lain?

Ternyata Flogger memiliki beberapa keunggulan dibandingkan framework lain - mari kita lihat.

2.1. Keterbacaan

Sifat Flogger's API yang lancar sangat membantu membuatnya lebih mudah dibaca.

Mari kita lihat contoh di mana kita ingin mencatat pesan setiap sepuluh iterasi.

Dengan kerangka kerja logging tradisional, kami akan melihat sesuatu seperti:

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

Tapi sekarang, dengan Flogger, hal di atas bisa disederhanakan menjadi:

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

Sementara orang akan berpendapat bahwa versi Flogger dari pernyataan logger terlihat sedikit lebih bertele-tele daripada versi tradisional, itu memungkinkan fungsionalitas yang lebih besar dan pada akhirnya mengarah ke pernyataan log yang lebih mudah dibaca dan ekspresif .

2.2. Performa

Objek pencatatan dioptimalkan selama kita menghindari panggilan toString pada objek yang dicatat:

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

Jika kita log, seperti yang ditunjukkan di atas, backend memiliki kesempatan untuk mengoptimalkan logging. Di sisi lain, jika kita memanggil toString secara langsung, atau menggabungkan string, maka peluang ini hilang:

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

2.3. Kemungkinan diperpanjang

Framework Flogger sudah mencakup sebagian besar fungsionalitas dasar yang kami harapkan dari framework logging.

Namun, ada kasus di mana kami perlu menambahkan fungsionalitas tersebut. Dalam kasus ini, API dapat diperluas.

Saat ini, ini membutuhkan kelas pendukung terpisah. Misalnya, kita dapat memperluas Flogger API dengan menulis kelas UserLogger :

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

Ini dapat berguna jika kami ingin memformat pesan secara konsisten. The UserLogger kemudian akan memberikan implementasi untuk metode kustom forUserId (String id) dan withUsername (String nama).

Untuk melakukan hal ini, para UserLogger kelas harus memperpanjang AbstractLogger kelas dan memberikan implementasi untuk API . Jika kita melihat FluentLogger , itu hanya logger tanpa metode tambahan, oleh karena itu, kita dapat mulai dengan menyalin kelas ini sebagaimana adanya, dan kemudian membangun dari fondasi ini dengan menambahkan metode ke dalamnya.

2.4. Efisiensi

Kerangka tradisional banyak menggunakan vararg. Metode ini membutuhkan Object [] baru untuk dialokasikan dan diisi sebelum metode dapat dipanggil. Selain itu, semua tipe fundamental yang diteruskan harus di-auto-box.

Ini semua membutuhkan tambahan bytecode dan latensi di situs panggilan. Sangat disayangkan jika pernyataan log sebenarnya tidak diaktifkan. Biaya menjadi lebih jelas dalam log level debug yang sering muncul dalam loop. Flogger membuang biaya ini dengan menghindari vararg sepenuhnya.

Flogger mengatasi masalah ini dengan menggunakan rantai panggilan yang lancar tempat pernyataan logging dapat dibuat. Hal ini memungkinkan framework untuk hanya memiliki sejumlah kecil penggantian ke metode log , dan dengan demikian dapat menghindari hal-hal seperti vararg dan auto-boxing. Artinya, API dapat menampung berbagai fitur baru tanpa ledakan kombinatorial.

Kerangka kerja logging biasanya memiliki metode ini:

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

di mana level bisa menjadi salah satu dari sekitar tujuh nama level log ( parah misalnya), serta memiliki metode log kanonis yang menerima level log tambahan:

log(Level, Object...)

Selain itu, biasanya ada varian metode yang menyebabkan ( instance Throwable ) yang terkait dengan pernyataan log:

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

Jelas bahwa API menggabungkan tiga masalah menjadi satu panggilan metode:

  1. Ini mencoba untuk menentukan tingkat log (pilihan metode)
  2. Mencoba melampirkan metadata ke pernyataan log ( Penyebab yang dapat dilempar )
  3. Dan juga, menentukan pesan log dan argumen.

Pendekatan ini dengan cepat menggandakan jumlah metode penebangan berbeda yang diperlukan untuk memenuhi kepentingan independen ini.

Sekarang kita dapat melihat mengapa penting untuk memiliki dua metode dalam rantai:

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

Sekarang mari kita lihat bagaimana kita dapat menggunakannya di basis kode kita.

3. Ketergantungan

Cukup mudah untuk menyiapkan Flogger. Kami hanya perlu menambahkan flogger dan flogger-system-backend ke pom kami :

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

Dengan pengaturan dependensi ini, sekarang kita dapat melanjutkan untuk menjelajahi API yang tersedia.

4. Menjelajahi Fluent API

Pertama, mari kita deklarasikan instance statis untuk logger kita:

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

Dan sekarang kita bisa mulai masuk. Kami akan mulai dengan sesuatu yang sederhana:

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

Pesan log dapat menggunakan salah satu penentu format printf Java , seperti % s,% d, atau % 016x .

4.1. Menghindari Pekerjaan di Situs Log

Pembuat Flogger menyarankan agar kami menghindari melakukan pekerjaan di situs 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 back-end Log4j. Mari tambahkan dependensi flogger-log4j-backend ke pom kita :

 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 pabrik back-end Flogger untuk Log4j:

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

Dan hanya itu, aplikasi kita sekarang diatur untuk menggunakan konfigurasi Log4j yang ada!

6. Kesimpulan

Dalam tutorial ini, kita telah melihat bagaimana menggunakan framework Flogger sebagai alternatif untuk framework logging tradisional. Kami telah melihat beberapa fitur canggih yang dapat kami manfaatkan saat menggunakan kerangka kerja.

Kami juga telah melihat bagaimana kami dapat memanfaatkan konfigurasi yang ada dengan mendaftarkan back-end yang berbeda seperti Slf4j dan Log4j.

Seperti biasa, kode sumber untuk tutorial ini tersedia di GitHub.