diff --git a/build.gradle b/build.gradle index d4a6733558..233c8be5c1 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ buildscript { ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") ext.guava_version = constants.getProperty("guavaVersion") ext.caffeine_version = constants.getProperty("caffeineVersion") + ext.disruptor_version = constants.getProperty("disruptorVersion") ext.metrics_version = constants.getProperty("metricsVersion") ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion") ext.okhttp_version = '3.5.0' diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index 63681f41a8..b351e59536 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -46,7 +46,7 @@ - + diff --git a/constants.properties b/constants.properties index 9d4db470fc..8d23d66503 100644 --- a/constants.properties +++ b/constants.properties @@ -6,6 +6,7 @@ platformVersion=4 guavaVersion=25.1-jre proguardVersion=6.0.3 bouncycastleVersion=1.60 +disruptorVersion=3.4.2 typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 artifactoryPluginVersion=4.7.3 diff --git a/docs/source/node-administration.rst b/docs/source/node-administration.rst index 690a339732..98e5697ddb 100644 --- a/docs/source/node-administration.rst +++ b/docs/source/node-administration.rst @@ -13,6 +13,12 @@ It may be the case that you require to amend the log level of a particular subse closer look at hibernate activity). So, for more bespoke logging configuration, the logger settings can be completely overridden with a `Log4j 2 `_ configuration file assigned to the ``log4j.configurationFile`` system property. +The node is using log4j asynchronous logging by default (configured via log4j2 properties file in its resources) +to ensure that log message flushing is not slowing down the actual processing. +If you need to switch to synchronous logging (e.g. for debugging/testing purposes), you can override this behaviour +by adding ``-DLog4jContextSelector=org.apache.logging.log4j.core.selector.ClassLoaderContextSelector`` to the node's +command line or to the ``jvmArgs`` section of the node configuration (see :doc:`corda-configuration-file`). + Example +++++++ diff --git a/node/build.gradle b/node/build.gradle index 8d758c8891..3a344867a3 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -91,6 +91,9 @@ dependencies { // For caches rather than guava compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version" + // For async logging + compile "com.lmax:disruptor:$disruptor_version" + // JOpt: for command line flags. compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" diff --git a/node/src/main/kotlin/net/corda/node/utilities/logging/AsyncLoggerContextSelectorNoThreadLocal.kt b/node/src/main/kotlin/net/corda/node/utilities/logging/AsyncLoggerContextSelectorNoThreadLocal.kt new file mode 100644 index 0000000000..daa86cd62c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/logging/AsyncLoggerContextSelectorNoThreadLocal.kt @@ -0,0 +1,43 @@ +package net.corda.node.utilities.logging + +import net.corda.nodeapi.internal.addShutdownHook +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.LoggerContext +import org.apache.logging.log4j.core.async.AsyncLoggerContext +import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector +import org.apache.logging.log4j.core.util.Constants +import org.apache.logging.log4j.util.PropertiesUtil +import java.net.URI + +class AsyncLoggerContextSelectorNoThreadLocal : ClassLoaderContextSelector() { + companion object { + /** + * Returns `true` if the user specified this selector as the Log4jContextSelector, to make all loggers + * asynchronous. + * + * @return `true` if all loggers are asynchronous, `false` otherwise. + */ + @JvmStatic + fun isSelected(): Boolean { + return AsyncLoggerContextSelectorNoThreadLocal::class.java.name == PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR) + } + } + + init { + // if we use async log4j logging, we need to shut down the logging to flush the loggers on exit + addShutdownHook { LogManager.shutdown() } + } + + override fun createContext(name: String?, configLocation: URI?): LoggerContext { + return AsyncLoggerContext(name, null, configLocation).also { it.setUseThreadLocals(false) } + } + + override fun toContextMapKey(loader: ClassLoader?): String { + // LOG4J2-666 ensure unique name across separate instances created by webapp classloaders + return "AsyncContextNoThreadLocal@" + Integer.toHexString(System.identityHashCode(loader)) + } + + override fun defaultContextName(): String { + return "DefaultAsyncContextNoThreadLocal@" + Thread.currentThread().name + } +} diff --git a/node/src/main/resources/log4j2.component.properties b/node/src/main/resources/log4j2.component.properties new file mode 100644 index 0000000000..1b55982139 --- /dev/null +++ b/node/src/main/resources/log4j2.component.properties @@ -0,0 +1,2 @@ +Log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal +AsyncLogger.RingBufferSize=262144 \ No newline at end of file