-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Migrate Recycler API to log4j-api #1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This adds executions during process-sources and process-test-sources to ensure that modified files are formatted properly.
This will allow for reuse of this API in places where ThreadLocal-recycled objects are currently used. This will be beneficial to runtimes where workloads don't correspond to threads such as virtual threads, coroutines, and reactive streams. This adds an optional dependency on JCTools to log4j-api for one of the QueueingRecycler implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if o.a.l.l.util
is the right package for this: RecyclerFactory
is something a provider might like to implement. Maybe o.a.l.l.spi
is more appropriate?
I would also remove the implementations from the exported packages, leaving just RecyclerFactory
and the RecyclerFactories
meta-factory.
Any ideas on how an implementer would add new factories? ServiceLoader
?
Could be pluggable via dependency injection. That's how I was initially using it, so I suppose that could mean moving the concrete implementations to core. Having access to DI from the API module is related to a new API added in the larger PR that this was separated from. |
While I was working on LOG4J2-3228, I noticed what you were referring to in the reusable message classes. Yes, I'd like to make the |
Signed-off-by: Matt Sicker <[email protected]>
Also updates ThreadLocalRecyclerFactory to support nested calls to acquire() by using an ArrayDeque to track potentially more than one object. Signed-off-by: Matt Sicker <[email protected]>
So I've moved it to the SPI package. As it stands now, there's already a mini configuration spec string you can specify for recyclers based on the existing JTL configuration parsing logic documented here and here for how to specifically make it work for JTL. I expect that to be generally sufficient when using the API by itself without the backend. In the backend, we have the |
Looks like some test failures. Still a work in progress I suppose. |
Signed-off-by: Matt Sicker <[email protected]>
- Split cleaner concerns in RecyclerFactory: eager versus lazy cleaning. This makes the Recycler API work naturally with existing StringBuilder post-usage trimming. - Uses a simpler method for checking for the presence of JCTools. Given that it's an optional (static) dependency, we can compile direct references to it in classes and see if our classes throw LinkageError-related exceptions at runtime when attempting to link to JCTools. - Rename QueueSupplier to QueueFactory - Extract a Queues util for easier access to different JCTools queue implementations when available - Update implementations of AbstractStringLayout to use Recycler API - CsvLogEventLayout - CsvParameterLayout - GelfLayout - HtmlLayout - Log4j1SyslogLayout - Log4j1XmlLayout - PatternLayout - Rfc5424Layout - SyslogLayout Signed-off-by: Matt Sicker <[email protected]>
Alright, I think I've figured out one key update to I've also gone ahead and abstracted out some |
@vy care to have a look at my changes to the |
final StringBuilder messageBuffer = getMessageStringBuilder(); | ||
layout.serialize(event, messageBuffer); | ||
JsonUtils.quoteAsString(messageBuffer, builder); | ||
final StringBuilder messageBuffer = acquireStringBuilder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting little use case where you can now abuse the recursion support in ThreadLocalRecyclerFactory
to get multiple recyclable objects. Already works as expected with the QueueingRecyclerFactory
version.
return builder; | ||
} | ||
|
||
private static final ThreadLocal<StringBuilder> timestampStringBuilder = new ThreadLocal<>(); | ||
|
||
private static StringBuilder getTimestampStringBuilder() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also got a kick out of how pointless this buffer is when using the buffer provided by AbstractStringLayout
already. The joys of new APIs!
Signed-off-by: Matt Sicker <[email protected]>
There is one In the case of
|
@@ -89,9 +83,21 @@ public static void release(final Message message) { // LOG4J2-1583 | |||
} | |||
} | |||
|
|||
@Override | |||
public void recycle(final Message message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this new method compare to release(Message)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of merely clearing the message, this also returns the object back to its pool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and I wasn't able to name this method release
because it conflicts with the static method with the same signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
release
javadoc states the following
This flag is used internally to verify that a reusable message is no longer in use and can be reused.
This sounds to me exactly like what recycle does. I am curious why do we need separate recycle()
and release()
methods. Can't we simply move the recycle()
logic to the bottom of the release()
?
log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java
Outdated
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
Outdated
Show resolved
Hide resolved
log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java
Outdated
Show resolved
Hide resolved
log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java
Show resolved
Hide resolved
log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java
Outdated
Show resolved
Hide resolved
private void onPropertiesPresent(final RingBufferLogEvent event, final List<Property> properties) { | ||
final StringMap contextData = getContextData(event); | ||
for (int i = 0, size = properties.size(); i < size; i++) { | ||
final Property prop = properties.get(i); | ||
// List::forEach is garbage-free when using an ArrayList or Arrays.asList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it? You need to pass a lambda, which needs to be allocated. I think this needs to be reverted along with the switch from List.of()
to Arrays.asList()
change in LoggerConfig
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I discovered the temporary allocation due to our existing GC tests. Lambdas compile down to some sort of synthetic static method on the class it was declared in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not able to follow. Are you saying that our GC-tests are returning false negatives and in reality JIT optimizes this piece of code to have no allocations?
log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
Outdated
Show resolved
Hide resolved
log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
Outdated
Show resolved
Hide resolved
…actories.java Co-authored-by: Volkan Yazıcı <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Signed-off-by: Matt Sicker <[email protected]>
Ignore test failures as those are broken in master already. |
This simplifies DefaultLogBuilder as reuse is now handled by the recycler. This also adds a place to store a default RecyclerFactory in LoggingSystem. Signed-off-by: Matt Sicker <[email protected]>
Updated to support this. The recycler doesn't even need to reset the LogBuilder as that can be done in a central place already. |
final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, | ||
final LogEvent logEvent = logEventFactory.createEvent(rootLogger.getName(), null, Strings.EMPTY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the need for extracting a local variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getter wasn't for a field; it was doing work each time. I realized upon fixing this that Configuration
must be non-null at this point, so we can just get it from Configuration::getLogEventFactory
(recently added method).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreeing with @jvz here. If a method is not merely a simple field getter (which is a shame that it is still called getBlah
), its result should first better be stored in a self-explanatory variable. This greatly helps while stepping in a debugger.
@ppkarwasz, does the logger already pool returned |
Ignore this please. @jvz says he has already implemented it. |
protected final Recycler<LogBuilder> recycler = LoggingSystem.getRecyclerFactory() | ||
.create(() -> new DefaultLogBuilder(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a future extension we should probably allow any LogBuilder
...
final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); | ||
return builder.atLevel(Level.OFF); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...so that we don't need this unchecked cast.
I can open a new issue for this, since this PR is getting huge.
// recycle self | ||
this.level = null; | ||
this.marker = null; | ||
this.throwable = null; | ||
this.location = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fail to understand how can the recycler knows that the LogBuilder
can be used again?
This will allow for reuse of this API in places where ThreadLocal-recycled objects are currently used. This will be beneficial to runtimes where workloads don't correspond to threads such as virtual threads, coroutines, and reactive streams. This adds an optional dependency on JCTools to log4j-api for one of the QueueingRecycler implementations.
Just one of many PRs to break out from #1144.