Historically, logging messages were supposed to be consumed by humans, and its main purpose were to aid the developer on a debugging process. Past the DevOps evolution and the increase on scale of applications, machines are nowadays the major consumers of logging messages.
So why are we still logging messages like these:
Processed 23 flight records for flight UA1234 for airline United
Instead of:
evt=FLIGHT_RECORDS_PROCESSED recordCount=23 airlineCode=UA flightNumber=1234 airlineName=United
Both messages convey the same information but the later format has the following advantages:
-
It’s very easy to parse, avoiding complex and cumbersome regular expressions to extract semantics from them;
-
It’s much easier to maintain. One can easily extend it with additional information without breaking the parser.
SLog4j is implemented itself on top of SLF4J and mimics its API. Therefore any application that already uses SLF4J can start using SLog4j very easily with a few minor modifications.
SLog4j requires Java 1.6+ and is available from both JCenter and Maven Central repositories.
The default formatter used by SLog4j fits better the typical case where both structured and unstructured messages are being logged. On such cases, a basic pattern configuration for Logback and Log4J could be:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSSZ} %level %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSSZ} %p %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
Instead of the standard SLF4J Logger, you must instantiate the SLog4j Logger:
import org.slog4j.SLogger;
import org.slog4j.SLoggerFactory;
class MyClass {
private static final SLogger slog = SLoggerFactory.getLogger(MyClass.class);
// ...
}
Before delving into details of SLog4j API, let’s see some examples that work out-of-the-box.
Map<String, ?> credentials = new HashMap<>() {{
put("username", "jsmith");
put("login_time", new Date());
}};
byte[] token = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
slog.info("CONNECT");
slog.info("SSL_CONNECT", "protocol", "tlsv1.2", "tcp_port", 443);
slog.info("USER_LOGIN", credentials, "token", token);
2017-10-08T16:08:02.055-0500 INFO evt=CONNECT 2017-10-08T16:08:02.195-0500 INFO evt=SSL_CONNECT protocol=tlsv1.2 tcp_port=443 2017-10-08T16:08:02.913-0500 INFO evt=USER_LOGIN username=jsmith login_time=2017-10-08T16:08:02.899-0500 token=AQIDBAUGBwg=
A simplified view of the SLogger interface is shown below:
package org.slog4j;
public interface SLogger {
void error(String eventId, Object... objs);
void warn(String eventId, Object... objs);
void info(String eventId, Object... objs);
void debug(String eventId, Object... objs);
void trace(String eventId, Object... objs);
}
With SLog4j you’ll be always logging structured events. At the API level this implies that your application will provide a sequence of one or more properties, i.e. Name/Value pairs, to one of the SLogger methods above. This sequence is conceptually comprised of:
-
A mandatory property to identify the event being logged. The value is taken from the eventId argument and the name is
evt
by default and can be configured to another value; -
An optional spanId property used to correlate events;
-
Additional properties taken from the objs array.
The objs array, in its turn, contains a variable sequence of either:
-
Name/Value attribute, where Name is a String and Value is an object;
-
An object that can be expanded to a properties sequence.
-
A Throwable
Every object must be first marshalled to text to be logged. On SLog4j this marshalling is a four step process:
-
Is it a Single Object? True if obj type is a String, a primitive wrapper class or has a registered joda-convert ToStringConverter;
-
Is a Complex Object? Is there a registered ToPropertiesConverter for its type?
-
Is a Throwable?
-
Everything else
The Structured logging technique was positioned on the Adopt ring on January 2015 edition of [ThoughtWorks' Technology Radar](https://www.thoughtworks.com/radar/techniques/structured-logging). Today most popular programming languages has at least one mature solution for Structured logging but Java, albeit surprisingly, still lacks behind. Until now ;-)