-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
In the current
Log4jEventBuilder#addKeyValue(String key, Object value)
implementation, complex objects are serialized usingString.valueOf()
, which lacks flexibility. My suggestion is to allow users to provide a custom serializer that can handle more complex serialization needs
We can provide a StringBuilderFormattable
specialization there. That is, we can change addKeyValue()
implementation such that:
String encodedValue = value instance StringBuilderFormattable
? encodeUsingStringBuilderFormattable((StringBuilderFormattable) value)
: toString(value);
keyValuePairs.put(key, encodedValue);
But,
- This doesn't solve the encoding problem. Assume
JsonTemplateLayout
is employed, it will encode theMap<String, String>
passed and this will result in"kvPairs": {"yourKey": "{\"some-value\": \"you-already-JSONified\"}"}
. See? - This encourages a bad-practice: assumption on the logging layout employed. (What if the effective layout is XML?)
- There is a better alternative, which I will elaborate on below.
... handling complex objects, especially when structured logging is required.
... complex serialization needs (e.g., converting objects toJSON
)
There is so much to unpack here.
For one, logging as follows
LOGGER.info("userContext: {}", toJSON(userContext));
is a bad-practice, because this implies an assumption on the logging layout employed, which contradicts with the separation-of-concerns practiced with the logging API vs. logging implementation separation. What if the used layout is XML? etc. Hence, we should avoid encoding payloads with assumptions on the employed layout. Instead, we should allow registering arbitrarily-typed parameters, if necessary, with sufficient encoding hints (e.g., extending from MultiFormatStringBuilderFormattable
) and let them be handled by the employed layout.
Currently, addKeyValue()
admissions get registered to a Map<String, String>
– we should amend this with a Map<String, Object>
. Though the bigger problem is how KV-pairs are passed to the LogBuilder
:
try (final Instance c = CloseableThreadContext.putAll(keyValuePairs)) {
logBuilder.log(message, arguments.toArray());
}
That is, log4j-slf4j2-impl
hacks auto-clearing thread context (i.e., CloseableThreadContext
) to pass the KV-pairs all the way down to the layout. This is due to the fact that LogBuilder
of Log4j doesn't have a counterpart for arbitrary KV-pairs and the feature that comes closest to it we can leverage (and we did) is thread context. There are a couple of angles we can approach this problem from:
- Implement accepting non-
String
-typed values inCloseableThreadContext
– LOG4J2-1648 proposes this. Registering instances of custom types in a thread context can cause memory leakage in Java EE container environments, though this should not be a problem forCloseableThreadContext
. - Extend
LogBuilder
to accept custom KV-pairs
I propose that the serialization of the value should happen asynchronously, offloading the potentially expensive serialization task to a separate thread. This will prevent blocking the thread that calls logging methods (such as
log.info()
), thus improving performance in high-throughput logging environments where structured logging is used extensively.
"Improve performance in high-throughput logging environments [by offloading the log event encoding to a background thread]" this is a claim that cannot hold without given sufficient context. Given the context, most of the time it becomes a special case rather than a general one we can practice for every user. Nevertheless, if we can pass arbitrary objects in KV-pairs (and effectively allow the layout to take charge of the complete encoding effort), you can combine this with asynchronous logging to effectively encode events in a thread different from the one invoking the logging API.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status