Using Graylog with Log4j2

Do you need to get your project integrated with a Graylog system? If yes, this article is for you. I will give the exact steps to add a gelf appender in your project and try to share as much detail as I can along with my experiences about exceptional cases you will encounter when you are packaging an uber-jar (aka fat-jar)

Before getting into the real stuff we will explain a little bit about Grayloy. Let’s see the formal definition put by the Graylog team itself.

What is Graylog?

Graylog is a leading centralized log management solution built to open standards for capturing, storing, and enabling real-time analysis of terabytes of machine data.

graylog.org

Graylog uses GELF instead of the plain old syslog style. GELF stands for Graylog Extended Log Format. Logging to graylog is more than just writing simply a line of text into a log file. It is more like firing structured log events in json format as shown in the code snippet below. You can log using UDP or TCP depending on your needs. It also supports compression and chunking if you want (Chunking is only available through UDP) For Graylog options and settings please check Graylog DOCs. This article will be about how to get a Java application talk to Graylog by using log4j2.

{
  "version": "1.1",
  "host": "example.org",
  "short_message": "A short message that helps you identify what is going on",
  "full_message": "Backtrace here\n\nmore stuff",
  "timestamp": 1385053862.3072,
  "level": 1,
  "_user_id": 9001,
  "_some_info": "foo",
  "_some_env_var": "bar"
}

GELF Implementations for Java

Currently there isn’t an official GELF implementation published by Graylog for any of the Java logging frameworks . However there is an opensource library that the Graylog community widely relies on. It is logstash-gelf.

Logstash-Gelf

logstash-gelf requires as of version 1.14.0 Java 7 or higher. Version 1.13.x and older require Java 6

logstash-gelf

Logstash-gelf is quite an all-rounder. It is not just supporting log4j2, it supports all the major java logging frameworks. For the log4j2 part it implements a log4j2 plugin, catches log events and creates GELF messages out of them. That’s why in order to get integrated into Graylog by using logstash-gelf, all you need to do is just having the right dependencies in your classpath and updating log4j2.xml accordingly.

Steps to Get Started

  1. Add logstash-gelf in your dependencies. Your logging related dependencies should end up looking like below:
<dependencies>
<dependency>
<groupId>biz.paluch.logging</groupId>
<artifactId>logstash-gelf</artifactId>
<version>1.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
view raw pom.xml hosted with ❤ by GitHub

2. Update your log4j2.xml adding the gelf appender accordingly. Host is the only mandatory thing.

<Configuration status="TRACE" monitorInterval="180">
<!–CHANGE HOST AND PORT PROPERTIES ACCORDING TO YOUR NEEDS–>
<Appenders>
<Gelf name="gelf" host="udp:graylog.somedomain.com" port="12211" version="1.1" extractStackTrace="true" filterStackTrace="true" mdcProfiling="true" includeFullMdc="true" maximumMessageSize="8192" originHost="%host{fqdn}">
<!–THESE FIELD DEFINITIONS ARE NOT MANDATORY, YOU CAN USE DEFAULTS–>
<Field name="timestamp" pattern="%d{dd MMM yyyy HH:mm:ss,SSS}"/>
<Field name="level" pattern="%level"/>
<Field name="simpleClassName" pattern="%C{1}"/>
<Field name="className" pattern="%C"/>
<Field name="server" pattern="%host"/>
<Field name="server.fqdn" pattern="%host{fqdn}"/>
<!–THESE ARE MY CUSTOM GRAYLOG FIELDS–>
<Field name="logStream" literal="MYAWESOMEAPPS"/>
<Field name="projectName" literal="AWESOMEPROJECT"/>
</Gelf>
</Appenders>
<Loggers>
<Root level="INFO" additivity="false" includeLocation="true">
<AppenderRef ref="RollingFile"/>
<AppenderRef ref="gelf"/>
</Root>
</Loggers>
</Configuration>
view raw log4j2.xml hosted with ❤ by GitHub

That’s all. Now your logs will be appended to Graylog as well.

For the details of the settings, check the list that I quoted from the logstash-gelf docs.

Attribute NameDescriptionDefault
hostHostname/IP-Address of the Logstash host. The host field accepts following forms: tcp:hostname for TCP transport, e. g. tcp:127.0.0.1 or tcp:some.host.com udp:hostname for UDP transport, e. g. udp:127.0.0.1, udp:some.host.com or just some.host.com redis://[:password@]hostname:port/db-number#listname for Redis transport. See Redis transport for logstash-gelf for details. redis-sentinel://[:password@]hostname:port/db-number?masterId=masterId#listname for Redis transport with Sentinel lookup. See Redis transport for logstash-gelf for details.none
portPort of the Logstash host12201
versionGELF Version 1.0 or 1.11.0
originHostOriginating HostnameFQDN Hostname
extractStackTraceSend the Stack-Trace to the StackTrace field (true/false)false
filterStackTracePerform Stack-Trace filtering (true/false)false
facilityName of the Facilitylogstash-gelf
mdcProfilingPerform Profiling (Call-Duration) based on MDC Data. See MDC Profiling for detailsfalse
includeFullMdcInclude all fields from the MDC.false
maximumMessageSizeMaximum message size (in bytes). If the message size is exceeded, the appender will submit the message in multiple chunks.8192
additionalFieldTypesType specification for additional and MDC fields. Supported types: String, long, Long, double, Double and discover (default if not specified, discover field type on parseability). Eg. field=String,field2=doublediscover for all additional fields
ignoreExceptionsThe default is true, causing exceptions encountered while appending events to be internally logged and then ignored. When set to false exceptions will be propagated to the caller, instead. You must set this to false when wrapping this Appender in a FailoverAppender.true
https://logging.paluch.biz/examples/log4j-2.x.html

Things you need to know when you are using Graylog

MDC

The acronym stands for Mapped Diagnostic Context. MDC is not a Graylog specific concept. It is a logging instrument employed to enrich log messages with more useful information. If you have some data that you want to be available when the actual log message is written, you put it into MDC. Speaking of log4j2, everything you put will be accessible in the appender.

This is how you set your custom gelf message fields programmaticly. If you want a field named _userId to be available in Graylog then you put it into ThreadContext before firing the log events. All the log events will be, sort of, paired with the information you put inside the ThreadContext untill you call ThreadContext.clearMap()

Streams

The Graylog streams are a mechanism to route messages into categories in realtime while they are processed. You define rules that instruct Graylog which message to route into which streams.

docs.graylog.org

If your Graylog system uses streams than it expects you to tell which stream you want your log messages to be in. So you should be providing the correct stream key-value pair either as a log4j2 appender field (like I did in the log4j2.xml above) or using MDC. For example in my case the stream identifying gelf field was logStream so I’ve set it’s value to MYAWESOMEAPPS.

Fat-Jar Issues

During runtime, plugins are located by the PluginManager of log4j2. PluginManager locates them inside the classpath by looking at a metadata file that is supposed to list all the plugins available. If you are packaging a fat-jar, this metadata file is generated during compilation by the corresponding annotation processor of log4j2-core. It does not append the entries for custom plugins to the metadata and always overwrites with the default value that comes from log4j2-core artifact. In order to get your plugin of interest listed inside this metadata file, you need to set a transformer in your pom.xml. Check the corresponding pom.xml changes:

<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<!–$NO-MVN-MAN-VER$–>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>com.talhature.graylog.fatjar.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>shaded</finalName>
<transformers>
<transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer"></transformer>
</transformers>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.edwgiz</groupId>
<artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
view raw pom.xml hosted with ❤ by GitHub

Since fat-jar packaging always tends to mess things up with the classpath metadata files you need to be careful for similar issues for your other dependencies. For example if you are using Apache CXF you will need one more transformer:

<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
   <resource>META-INF/cxf/bus-extensions.txt</resource>
</transformer>

Example Project

I have created a sample project and tested in my environment. Please feel free to fork and clone on GitHub. https://github.com/HalitTalha/Graylog-FatJar-Example

One thought on “Using Graylog with Log4j2

Leave a comment