[ 
https://issues.apache.org/jira/browse/LOG4J2-506?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13878109#comment-13878109
 ] 

Remko Popma edited comment on LOG4J2-506 at 1/22/14 1:19 AM:
-------------------------------------------------------------

One thing: there is currently a discussion on the log4j-dev mailing list about 
adding log Levels (which everyone is welcome to join btw).

My suggested implementation of {{BinaryLayout$LevelStrategy}} would put the 
{{Level.intLevel()}} in the binary log. Be aware that it is possible that these 
int values change with the next log4j2 release.


was (Author: rem...@yahoo.com):
One thing: there is currently a discussion on the log4j-dev mailing list about 
adding log Levels (which everyone is welcome to join).

My suggested implementation of {{BinaryLayout$LevelStrategy}} would put the 
{{Level.intLevel()}} in the binary log. Be aware that it is possible that these 
int values change with the next log4j2 release.

> Binary logging interface
> ------------------------
>
>                 Key: LOG4J2-506
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-506
>             Project: Log4j 2
>          Issue Type: New Feature
>          Components: Core, Layouts
>            Reporter: Gregg Donovan
>            Priority: Minor
>
> From 
> [discussion|https://groups.google.com/forum/#!topic/mechanical-sympathy/7HdRCacJCDk]
>  with Remko Pompa on the mechanical-sympathy list:
> {quote}We -- Etsy.com -- have a request logging system that we've begun to 
> outgrow due to increased log volume and increased size of the messages we 
> need to log. The messages are Thrift-encoded search requests from 1-100K in 
> size. We use the logs to feed our homegrown traffic replay tools for offline 
> performance tests and to reproduce issues found in production (GC, perf, 
> etc.).
> For our next gen request logging system we would like the following:
> Low contention -- Minimal lock contention despite log writes from many (10s 
> to 100s) application threads.
> Async writes -- File I/O should be outside of the actual request path.
> Binary logging -- Our current system base64 encodes the payload as a String 
> and logs it via log4j 1.2. ;-) This creates more garbage than we would like 
> and makes the data written twice as large. It was good enough for a while, 
> but now we need something more efficient...
> Compression -- Either per-message or per block of messages would be great. 
> Incurring large periodic CPU penalties from logrotate + gzip, as we do now, 
> is less than ideal.
> Rotation -- Ideally based on file size, but time-based would also do.
> {quote}
> Remko:
> {quote}
> You can do this with log4j-2.0. The current version (log4j-2.0-beta-9) out of 
> the box gives you 3 out of 5: Async Loggers use the Disruptor under the hood 
> -> low contention & async writes, and RollingRandomAccessFileAppender (the 
> fastest appender) can do rotation. This appender can also optionally do 
> compression in a separate thread, but this happens on roll-over only, not 
> continuously. (Still, I would guess that this is more CPU-efficient than 
> using logrotate + gzip in a separate process...)
> The binary logging can be achieved with a bit of custom code.
> First, your objects would need to provide some way to turn themselves into 
> bytes. I doubt that you want to use java standard serialization :-), so how 
> about this as an interface to log4j that your objects would implement:
> {quote}
> {code:java}
> public interface BinaryMessage extends 
> org.apache.logging.log4j.message.Message {
>     int getLength(); // return byte count
>     void writeInto(ByteBuffer buffer); // write length bytes from the current 
> buffer position
> }
> {code}
> {quote}
> Log4j2 Loggers already have methods that take a Message argument (instead of 
> a String), so in your application code you could just write 
> {quote}
> {code:java}
> Logger logger = LogManager.getLogger(MyClass.class); // usually cached in 
> static field
> logger.info(myObject); // where myObject implements BinaryMessage
> {code}
> {quote}
> Second, in the log4j2 framework, the Layout is responsible for converting a 
> LogEvent into bytes, so you need a BinaryLayout that recognizes 
> BinaryMessages. The layout could optionally output the timestamp (long) and 
> log level (byte), followed by the message size (int) and the message bytes 
> (byte[]).
> Using the log4j2 plugin model, an implementation could look something like 
> below.
> {quote}
> {code:java}
> @Plugin(name = "BinaryLayout", category = "Core", elementType = "layout", 
> printObject = true)
> public final class BinaryLayout extends AbstractLayout<LogEvent> {
>     private static enum TimestampStrategy {
>         WRITE {
>             public void write(LogEvent event, ByteBuffer buffer) {
>                 buffer.putLong(event.getMillis());
>             }
>         },
>         DONT {
>             public void write(LogEvent event, ByteBuffer buffer) {
>             }
>         };
>         abstract void write(LogEvent event, ByteBuffer buffer);
>     }
>     private static enum LevelStrategy {
>         WRITE {
>             public void write(LogEvent event, ByteBuffer buffer) {
>                 buffer.put((byte) event.getLevel().intLevel());
>             }
>         },
>         DONT {
>             public void write(LogEvent event, ByteBuffer buffer) {
>             }
>         };
>         abstract void write(LogEvent event, ByteBuffer buffer);
>     }
>     private final TimestampStrategy timestamp;
>     private final LevelStrategy level;
>     private final int prefixLength;
>     public BinaryLayout(boolean timestamp, boolean level) {
>         this.timestamp = timestamp ? TimestampStrategy.WRITE : 
> TimestampStrategy.DONT;
>         this.level = level ? LevelStrategy.WRITE : LevelStrategy.DONT;
>         final int stampBytes = timestamp ? 8 : 0;
>         final int levelBytes = level ? 1 : 0;
>         prefixLength = 4 + stampBytes + levelBytes;
>     }
>     @Override
>     public byte[] toByteArray(LogEvent event) {
>         final BinaryMessage msg = (BinaryMessage) event.getMessage();
>         final byte[] result = new byte[msg.getLength() + prefixLength];
>         final ByteBuffer buffer = ByteBuffer.wrap(result);
>         timestamp.write(event, buffer);
>         level.write(event, buffer);
>         buffer.putInt(msg.getLength());
>         msg.writeInto(buffer);
>         return result;
>     }
>     @Override
>     public LogEvent toSerializable(LogEvent event) {
>         return event;
>     }
>     @Override
>     public String getContentType() {
>         return "application/octet-stream";
>     }
>     @Override
>     public Map<String, String> getContentFormat() {
>         return new HashMap<String, String>();
>     }
>     @PluginFactory
>     public static BinaryLayout createLayout(
>             @PluginAttribute("writeTimestamp") final String writeTimestamp,
>             @PluginAttribute("writeLevel") final String writeLevel) {
>         final boolean timestamp = Booleans.parseBoolean(writeTimestamp, 
> false);
>         final boolean level = Booleans.parseBoolean(writeLevel, false);
>         return new BinaryLayout(timestamp, level);
>     }
> }
> {code}
> {quote}
> Unfortunately the log4j API does not allow us to be completely garbage-free, 
> you still need to allocate the byte[] array and ByteBuffer for each event...
> (The LogEvents are reused Disruptor ring buffer slots if you make all loggers 
> AsyncLoggers.)
> Still, this should be a lot better (garbage-wise) than base64-encoding a 
> String, and then turning that String into bytes again...
> Here is the log4j2.xml configuration that would put it all together (modify 
> the "packages" attribute at the top to match your package):
> (Don't forget to set system property 
> -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
>  to make all loggers AsyncLoggers.)
> {quote}
> {code:xml}
> <?xml version="1.0" encoding="UTF-8"?>
> <Configuration status="WARN" packages="package.of.binary.layout.class">
>   <Appenders>
>     <RollingRandomAccessFile name="rraf" fileName="binary.log"
>                  
> filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz"
>                  append="false" immediateFlush="false">
>       <BinaryLayout writeTimestamp="true" writeLevel="true" />
>       <Policies>
>         <TimeBasedTriggeringPolicy />
>         <SizeBasedTriggeringPolicy size="250 MB"/>
>       </Policies>
>     </RollingRandomAccessFile>
>   </Appenders>
>   
>   <Loggers>
>     <Root level="trace" includeLocation="false">
>       <AppenderRef ref="rraf"/>
>     </Root>
>   </Loggers>
> </Configuration>
> {code}



--
This message was sent by Atlassian JIRA
(v6.1.5#6160)

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org
For additional commands, e-mail: log4j-dev-h...@logging.apache.org

Reply via email to