Hi Volkan, On Mon, 5 Feb 2024 at 16:18, Volkan Yazıcı <vol...@yazi.ci> wrote: > I thought of matching the behavior between the two log event factories by > making the `DefaultLogEventFactory` format the message at creation. This > would imply both message factories will be formatting the message eagerly. > (Formatting the message at the logger thread was also suggested by Nitsan > Wakart > <https://github.com/apache/logging-log4j2/issues/2220#issuecomment-1902035073>, > the JVM performance celebrity, too.)
I would prefer for the two event factories **not** to call `Message#getFormattedMessage` at all. During the processing of a log event, filters are applied up to 4 times: 1. Logger: checks global filter and level, 2. Logger: creates message and calls LoggerConfig, 3. LoggerConfig: checks filter and level, 4. LoggerConfig: creates log event and calls AppenderControl, 5. AppenderControl: checks filter and level, 6. AppenderControl: calls appender, 7. AppenderControl: checks filter and level of the Appender, 8. Appender: appends message. If all messages are formatted in 4, we have a huge performance loss for messages that are filtered out in 5 and 7. > While examining the code base on this subject, I came across the > `log4j2.formatMsgAsync` property (flag that *"will make sure the message is > formatted in the caller thread"*) and the `@AsynchronouslyFormattable` > annotation. This gets the things even more complicated: > > ... (omissis) ... > 2. If `log4j2.formatMsgAsync=true`, nested logging does not work > anymore. That is, postponing the message formatting to `log(logEvent)` > causes recursion and hence the nested `log(logEvent)` gets skipped. > (`DefaultLogEventFactory` fails to log the nested call exactly due to the > same reason.) > 3. One would expect these configuration knobs to be effective somewhere > in my nested logging example above. But they surprisingly play no role in > this entire flow in almost all cases. That is, there is > `InternalAsyncUtil.makeMessageImmutable()` taking these configuration knobs > into account, but it is only called in async. contexts (e.g., > `AsyncAppender` or `AsyncLogger) when a message is not reusable. It makes > sense that the configuration is taken into account only when the context is > async. But it should *not* depend on if-the-message-is-reusable > condition. That is, the fact that reusable messages are *always* > formatted eagerly violates the `log4j2.formatMsgAsync=true` condition, if > provided. As Ralph said, I wouldn't care much about nested logging, especially nested logging in a `toString()` method. The contract should be: every nested log event is delivered **or** a warning is submitted to the status logger. Or even better: using logging in a `toString` method causes undefined behavior. https://en.cppreference.com/w/cpp/language/ub > There is a performance issue in the design > of `InternalAsyncUtil.makeMessageImmutable()` too. It invokes > `Message#getFormattedMessage()` to indicate to the message that it can > format the message and cache the result, if possible. Yet, > `getFormattedMessage()` has a return type of `String`, forcing the message > to allocate a string unnecessarily. I guess we could improve that: * AsyncLogger can format the message to `RingBufferLogEvent#messageText`, * AsyncLoggerConfig can do the same if `mutable == true` (i.e. the ring buffer uses mutable objects), * AsyncAppender could also use mutable objects in its queue. Piotr