15. Logging

Apache Geode 1.9.2 was modularized to separate its use of the Apache Log4j API to log output in Geode code from the underlying implementation of logging, which uses Apache Log4j as the logging provider by default.

Prior to 1.9.2, the Apache Log4j API (i.e. log4j-api) along with the Apache Log4j provider (i.e. log4j-core) were automatically pulled in by Apache Geode core (i.e. org.apache.geode:geode-core) thereby making it problematic to change logging providers when using Apache Geode in Spring Boot applications.

However, now, in order to get any log output from Apache Geode whatsoever, Apache Geode requires a logging provider on your Spring Boot application classpath. Consequently, this also means the old Apache Geode Properties, e.g. log-level no longer have any effect, regardless of whether the property (e.g. log-level) is specified in gemfire.properties, in Spring Boot application.properties or even as a JVM System Property, -Dgemfire.log-level.

[Tip]Tip

Refer to Apache Geode’s Documentation for a complete list of valid Properties, including the Properties used to configure logging.

Unfortunately, this also means the Spring Data for Apache Geode & Pivotal GemFire (SDG) @EnableLogging annotation no longer has any effect on Apache Geode or Pivotal GemFire logging either and is the reason it has been deprecated. The reason @EnableLogging no longer has any effect on logging is because this annotation’s attributes and associated SDG properties indirectly sets the corresponding Apache Geode or Pivotal GemFire properties, which again, are useless from Apache Geode 1.9.2 onward.

By way of example, and to make this concrete, none of the following approaches have any effect on Apache Geode or Pivotal GemFire logging:

Command-line configuration. 

$ java -classpath ...:/path/to/MySpringBootApacheGeodeClientCacheApplication.jar -Dgemfire.log-level=DEBUG
    example.app.MySpringBootApacheGeodeClientCacheApplication

Externalized configuration using Apache Geode gemfire.properties

# Apache Geode/Pivotal GemFire only/specific properties
log-level=INFO

Externalized configuration using Spring Boot application.properties

spring.data.gemfire.cache.log-level=DEBUG

Or:

spring.data.gemfire.logging.level=DEBUG

Java configuration using SDG’s @EnableLogging annotation. 

@SpringBootApplication
@EnableLogging(logLevel = "DEBUG")
class MySpringBootApacheGeodeClientApplication {
	// ...
}

That is to say, none of the approaches above have any effect without the new SBDG logging starter.

15.1 Configure Apache Geode & Pivotal GemFire Logging

So, how do you configure logging for Apache Geode and Pivotal GemFire?

Effectively, 3 things are required to get Apache Geode or Pivotal GemFire to log output:

1) First, you must declare a logging provider on your Spring Boot application classpath (e.g. Logback).

2) (optional) Next, you must declare an adapter, or bridge JAR, between Log4j and your logging provider if your declared logging provider is not Apache Log4j.

For example, if you use the SLF4J API to log output from your Spring Boot application along with Logback as your logging provider/implementation, then you must include the org.apache.logging.log4j.log4j-to-slf4j adapter/bridge JAR dependency as well.

Internally, Apache Geode uses the Apache Log4j API to log output from Geode components. Therefore, you must bridge Log4j to any other logging provider (e.g. Logback) that is not Log4j (i.e. log4j-core). If you are using Log4j as your logging provider then you do not need to declare an adapter/bridge JAR on your Spring Boot application classpath.

3) Finally, you must supply logging provider configuration to configure Loggers, Appenders, log levels, etc.

For example, when using Logback, you must provide a logback.xml configuration file on your Spring Boot application classpath, or in the filesystem. Alternatively, you can use other means to configure your logging provider and get Apache Geode to log output.

[Note]Note

Apache Geode’s geode-log4j module covers the required configuration for steps 1-3 above and uses Apache Log4j (i.e. org.apache.logging.log4j:log4j-core) as the logging provider. The geode-log4j module even provides a default, log4j2.xml configuration file to configure Loggers, Appenders and log levels for Apache Geode.

If you declare Spring Boot’s own org.springframework.boot:spring-boot-starter-logging on your application classpath then this will cover Steps 1 and 2 above.

The spring-boot-starter-logging dependency declares Logback as the logging provider and automatically adapts, or bridges java.util.logging (JUL) and Apache Log4j to SLF4J. However, you still need to supply logging provider configuration, such as a logback.xml file for Logback, to configure logging not only for your Spring Boot application, but also for Apache Geode as well.

SBDG has simplified the setup of Apache Geode and Pivotal GemFire logging. Simply declare the org.springframework.geode:spring-geode-starter-logging dependency on your Spring Boot application classpath!

Unlike Apache Geode’s default Log4j XML configuration file (i.e. log4j2.xml), SBDG’s provided logback.xml configuration file is properly parameterized enabling you to adjust log levels as well as add Appenders.

In addition, SBDG’s provided Logback configuration uses templates so you can compose your own logging configuration while still "including" snippets from SBDG’s provided logging configuration metadata, such as Loggers and Appenders.

15.1.1 Configuring Log Levels

One of the most common logging tasks is to adjust the log-level of one or more Loggers, or the ROOT Logger. However, a user may only want to adjust the log-level for specific components of his/her Spring Boot application, such as for Apache Geode, by setting the log-level for only the Logger that logs Apache Geode events.

SBDG’s Logback configuration defines 3 Loggers to control the log output from Apache Geode:

Apache Geode Loggers by name. 

<logger name="com.gemstone.gemfire" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.apache.geode" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.jgroups" level="${spring.boot.data.gemfire.jgroups.log.level:-ERROR}"/>

The com.gemstone.gemfire Logger is a legacy Logger covering old Pivotal GemFire bits still present in Apache Geode for backwards compatibility reasons. This Logger’s use should be largely unnecessary.

The org.apache.geode Logger is the primary Logger used to control log output from all Apache Geode components during the runtime operation of Apache Geode. Both this Logger and the legacy com.gemstone.gemfire Logger default log output to INFO.

The org.jgroups Logger is used to log output from Apache Geode’s message distribution and membership system. Apache Geode uses JGroups for membership and message distribution between peer members (nodes) in the cluster (distributed system). By default, JGroups log messages are logged at ERROR.

The log-level for the com.gemstone.gemfire and org.apache.geode Loggers are configured with the spring.boot.data.gemfire.log.level property. The org.jgroups Logger is independently configured with the spring.boot.data.gemfire.jgroups.log.level property.

The SBDG logging properties can be set on the command-line as JVM System Properties when running your Spring Boot application:

Setting the log-level from the command-line. 

$ java -classpath ...:/path/to/MySpringBootApplication.jar -Dspring.boot.data.gemfire.log.level=DEBUG
    package.to.MySpringBootApplicationClass

[Note]Note

Setting JVM System Properties using $ java -jar MySpringBootApplication.jar -Dspring.boot.data.gemfire.log.level=DEBUG is not supported by the Java Runtime Environment (JRE).

Alternatively, you can configure and control Apache Geode logging in Spring Boot application.properties:

Setting the log-level in application.properties

spring.boot.data.gemfire.log.level=DEBUG

For backwards compatibility, SBDG additionally supports the old Spring Data for Apache Geode (SDG) logging properties as well, using either:

spring.data.gemfire.cache.log-level=DEBUG

Or:

spring.data.gemfire.logging.level=DEBUG

If you previously used either of these SDG based logging properties, they will continue to work as designed in SBDG 1.3 or later.

15.1.2 Composing Logging Configuration

As mentioned earlier, SBDG allows you to compose your own logging configuration from SBDG’s default, provided Logback configuration metadata.

SBDG conveniently bundles the Loggers and Appenders from SBDG’s logging starter into a template file that you can include into your own, custom Logback XML configuration file.

The Logback template file appears as follows:

logback-include.xml. 

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

	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
		</encoder>
	</appender>

	<appender name="delegate" class="org.springframework.geode.logging.slf4j.logback.DelegatingAppender"/>

	<logger name="com.gemstone.gemfire" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
	<logger name="org.apache.geode" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
	<logger name="org.jgroups" level="${spring.boot.data.gemfire.jgroups.log.level:-ERROR}"/>

</included>

Then, this Logback configuration snippet can be included in an application-specific, Logback XML configuration file as follows:

logback.xml. 

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

	<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>

	<include resource="logback-include.xml"/>

	<root level="${logback.root.log.level:-INFO}">
		<appender-ref ref="console"/>
		<appender-ref ref="delegate"/>
	</root>

</configuration>

15.2 SLF4J & Logback API Support

SBDG provides additional support when working with the SLF4J and Logback APIs. This support is available when you declare the org.springframework.geode:spring-geode-starter-logging dependency on your Spring Boot application classpath.

One of the main supporting classes from the spring-geode-starter-logger is the org.springframework.geode.logging.slf4j.logback.LogbackSupport class. This class provides methods to:

  • Resolve a reference to the Logback LoggingContext
  • Resolve the SLF4J ROOT Logger as a Logback Logger
  • Lookup Appenders by name and required type
  • Add/Remove Appenders to Loggers
  • And even reset the state of the Logback logging system, which can prove to be most useful during testing

LogbackSupport can even suppress the auto-configuration of Logback performed by Spring Boot on startup, another useful utility during automated testing.

In addition to the LogbackSupport class, SBDG also provides some custom Logback Appenders.

15.2.1 CompositeAppender

The org.springframework.geode.logging.slf4j.logback.CompositeAppender class is an implementation of Logback Appender and the Composite Software Design Pattern.

CompositeAppender enables developers to compose multiple Appenders and use them as if they were a single Appender.

For example, you could compose both the Logback ConsoleAppender and FileAppender into one using:

Composing multiple Appenders

ConsoleAppender<ILoggingEvent> consoleAppender = ...;

FileAppender<ILoggingEvent> fileAppender = ...;

Appender<ILoggingEvent> compositeAppender = CompositeAppender.compose(consoleAppender, fileAppender);

// do something with the compositeAppender

You could then add the CompositeAppender to a "named" Logger by doing:

Register CompositeAppender on "named" Logger

Logger namedLogger = LoggerFactory.getLogger("loggerName");

LogbackSupport.toLogbackLogger(namedLogger)
  .ifPresent(it -> LogbackSupport.addAppender(it, compositeAppender));

In this case, the "named" Logger will log events (or log messages) to both the Console and File Appenders.

It is simple to compose an array or Iterable of Appenders by using either the CompositeAppender.compose(:Appender<T>[]) method or the CompositeAppender.compose(:Iterable<Appender<T>>) method.

15.2.2 DelegatingAppender

The org.springframework.geode.logging.slf4j.logback.DelegatingAppender is a pass-through Logback Appender implementation wrapping another Logback Appender, or collection of Appenders doing actual work, like the ConsoleAppender, a FileAppender or a SocketAppender, etc. By default, the DelegatingAppender delegates to the NOPAppender thereby doing no actual work.

By default, SBDG registers the org.springframework.geode.logging.slfj4.logback.DelegatingAppender with the ROOT Logger, which can be useful for testing purposes.

With a reference to a DelegatingAppender, you can add any Appender as the delegate, even a CompositeAppender:

Add ConsoleAppender as the "delegate" for the DelegatingAppender

ConsoleAppender consoleAppender = new ConsoleAppender();

LogbackSupport.resolveLoggerContext().ifPresent(consoleAppender::setContext);

consoleAppender.setImmediateFlush(true);
consoleAppender.start();

LogbackSupport.resolveRootLogger()
  .flatMap(LogbackSupport::toLogbackLogger)
  .flatMap(rootLogger -> LogbackSupport.resolveAppender(rootLogger,
    LogbackSupport.DELEGATE_APPENDER_NAME, DelegatingAppender.class))
  .ifPresent(delegateAppender -> delegateAppender.setAppender(consoleAppender));

15.2.3 StringAppender

The org.springframework.geode.logging.slf4j.logback.StringAppender stores log message in-memory, appended to a String.

The StringAppender is very useful for testing purposes. For instance, you can use the StringAppender to assert that a Logger used by certain application components logged messages at the appropriately configured log level while other log messages were not logged.

For example:

StringAppender in Action. 

class ApplicationComponent {

	private final Logger logger = LoggerFactory.getLogger(getClass());

	public void someMethod() {
		logger.debug("Some debug message");
		// ...
	}

	public void someOtherMethod() {
		logger.info("Some info message");
	}
}

// Assuming the ApplicationComponent Logger was configured with log-level 'INFO', then...
class ApplicationComponentUnitTests {

	private final ApplicationComponent applicationComponent = new ApplicationComponent();

	private final Logger logger = LoggerFactory.getLogger(ApplicationComponent.class);

	private StringAppender stringAppender;

	@Before
    public void setup() {

        LogbackSupport.toLogbackLogger(logger)
            .map(Logger::getLevel)
            .ifPresent(level -> assertThat(level).isEqualTo(Level.INFO));

        stringAppender = new StringAppender.Builder()
            .applyTo(logger)
            .build();
    }

    @Test
    public void someMethodDoesNotLogDebugMessage() {

        applicationComponent.someMethod();

        assertThat(stringAppender.getLogOutput).doesNotContain("Some debug message");
    }

    @Test
    public void someOtherMethodLogsInfoMessage() {

        applicationComponent.someOtherMethod();

        assertThat(stringAppender.getLogOutput()).contains("Some info message");
    }
}

There are many other uses for the StringAppender and it can be used safely in a multi-Threaded context by calling StringAppender.Builder.useSynchronization().

When combined with other SBDG provided Appenders in conjunction with the LogbackSupport class, you have a lot of power both in application code as well as your tests.