[
https://issues.apache.org/jira/browse/LOG4J2-1274?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15148483#comment-15148483
]
Remko Popma edited comment on LOG4J2-1274 at 2/16/16 11:35 AM:
---------------------------------------------------------------
Actually, given that I want to do something very similar for LOG4J2-506
(logging binary messages), it makes more sense to create a separate interface
for the new method, and let {{Layout}} extend that interface. I was thinking to
call this new interface {{Encoder}}:
{code}
public interface Layout<T extends Serializable> extends Encoder<LogEvent> {
// current methods
}
public interface Encoder<T> {
void encode(T source, ByteBufferDestination destination);
}
public interface ByteBufferDestination {
ByteBuffer getByteBuffer();
/**
* Consumes the buffer content and returns a buffer with more available()
space
* (which may or may not be the same instance).
* <p>
* Called by the producer when buffer becomes too full to write to.
*/
ByteBuffer drain(ByteBuffer buf);
}
{code}
The {{encode}} method would be implemented in AbstractLayout by calling the
existing toByteArray logic and writing the result into the ByteBuffer, so all
layouts that extend from AbstractLayout work as before.
Thoughts?
was (Author: [email protected]):
Actually, given that I want to do something very similar for LOG4J2-506
(logging binary messages), it makes more sense to create a separate interface
for the new method, and let {{Layout}} extend that interface. I was thinking to
call this new interface {{Encoder}}:
{code}
public interface Layout<T extends Serializable> extends Encoder<LogEvent> {
// current methods
}
public interface Encoder<T> {
void encode(T source, BinaryDestination destination);
}
public interface ByteBufferDestination {
ByteBuffer getByteBuffer();
/**
* Consumes the buffer content and returns a buffer with more available()
space
* (which may or may not be the same instance).
* <p>
* Called by the producer when buffer becomes too full to write to.
*/
ByteBuffer drain(ByteBuffer buf);
}
{code}
The {{encode}} method would be implemented in AbstractLayout by calling the
existing toByteArray logic and writing the result into the ByteBuffer, so all
layouts that extend from AbstractLayout work as before.
Thoughts?
> Layout improvements to enable avoiding temporary object allocation
> ------------------------------------------------------------------
>
> Key: LOG4J2-1274
> URL: https://issues.apache.org/jira/browse/LOG4J2-1274
> Project: Log4j 2
> Issue Type: Improvement
> Components: Layouts
> Affects Versions: 2.5
> Reporter: Remko Popma
>
> *Problem*
> The current Layout API does not make it easy for implementors to avoid
> creating temporary objects. Especially these methods:
> {code}
> byte[] toByteArray(LogEvent);
> T toSerializable(LogEvent);
> {code}
> The byte array returned from {{toByteArray(LogEvent)}} cannot be re-used
> between log events, since the caller cannot know how many bytes a partially
> filled array contains.
> In practice, all Layout implementations in Log4j 2 except SerializedLayout
> implement the {{StringLayout}} subinterface. This means that the
> {{toSerializable()}} method needs to return a new String object for every log
> event.
> *Forces*
> I am interested in reducing or even eliminating the allocation of temporary
> objects for text-based layouts. Many of these use (and re-use) a
> StringBuilder to build a text representation of the current log event. Once
> this text representation is built, it needs to be converted to bytes that the
> Appender can consume. I am aware of two ways in the JDK to convert text to
> bytes:
> * the various {{String#getBytes}} methods - these all allocate a new byte
> array for each invocation
> * the underlying {{java.nio.charset.CharsetEncoder}} used internally by
> String - especially method {{CoderResult encode(CharBuffer in, ByteBuffer
> out, boolean endOfInput)}} which converts characters to bytes without object
> allocation.
> The last method is interesting because this gives us an opportunity to also
> reduce the amount of copying by directly supplying the ByteBuffer buffer used
> by RandomAccessFileAppender, or the MappedByteBuffer of the
> MemoryMappedFileAppender.
> The resulting API needs to support the fact that implementations may need to
> call {{CharsetEncoder#encode}} multiple times:
> * The ByteBuffer may not have enough remaining space to hold all the data;
> {{CharsetEncoder#encode}} returns {{CoderResult.OVERFLOW}} to signal this so
> the caller can consume the contents and reset/clear the buffer before
> continuing.
> * The CharBuffer may not be large enough to hold the full text representation
> of the log event. Again, {{CharsetEncoder#encode}} may need to be invoked
> multiple times.
> *Proposal*
> (Thinking out loud here, I'm open to suggestions.)
> It may be sufficient for the layout interface to have a single additional new
> method:
> {code}
> /**
> * Formats the event suitable for display and writes the result to the
> specified destination.
> *
> * @param event The Logging Event.
> * @param destination Holds the ByteBuffer to write into.
> */
> void writeTo(LogEvent e, ByteBufferDestination destination);
> {code}
> Appenders that want to be allocation-free need to implement the
> {{ByteBufferDestination}} interface:
> {code}
> public interface ByteBufferDestination {
> ByteBuffer getByteBuffer();
> /**
> * Consumes the buffer content and returns a buffer with more available()
> space
> * (which may or may not be the same instance).
> * <p>
> * Called by the producer when the buffer becomes too full
> * to write more data into it.
> */
> ByteBuffer drain(ByteBuffer buf);
> }
> {code}
> Usage: for example RandomAccessFileAppender code can look like this:
> {code}
> // RandomAccessFileAppender
> public void append(final LogEvent event) {
> getLayout().writeTo(event, (ByteBufferDestination) manager);
> }
> {code}
> Layout implementation of the {{writeTo}} method: layouts need to know how to
> convert LogEvents to text, but writing this text into the ByteBuffer can be
> delegated to a helper:
> {code}
> // some layout
> public void writeTo(LogEvent event, ByteBufferDestination destination) {
> StringBuilder text = toText(event, getCachedStringBuilder());
>
> TextEncoderHelper helper = getCachedHelper();
> helper.encodeWithoutAllocation(text, destination);
> }
> /**
> * Creates a text representation of the specified log event
> * and writes it into the specified StringBuilder.
> * <p>
> * Implementations are free to return a new StringBuilder if they can
> * detect in advance that the specified StringBuilder is too small.
> */
> StringBuilder toText(LogEvent e, StringBuilder destination) {} // existing
> logic goes here
> public String toSerializable(LogEvent event) { // factored out logic to
> toText()
> return toText(event, getCachedStringBuilder()).toString();
> }
> {code}
> Helper contains utility code for moving the text into a CharBuffer, and for
> repeatedly calling {{CharsetEncoder#encode}}.
> {code}
> public class TextEncoderHelper {
> TextEncoderHelper(Charset charset) {} // create CharsetEncoder
> void encodeWithoutAllocation(StringBuilder text, BinaryDestination
> destination) {
> ByteBuffer byteBuf = destination.getByteBuffer();
> CharBuffer charBuf = getCachedCharBuffer();
> charBuf.reset();
> int start = 0;
> int todoChars = text.length();
> do {
> int copied = copy(text, start, charBuf);
> start += copied;
> todoChars -= copied;
> boolean endOfInput = todoChars <= 0;
>
> charBuf.flip();
> CodeResult result;
> do {
> result = charsetEncoder.encode(charBuf, byteBuf, endOfInput);
> if (result == CodeResult.OVERFLOW) { // byteBuf full
> // destination consumes contents
> // and returns byte buffer with more capacity
> byteBuf = destination.drain(byteBuf);
> }
> } while (result == CodeResult.OVERFLOW);
> } while (!endOfInput);
> }
>
> /**
> * Copies characters from the StringBuilder into the CharBuffer,
> * starting at the specified offset and ending when either all
> * characters have been copied or when the CharBuffer is full.
> *
> * @return the number of characters that were copied
> */
> int copy(StringBuilder source, int offset, CharBuffer destination) {}
> }
> {code}
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]