Rewrite `logbuilder.adoc` (#2595)
Co-authored-by: Volkan Yazıcı <volkan@yazi.ci>
diff --git a/src/site/antora/modules/ROOT/images/LocationPerf.png b/src/site/antora/modules/ROOT/images/LocationPerf.png
deleted file mode 100644
index 1a232ef..0000000
--- a/src/site/antora/modules/ROOT/images/LocationPerf.png
+++ /dev/null
Binary files differ
diff --git a/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc b/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc
index f6b93f1..7fe507e 100644
--- a/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc
@@ -15,64 +15,125 @@
limitations under the License.
////
-= Log Builder
+////
+ I think it is unfortunate that Log4j calls its *fluent API* "Log Builder".
+ As explained in this[1] "Fluent API vs. Builder Pattern" post, the difference lies in semantics.
+ That is, if `LogBuilder` had been designed as follows:
-Log4j has traditionally been used with logging statements like
-[source,java]
+ LOGGER
+ .newBuilder()
+ .setLevel(...)
+ .setMarker(...)
+ .setMessage(...)
+ // ...
+
+ It would be perfectly valid to call it "Log Builder".
+ But instead, we provide a semantic model, a domain-specific language to log by chaining methods:
+
+ LOGGER
+ .atInfo()
+ .withMarker(...)
+ .withLocation()
+ .log(...)
+
+ Hence, my insistence on calling it "Fluent API".
+ Not to mention, other Java logging software call it "Fluent API" too[2][3].
+
+ [1] https://stackoverflow.com/a/17940086/1278899
+ [2] https://www.slf4j.org/manual.html#fluent
+ [3] https://tinylog.org/v2/news/#tags-for-log-entries
+////
+
+= Fluent API
+
+Next to the traditional `info()`, `error()`, etc. `Logger` methods, Log4j API also provides a https://en.wikipedia.org/wiki/Fluent_interface[fluent interface] for logging.
+
+[#rationale]
+== Rationale
+
+Developers use Log4j traditionally with logging statements like:
+
+[source, java]
----
-logger.error("Unable to process request due to {}", code, exception);
+LOGGER.error("Unable to process request due to {}", errorCode, exception);
----
-This has resulted in some confusion as to whether the exception should be a parameter to the message or
-if Log4j should handle it as a throwable. In order to make logging clearer a builder pattern has been
-added to the API. Using the builder syntax the above would be handled as:
-[source,java]
+This style has certain drawbacks:
+
+* It is confusing whether the last argument, `exception`, is a parameter of the message to be formatted, or is separately attached to the log event.
+* One must know in which order `error()` arguments should be passed to specify, say, a xref:manual/markers.adoc[marker].
+
+The fluent interface (also referred to as _the fluent API_) has been added to Log4j API to increase code legibility and avoid ambiguities.
+For instance, the above `error()` call can be expressed using the fluent API as follows:
+
+[source, java]
----
-logger.atError().withThrowable(exception).log("Unable to process request due to {}", code);
+LOGGER
+ .atError() // <1>
+ .withThrowable(exception) // <2>
+ .log("Unable to process request due to {}", errorCode); // <3>
----
+<1> The log level is set to `ERROR`
+<2> The associated exception is attached
+<3> The log message is formatted with the `errorCode` parameter
-With this syntax it is clear that the exception is to be treated as a Throwable by Log4j.
+With this syntax, it is clear that the `exception` is part of the log event and `errorCode` is a parameter of the message.
-The Logger class now returns a LogBuilder when any of the atTrace, atDebug, atInfo, atWarn, atError,
-atFatal, always, or atLevel(Level) methods are called. The logBuilder then allows a Marker, Throwable,
-and/or location to be added to the event before it is logged. A call to the log method always causes the
-log event to be finalized and sent.
+[#usage]
+== Usage
-A logging statement with a Marker, Throwable, and location would look like:
-[source,java]
+The fluent API entry point is link:../log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`], which can be obtained by using one of the following `Logger` methods:
+
+- `atTrace()`
+- `atDebug()`
+- `atInfo()`
+- `atWarn()`
+- `atError()`
+- `atFatal()`
+- `always()`
+- `atLevel(Level)`
+
+`LogBuilder` allows attaching a xref:manual/markers.adoc[marker], a `Throwable`, and a location to the log event by means of following methods:
+
+- `withMarker()`
+- `withThrowable()`
+- `withLocation()`
+
+After that, developers can call the `log()` method to finalize and send the log event.
+
+In the following example, we log a parameterized message at `INFO` level, and attach a marker and an exception to the log event:
+
+[source, java]
----
-logger.atInfo().withMarker(marker).withLocation().withThrowable(exception).log("Login for user {} failed", userId);
+LOGGER
+ .atInfo() // <1>
+ .withMarker(marker) // <2>
+ .withThrowable(exception) // <3>
+ .log("Unable to process request due to {}", errorCode); // <4>
----
-Providing the location method on the LogBuilder provides two distinct advantages:
+<1> The log level is set to `INFO`
+<2> `marker` is attached to the log event
+<3> `exception` is attached to the log event
+<4> A message with `errorCode` parameter is provided and the statement is finalized
-1. Logging wrappers can use it to provide the location information to be used by Log4j.</li>
-2. The overhead of capturing location information when using the location method with no
-parameters is much better than having to calculate the location information when it is needed. Log4j
-can simply ask for the stack trace entry at a fixed index instead of having to walk the stack trace
-to determine the calling class. Of course, if the location information will not be used by the layout
-this will result in slower performance.</li>
+[#location-information]
+== Location information
-== Location Performance
+The fluent API allows users to instruct the location information to be *eagerly* populated in the log event using the `withLocation()` method:
-The table below shows some of the results from the FileAppenderBenchmark and FileAppenderWithLocationBenchmark
-classes in the log4j-perf-test project when configured to use 4 threads. The results show that lazily including
-the location information is about 8 times slower than not including location information. While using the
-withLocation method of LogBuilder is about 3 times faster than lazily calculating the location information
-it is still about 2.5 times slower than not including location information.
+[source, java]
+----
+LOGGER
+ .atInfo()
+ .withLocation() // <1>
+ .log("Login for user with ID `{}` failed", userId);
+----
+<1> Instructing to eagerly populate the location information
-The tests were run on a 2018 MacBook Pro with a 2.9 GHz Intel Core i9 processor with 6 cores, 32 GB of memory
-and 1 TB of SSD storage on Java 11 using Log4j 2.13.0 and Logback 1.2.3.
-image:LocationPerf.png[Location Performance]
+Capturing location information using `withLocation()` is orders of magnitude more efficient compared to letting the `Logger` to figure it out indirectly.
-|===
-|Test|Print Location Info|No Location Info Printed
-
-|Log4j2 File| 191,509.724 ± 11339.978 ops/s| 1,407,329.130 ± 22595.997 ops/s
-|Log4j2 Log Builder withLocation()|469,200.684 ± 50025.985 ops/s|577,127.463 ± 11464.342 ops/s
-|Logback File|159,116.538 ± 1884.969 ops/s|1,240,438.384 ± 76619.873 ops/s
-|===
-As expected, when using LogBuilder with a call to the withLocation() method logging is much faster when
-location information is used in the output but significantly slower when it is not.
-
-Note: Running the tests at various times provides varying results. Although some results have been as much
-as 10% higher all results are generally affected similarly so the comparisons between them stay the same.
\ No newline at end of file
+[WARNING]
+====
+You are strongly advised to use `withLocation()` if you are certain that the populated location information will be used.
+Otherwise – that is, if the log event might either get dropped due to some filtering or its location information not get used – it will only slow things down.
+====