- Logging at disabled levels is effectively free. Finally, you can add as many fine-grained log statements to your code as you want, without worry.
- Flogger also has very high performance for enabled log statements.
- A fluent API accommodates a variety of present and future features without combinatorial explosion, and without requiring separate logging façades.
- Less reliance on long parameter lists makes it harder to misuse and yields more self-documenting code.
When possible, log objects, not strings {#structured}
It's good to remember that a logging backend might not necessarily be outputting only plain text files. For a statement such as
logger.atInfo().log("Received message is: %s", proto)
the backend has the opportunity to do more interesting things with the data in its original, structured form.
On the other hand, with either of these calls:
logger.atInfo().log("Received message is: %s", proto.toString())
logger.atInfo().log("Received message is: " + proto);
Don't create a Throwable
just to log it {#stack-trace}
There is no need to do this:
logger.atInfo().withCause(new Exception()).log("Message");
You should do this instead:
logger.atInfo().withStackTrace(<SIZE>).log("Message");
Make the logger the first static field in a class
Fortunately we know whether or not logging is disabled at the point that the level selector was called (and this is always the first thing we do). So if logging is disabled we can chose to return a different implementation of the logging context which simply discards all its arguments for every subsequent method call (a “No-Op” instance). Conveniently this instance is naturally immutable and thread safe, so we can return the same singleton instance every time, which avoids an allocation when logging is disabled.
This means that as well as being a more functional API, using a fluent API means that in the vast majority of cases a disabled log statement can avoid needing to allocate anything.
These improvements include reducing the cost of disabled log statements, increasing overall readability, and allowing extensibility
More specifically, logging frameworks typically utilize
varargs
to accommodate the unknown number of parameters in a logging method call rather than having hundreds or even thousands of different and unpredictable method signatures. This use of varargs
results in additional bytecode, particularly to allocate an Object[]
for storing the varargs
. While additional bytecode doesn’t typically warrant concern, it becomes particularly important in applications with very fine-grained logging statements or logging statements that occur in loops.
Flogger avoids this cost through the design of its API. The fluent call chain always begins with a selector for a particular log level, for instance
atInfo()
. This selector returns an implementation to log at that level and in the case of disabled log statements, a singleton, no-op implementation, can be returned.
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
logger.atWarning().log("warning");
logger.atInfo().log("info");
logger.at(Level.SEVERE)
.atMostEvery(50, TimeUnit.SECONDS)
.log("SEVERE", LazyArgs.lazy(() -> doSomeCostly()));
logger.atWarning().withCause(e).log("warning");
logger.atInfo().every(100).log("Info logs %s", arg);
4. It also supports Extensibility
If you wanted to create separate logs per user or per session id. It is super easy to do that with the flogger
long sessionId;
logger.at(INFO).forUserId(sessionId).log("My message: %s", param);
5. Better runtime checking for disabled logging
if (logger.atFine().isEnabled()) {
Foo foo = doComplexThing();
logger.atFine().log("stats=%s", foo.toString());
}