Seq.Client.Log4j - Seq appender for Log4j 2

I've been working to build a Seq appender for Log4j 2, which will allow Java applications that use Log4j to send events to Seq. While I've been previously been able to configure Log4net instances to send to Seq using Seq.Client.Log4net and Log4net.Async, there seems to be a lack of an equivalent for Java. For Java, the Seq documentation directs you to either use GELF or - assuming you have the option - serilogj. 

I haven't had much Java development experience in the past, but I started with a sample from this discussion, which gave me some direction towards creating a working Log4j appender.  Initially I produced the Json output with Gson and Apache Commons Collections, but I trimmed those dependencies back to just use the Jackson package that Log4j 2 already uses. 

I elected not to use a Log4j layout, although it's likely I could have accomplished this with a JsonLayout - I wanted to simply build a class around the Seq Json format and pass it to Seq.

I included some of the features of Lurgle.Logging where possible, such as allowing a correlation id to be passed (via the ThreadContext stack) or automatically generating a per-thread correlation id. For this purpose, I added an ability to configure the correlation id property name, so that if a UUID is being passed by an application that doesn't have the name "correlationId", you can still catch it. I also added the ability to disable including a correlation id with events.

The correlation id functionality uses a per-thread cache, like Lurgle.Logging, which has a configurable cache time in seconds. The cache allows the appender to consistently pass the same correlation id for a thread while it's alive. If set to 0, the cache is disabled and a static correlation id is used.

I also pass the MachineName, ThreadId, MethodName, and ProcessName properties to Seq, which helps provide well structured events. 

Finally, the Seq appender will add any other items in the ThreadContext stack as properties, along with any configured via a Property key in the log4j  appender config.

Configuration is a fairly straightforward affair, and easily added to an async logger. You can see that the example below contains the configuration properties that are available, as well as an example as adding a property within the config.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="com.mattmofdoom.logging.log4j2.seqappender" status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<SeqAppender name="SeqAppender">
<SeqUrl name="Url">https://seq.domain.com</SeqUrl>
<SeqApiKey name="ApiKey"></SeqApiKey>
<AppName name="AppName">Test App</AppName>
<CacheTime>600</CacheTime>
<CorrelationProperty>CorrelationId</CorrelationProperty>
<IncludeCorrelation>true</IncludeCorrelation>
<Property name="Example">Example Property</Property>
</SeqAppender>
</Appenders>
<Loggers>
<AsyncRoot level="all">
<AppenderRef ref="Console"/>
<AppenderRef ref="SeqAppender"/>
</AsyncRoot>
</Loggers>
</Configuration>

I contemplated some interesting features for this, such as parsing log messages to extract properties and potentially transform them into fully structured logs - however some tinkering with this in Seq.Client.Log4net discouraged me after I found that the added overhead produced less than ideal outcomes. It's not absolute that this would occur in Log4j, but I considered that this was merely a 'nice to have'. 

I don't have an application that I can readily add this appender to, but it passes unit testing quite happily and logs to a Seq instance. 

You can grab the code from Github to compile - feedback welcome!

 

Comments