diff --git a/spring-geode-docs/spring-geode-docs.gradle b/spring-geode-docs/spring-geode-docs.gradle index 8161ed67..b94f0299 100644 --- a/spring-geode-docs/spring-geode-docs.gradle +++ b/spring-geode-docs/spring-geode-docs.gradle @@ -46,7 +46,10 @@ asciidoctor { 'docs-src-dir' : rootProject.projectDir.path + '/spring-geode-docs/src/main/java', 'docs-resources-dir' : rootProject.projectDir.path + '/spring-geode-docs/src/main/resources', 'examples-dir' : rootProject.projectDir.path + '/spring-geode-examples', - 'samples-dir' : rootProject.projectDir.path + '/spring-geode-samples' + 'samples-dir' : rootProject.projectDir.path + '/spring-geode-samples', + 'starter-logging-dir' : rootProject.projectDir.path + '/spring-geode-starter-logging', + 'starter-logging-src-dir' : rootProject.projectDir.path + '/spring-geode-starter-logging/src/main/src', + 'starter-logging-resources-dir' : rootProject.projectDir.path + '/spring-geode-starter-logging/src/main/resources' } javadoc { diff --git a/spring-geode-docs/src/docs/asciidoc/index.adoc b/spring-geode-docs/src/docs/asciidoc/index.adoc index eab7c4ed..8d794540 100644 --- a/spring-geode-docs/src/docs/asciidoc/index.adoc +++ b/spring-geode-docs/src/docs/asciidoc/index.adoc @@ -235,6 +235,7 @@ include::functions.adoc[] include::continuous-query.adoc[] include::data-serialization.adoc[] include::security.adoc[] +include::logging.adoc[] include::actuator.adoc[] include::session.adoc[] include::cloudfoundry.adoc[] diff --git a/spring-geode-docs/src/docs/asciidoc/logging.adoc b/spring-geode-docs/src/docs/asciidoc/logging.adoc new file mode 100644 index 00000000..7c7bc5f5 --- /dev/null +++ b/spring-geode-docs/src/docs/asciidoc/logging.adoc @@ -0,0 +1,368 @@ +[[geode-logging]] +== 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: Refer to Apache Geode's {apache-geode-docs}/reference/topics/gemfire_properties.html[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) +{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html[`@EnableLogging`] +annotation no longer has any effect on Apache Geode or Pivotal GemFire logging either and is the reason it has been +https://jira.spring.io/browse/DATAGEODE-299[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 +[source,txt] +---- +$ java -classpath ...:/path/to/MySpringBootApacheGeodeClientCacheApplication.jar -Dgemfire.log-level=DEBUG + example.app.MySpringBootApacheGeodeClientCacheApplication +---- + +.Externalized configuration using Apache Geode `gemfire.properties` +[source,properties] +---- +# Apache Geode/Pivotal GemFire only/specific properties +log-level=INFO +---- + +.Externalized configuration using Spring Boot `application.properties` +[source,properties] +---- +spring.data.gemfire.cache.log-level=DEBUG +---- + +Or: + +[source,properties] +---- +spring.data.gemfire.logging.level=DEBUG +---- + +.Java configuration using SDG's `@EnableLogging` annotation +[source,java] +---- +@SpringBootApplication +@EnableLogging(logLevel = "DEBUG") +class MySpringBootApacheGeodeClientApplication { + // ... +} +---- + +That is to say, none of the approaches above have any effect without the **new** SBDG logging starter. + +[[geode-logging-configuration]] +=== 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: 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. + +[[geode-logging-configuration-log-levels]] +==== 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 +[source,xml] +---- + + + +---- + +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 +[source,text] +---- +$ java -classpath ...:/path/to/MySpringBootApplication.jar -Dspring.boot.data.gemfire.log.level=DEBUG + package.to.MySpringBootApplicationClass +---- + +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` +[source,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. + +[[geode-logging-configuration-composition]] +==== 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 +[source,xml] +---- +include::{starter-logging-resources-dir}/logback-include.xml[] +---- + +Then, this Logback configuration snippet can be included in an application-specific, _Logback_ XML configuration file +as follows: + +.logback.xml +[source,xml] +---- +include::{starter-logging-resources-dir}/logback.xml[] +---- + + +[[geode-logging-slf4j-logback-api-support]] +=== 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`. + +[[geode-logging-slf4j-logback-api-support-appender-composite]] +==== CompositeAppender + +The `org.springframework.geode.logging.slf4j.logback.CompositeAppender` class is an implementation of _Logback_ +`Appender` and the https://en.wikipedia.org/wiki/Composite_pattern[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` +[source,java] +---- +ConsoleAppender consoleAppender = ...; + +FileAppender fileAppender = ...; + +Appender 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` +[source,java] +---- +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[])` method or the `CompositeAppender.compose(:Iterable>)` method. + +[[geode-logging-slf4j-logback-api-support-appender-delegate]] +==== 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` +[source,java] +---- +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)); +---- + +[[geode-logging-slf4j-logback-api-support-appender-string]] +==== 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 +[source,java] +---- +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.