Implementation of property definers with logback

I often use logback in my personal/demo Java projects because it is easy to use and configure. Logback is also an implementation of the SLF4J API, so there is no need for an explicit binding artifact. In this article I talk about a very specific feature of logback, the implementation of property definers to define a new property that is usable in a <pattern> specification.

Introduction

First of all, for the sake of example I want to show how to create a dynamic property that contains the “identifier” of the running Java virtual machine instance. This information can be obtained from the Java Management API (package java.lang.management) and more specifically from the RuntimeMXBean interface type.

Given the following code:

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

// ........

RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();

RuntimeMXBean offers several methods to get some useful informations about the Java runtime environment. Do not confuse, however, the getVmName() with getName(). The former provides the name of the JVM implementation, which is a string like "Java HotSpot(TM) 64-Bit Server VM". The latter provides a representation of the running JVM instance.

The javadoc documentation of getName() is a bit vague and generic because there is no specific standard for this string. However, for what I’ve always seen with Oracle JDKs, the returned string has a form like "nnnn@hhhh" where nnnn is the PID (process identifier) of the running JVM instance and hhhh is the hostname (the machine name).

For this article I am mainly interested in the latter getName(). In order to use this identifier in your logs with logback, there are only few simple steps to do as described below.

Implementing the PropertyDefiner interface

The first step is the implementation of the ch.qos.logback.core.spi.PropertyDefiner interface, which is part of the logback Core SPI (Service Provider Interface). There are two ways to define a concrete property definer:

Both approaches are relatively easy, however PropertyDefiner extends another interface (ContextAware) which has several methods related to the logback internal context. For this reason, it’s generally preferable to extend PropertyDefinerBase since it already implements those context-related methods in a reasonable way.

For the purpose of this article, a simple implementation can be the following:

package your.pkgname;

import java.lang.management.ManagementFactory;
import ch.qos.logback.core.PropertyDefinerBase;

public class VmIdPropertyDefiner extends PropertyDefinerBase {
    public String getPropertyValue() {
        return ManagementFactory.getRuntimeMXBean().getName();
    }
}

The PropertyDefiner implementation class must be public and must have a public no-args constructor.

And that’s really all! As you can see, the getPropertyValue() method simply returns the name provided by RuntimeMXBean. Note that getPropertyValue() will be invoked only one time, not for each log record!

Configuring the property definer

Unfortunately, the above VmIdPropertyDefiner class is not enough by itself, you have to declare it in your logback.xml configuration file like the following:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <define name="vmId" class="your.pkgname.VmIdPropertyDefiner" scope="system"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %property{vmId} [%thread] %-5level %20.20logger{20} | %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

The <define> tag defines a new property using the following attributes:

  • name (required), the arbitrary name of the property. In my example it is vmId.
  • class (required), the FQN (fully qualified name) of the PropertyDefiner implementation class.
  • scope (optional), can be local, context or system. See the documentation in Scopes for more details. In my example, system is appropriate.

Once the property is defined, you can use the property name in your <pattern> specification, like %property{vmId} in the example.

Final test

If everything is correctly written and defined, you can use the following sample Main class to verify the logging output.

package your.pkgname;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws InterruptedException {
        logger.info("Application main started");
        Thread.sleep(2000);
        logger.info("Application main terminated");
    }
}

The output should be similar to this:

19:05:45.283 1234@PCNAME [main] INFO     your.pkgname.Main | Application main started
19:05:47.287 1234@PCNAME [main] INFO     your.pkgname.Main | Application main terminated

Note the (dummy) id 1234@PCNAME which is supposed to be the identifier of the running JVM instance.

Conclusions

The implementation of property definers is a fairly “advanced” and particular feature of logback. With the help of this article I think you should now have enough informations to go deeper and implement your own property definers, in case you need one.

Similar Posts