mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
ENT-3444 create test-db module (#5093)
* ENT-3444 define RequiresDB annotation and junit5 extension * Move to internal * info to trace * Limit exposure of gradle imports * Enable annotation inheritance, and multiple SQL scripts per class or method * Get the test context class globally for all groups * usingRemoteDatabase flag
This commit is contained in:
parent
df19b444dd
commit
f01f8a129e
@ -396,6 +396,7 @@ bintrayConfig {
|
||||
'corda-node-api',
|
||||
'corda-test-common',
|
||||
'corda-test-utils',
|
||||
'corda-test-db',
|
||||
'corda-jackson',
|
||||
'corda-webserver-impl',
|
||||
'corda-webserver',
|
||||
|
@ -36,10 +36,11 @@ include 'jdk8u-deterministic'
|
||||
include 'test-common'
|
||||
include 'test-cli'
|
||||
include 'test-utils'
|
||||
include 'test-db'
|
||||
include 'smoke-test-utils'
|
||||
include 'node-driver'
|
||||
// Avoid making 'testing' a project, and allow build.gradle files to refer to these by their simple names:
|
||||
['test-common', 'test-utils', 'test-cli', 'smoke-test-utils', 'node-driver'].each {
|
||||
['test-common', 'test-utils', 'test-cli', 'test-db', 'smoke-test-utils', 'node-driver'].each {
|
||||
project(":$it").projectDir = new File("$settingsDir/testing/$it")
|
||||
}
|
||||
include 'tools:explorer'
|
||||
|
23
testing/test-db/build.gradle
Normal file
23
testing/test-db/build.gradle
Normal file
@ -0,0 +1,23 @@
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
testImplementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName 'corda-test-db'
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.extension.*
|
||||
import java.lang.reflect.AnnotatedElement
|
||||
|
||||
/***
|
||||
* A JUnit 5 [Extension] which invokes a [TestDatabaseContext] to manage database state across three scopes:
|
||||
*
|
||||
* * Test run (defined across multiple classes)
|
||||
* * Test suite (defined in a single class)
|
||||
* * Test instance (defined in a single method)
|
||||
*
|
||||
* A test class will not ordinarily register this extension directly: instead, it is registered for any class having the [RequiresDb]
|
||||
* annotation (or an annotation which is itself annotated with [RequiresDb]), which it consults to discover which group of tests the
|
||||
* test class belongs to (`"default"`, if not stated).
|
||||
*
|
||||
* The class of the [TestDatabaseContext] used is selected by a system property, `test.db.context` If this system property is not set, the
|
||||
* class name defaults to the [RequiresDb.defaultContextClassName] stated in the annotation, which in turn defaults to the class of
|
||||
* [NoOpTestDatabaseContext].
|
||||
*
|
||||
* When [BeforeAllCallback.beforeAll] is called prior to executing any test methods in a given class, the [ExtensionContext.Store] of the
|
||||
* root extension context is used to look up the [TestDatabaseContext] for the class's declared `groupName`, creating and initialising it
|
||||
* if it does not already exist. This ensures that a [TestDatabaseContext] is created exactly once during each test run for every named
|
||||
* group of tests using this extension. This context will be closed with a call to [ExtensionContext.Store.CloseableResource.close] once
|
||||
* the test run completes, tearing down the database state created at the beginning.
|
||||
*
|
||||
* For each test suite and test instance, this extension looks at the corresponding class or method to see if it is annotated with
|
||||
* [RequiresSql] (or any annotations which are themselves annotated with [RequiresSql]), indicating that further SQL setup/teardown is required
|
||||
* around the current scope. Calls are then made to [TestDatabaseContext.beforeClass], [TestDatabaseContext.beforeTest],
|
||||
* [TestDatabaseContext.afterTest] and [TestDatabaseContext.afterClass], passing through the names of any SQL scripts to be run.
|
||||
*
|
||||
* (Note that the same name is used for setup and teardown, and it is up to the [TestDatabaseContext] to map this to the appropriate SQL
|
||||
* script for each case).
|
||||
*/
|
||||
class DBRunnerExtension : Extension, BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
override fun beforeAll(context: ExtensionContext?) {
|
||||
val required = context?.testClass?.orElse(null)?.requiredSql ?: return
|
||||
getDatabaseContext(context)?.beforeTest(required)
|
||||
}
|
||||
|
||||
override fun afterAll(context: ExtensionContext?) {
|
||||
val required = context?.testClass?.orElse(null)?.requiredSql ?: return
|
||||
getDatabaseContext(context)?.afterClass(required.asReversed())
|
||||
}
|
||||
|
||||
override fun beforeEach(context: ExtensionContext?) {
|
||||
val required = context?.testMethod?.orElse(null)?.requiredSql ?: return
|
||||
getDatabaseContext(context)?.beforeTest(required)
|
||||
}
|
||||
|
||||
override fun afterEach(context: ExtensionContext?) {
|
||||
val required = context?.testMethod?.orElse(null)?.requiredSql ?: return
|
||||
getDatabaseContext(context)?.afterTest(required.asReversed())
|
||||
}
|
||||
|
||||
private fun getDatabaseContext(context: ExtensionContext?): TestDatabaseContext? {
|
||||
val rootContext = context?.root ?: return null
|
||||
|
||||
val testClass = context.testClass.orElse(null) ?: return null
|
||||
val annotation = testClass.requiredDb ?:
|
||||
throw IllegalStateException("Test run with DBRunnerExtension is not annotated with @RequiresDb")
|
||||
val groupName = annotation.group
|
||||
val defaultContextClassName = annotation.defaultContextClassName
|
||||
|
||||
val store = rootContext.getStore(ExtensionContext.Namespace.create(DBRunnerExtension::class.java.simpleName, groupName))
|
||||
return store.getOrComputeIfAbsent(
|
||||
TestDatabaseContext::class.java.simpleName,
|
||||
{ createDatabaseContext(groupName, defaultContextClassName) },
|
||||
TestDatabaseContext::class.java)
|
||||
}
|
||||
|
||||
private fun createDatabaseContext(groupName: String, defaultContextClassName: String): TestDatabaseContext {
|
||||
val className = System.getProperty("test.db.context") ?: defaultContextClassName
|
||||
|
||||
val ctx = Class.forName(className).newInstance() as TestDatabaseContext
|
||||
ctx.initialize(groupName)
|
||||
return ctx
|
||||
}
|
||||
|
||||
private val Class<*>.requiredDb: RequiresDb? get() = findAnnotations(RequiresDb::class.java).firstOrNull()
|
||||
private val AnnotatedElement.requiredSql: List<String> get() = findAnnotations(RequiresSql::class.java).map { it.name }.toList()
|
||||
|
||||
private fun <T : Any> AnnotatedElement.findAnnotations(annotationClass: Class<T>): Sequence<T> = declaredAnnotations.asSequence()
|
||||
.filterNot { it.isInternal }
|
||||
.flatMap { annotation ->
|
||||
if (annotationClass.isAssignableFrom(annotation::class.java))sequenceOf(annotationClass.cast(annotation))
|
||||
else annotation.annotationClass.java.findAnnotations(annotationClass)
|
||||
}
|
||||
|
||||
private val Annotation.isInternal: Boolean get() = annotationClass.java.name.run {
|
||||
startsWith("java.lang") ||
|
||||
startsWith("org.junit") ||
|
||||
startsWith("kotlin")
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
/**
|
||||
* An implementation of [TestDatabaseContext] which does nothing.
|
||||
*/
|
||||
class NoOpTestDatabaseContext : TestDatabaseContext {
|
||||
|
||||
override fun initialize(groupName: String) {}
|
||||
|
||||
override fun beforeClass(setupSql: List<String>) {}
|
||||
|
||||
override fun afterClass(teardownSql: List<String>) {}
|
||||
|
||||
override fun beforeTest(setupSql: List<String>) {}
|
||||
|
||||
override fun afterTest(teardownSql: List<String>) {}
|
||||
|
||||
override fun close() {}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
/**
|
||||
* An annotation which is applied to test classes to indicate that they belong to a group of tests which require a common database
|
||||
* environment, which is initialized before any of the tests in any of the classes in that group are run, and cleaned up after all of them
|
||||
* have completed.
|
||||
*
|
||||
* @param group The name of the group of tests to which the annotated test belongs, or `"default"` if unstated.
|
||||
* @param defaultContextClassName The class name of the [TestDatabaseContext] which should be instantiated to manage the database
|
||||
* environment for these tests, if none is given in the system property `test.db.context./groupName/`. This defaults to the class name of
|
||||
* [NoOpTestDatabaseContext].
|
||||
*/
|
||||
@ExtendWith(DBRunnerExtension::class)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class RequiresDb(
|
||||
val group: String = "default",
|
||||
val defaultContextClassName: String = "net.corda.testing.internal.db.NoOpTestDatabaseContext")
|
||||
|
||||
/**
|
||||
* An annotation which is applied to test classes and methods to indicate that the corresponding test suite / instance requires SQL scripts
|
||||
* to be run against its database as part of its setup / teardown.
|
||||
*
|
||||
* @param name The name of the SQL script to run. The same name will be used for setup and teardown: it is up to the [TestDatabaseContext] to
|
||||
* select the actual SQL script based on the context, e.g. `"specialSql"` may be translated to `"/groupName/-specialSql-setup.sql"` or to
|
||||
* `"/groupName/-specialSql-teardown.sql"` depending on which operation is being performed.
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
annotation class RequiresSql(val name: String)
|
@ -0,0 +1,66 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
|
||||
/**
|
||||
* Interface which must be implemented by any class offering to manage test database environments for tests annotated with [RequiresDb],
|
||||
* or with annotations which are themselves annotated with [@RequiresDb].
|
||||
*
|
||||
* A separate instance of [TestDatabaseContext] will be created and initialised for each group of tests, identified by [RequiresDb.group].
|
||||
*
|
||||
* Once all tests in the group have been run, [ExtensionContext.Store.CloseableResource.close] will be called; implementations should use
|
||||
* this method to tear down the database context.
|
||||
*/
|
||||
interface TestDatabaseContext : ExtensionContext.Store.CloseableResource {
|
||||
|
||||
companion object {
|
||||
private val _usingRemoteDatabase = ThreadLocal<Boolean>()
|
||||
|
||||
/**
|
||||
* A flag that an instantiating class can set to indicate to tests that a remote database is in use.
|
||||
*/
|
||||
var usingRemoteDatabase: Boolean
|
||||
get() = _usingRemoteDatabase.get() ?: false
|
||||
set(value) = _usingRemoteDatabase.set(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once when the context is first instantiated, i.e. at the start of the test run, before any tests at all have been executed.
|
||||
*
|
||||
* @param groupName The name of the group of tests whose database environment is to be managed by this context, as indicated by a
|
||||
* [RequiresDb] annotation (or annotations which are themselves annotated with [@RequiresDb]) on each test class in this group.
|
||||
*/
|
||||
fun initialize(groupName: String)
|
||||
|
||||
/**
|
||||
* Called once before a suite of tests is executed.
|
||||
*
|
||||
* @param setupSql The names of any SQL scripts to be run prior to running the suite of tests, as indicated by a [RequiresSql] annotation
|
||||
* (or annotations which are themselves annotated with [RequiresSql]), on the class containing the test suite. May be empty.
|
||||
*/
|
||||
fun beforeClass(setupSql: List<String>)
|
||||
|
||||
/**
|
||||
* Called once after a suite of tests is executed.
|
||||
*
|
||||
* @param teardownSql The names of any SQL scripts to be run after running the suite of tests, as indicated by a [RequiresSql] annotation
|
||||
* (or annotations which are themselves annotated with [RequiresSql]), on the class containing the test suite. May be empty.
|
||||
*/
|
||||
fun afterClass(teardownSql: List<String>)
|
||||
|
||||
/**
|
||||
* Called once before a given test is executed.
|
||||
*
|
||||
* @param setUpSql The names of any SQL scripts to be run before running the test, as indicated by a [RequiresSql] annotation
|
||||
* (or annotations which are themselves annotated with [RequiresSql]), on the method defining the test. May be empty.
|
||||
*/
|
||||
fun beforeTest(setupSql: List<String>)
|
||||
|
||||
/**
|
||||
* Called once after a given test is executed.
|
||||
*
|
||||
* @param teardownSql The names of any SQL scripts to be run after running the test, as indicated by a [RequiresSql] annotation
|
||||
* (or annotations which are themselves annotated with [RequiresSql]), on the method defining the test. May be empty.
|
||||
*/
|
||||
fun afterTest(teardownSql: List<String>)
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class AssertingTestDatabaseContext : TestDatabaseContext {
|
||||
|
||||
companion object {
|
||||
private val logger: Logger = LoggerFactory.getLogger(AssertingTestDatabaseContext::class.java)
|
||||
private val expectations = mutableMapOf<String, List<String>>()
|
||||
|
||||
fun addExpectations(groupName: String, vararg scripts: String) {
|
||||
expectations.compute(groupName) { _, expected ->
|
||||
(expected ?: emptyList()) + scripts.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var groupName: String
|
||||
private val scriptsRun = mutableListOf<String>()
|
||||
|
||||
override fun initialize(groupName: String) {
|
||||
this.groupName = groupName
|
||||
scriptsRun += "${groupName}-db-setup.sql"
|
||||
}
|
||||
|
||||
override fun beforeClass(setupSql: List<String>) {
|
||||
scriptsRun += setupSql.map { "$groupName-$it-setup.sql" }
|
||||
}
|
||||
|
||||
override fun afterClass(teardownSql: List<String>) {
|
||||
scriptsRun += teardownSql.map { "$groupName-$it-teardown.sql" }
|
||||
}
|
||||
|
||||
override fun beforeTest(setupSql: List<String>) {
|
||||
scriptsRun += setupSql.map { "$groupName-$it-setup.sql" }
|
||||
}
|
||||
|
||||
override fun afterTest(teardownSql: List<String>) {
|
||||
scriptsRun += teardownSql.map { "$groupName-$it-teardown.sql" }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
scriptsRun += "${groupName}-db-teardown.sql"
|
||||
|
||||
logger.info("SQL scripts run for group $groupName:\n" + scriptsRun.joinToString("\n"))
|
||||
|
||||
val expectedScripts = (listOf("db-setup") + (expectations[groupName] ?: emptyList()) + listOf("db-teardown"))
|
||||
.map { "$groupName-$it.sql" }
|
||||
.toTypedArray()
|
||||
|
||||
try {
|
||||
assertThat(scriptsRun).containsExactlyInAnyOrder(*expectedScripts)
|
||||
} catch (e: AssertionError) {
|
||||
throw IllegalStateException("Assertion failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@GroupA
|
||||
class GroupAMoreTests {
|
||||
|
||||
@Test
|
||||
fun setExpectations() {
|
||||
AssertingTestDatabaseContext.addExpectations("groupA",
|
||||
"specialSql1-setup", "specialSql2-setup", "specialSql2-teardown", "specialSql1-teardown")
|
||||
}
|
||||
|
||||
@Test
|
||||
@SpecialSql1
|
||||
@SpecialSql2
|
||||
fun moreSpecialSqlRequired() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@RequiresDb("groupA", "net.corda.testing.internal.db.AssertingTestDatabaseContext")
|
||||
@GroupASql
|
||||
class GroupATests {
|
||||
|
||||
@Test
|
||||
fun setExpectations() {
|
||||
AssertingTestDatabaseContext.addExpectations("groupA",
|
||||
"forClassGroupATests-setup", "specialSql1-setup", "specialSql1-teardown", "forClassGroupATests-teardown")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noSpecialSqlRequired() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@SpecialSql1
|
||||
fun someSpecialSqlRequired() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@GroupB
|
||||
class GroupBTests {
|
||||
|
||||
@Test
|
||||
fun setExpectations() {
|
||||
AssertingTestDatabaseContext.addExpectations("groupB",
|
||||
"forClassGroupBTests-setup", "specialSql1-setup", "specialSql1-teardown", "forClassGroupBTests-teardown")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noSpecialSqlRequired() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@SpecialSql1
|
||||
fun someSpecialSqlRequired() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.corda.testing.internal.db
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@RequiresDb("groupA", "net.corda.testing.internal.db.AssertingTestDatabaseContext")
|
||||
annotation class GroupA
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@RequiresDb("groupB", "net.corda.testing.internal.db.AssertingTestDatabaseContext")
|
||||
@RequiresSql("forClassGroupBTests")
|
||||
annotation class GroupB
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
@RequiresSql("specialSql1")
|
||||
annotation class SpecialSql1
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
@RequiresSql("specialSql2")
|
||||
annotation class SpecialSql2
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
@RequiresSql("forClassGroupATests")
|
||||
annotation class GroupASql
|
89
testing/test-db/src/test/resources/log4j2-test.xml
Normal file
89
testing/test-db/src/test/resources/log4j2-test.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info" packages="net.corda.common.logging">
|
||||
|
||||
<Properties>
|
||||
<Property name="log-path">${sys:log-path:-logs}</Property>
|
||||
<Property name="log-name">node-${hostName}</Property>
|
||||
<Property name="archive">${log-path}/archive</Property>
|
||||
<Property name="defaultLogLevel">${sys:defaultLogLevel:-info}</Property>
|
||||
</Properties>
|
||||
|
||||
<ThresholdFilter level="trace"/>
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}">
|
||||
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||
result = null;
|
||||
if (!logEvent.getContextData().size() == 0) {
|
||||
result = "WithMDC";
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
result;
|
||||
]]>
|
||||
</Script>
|
||||
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg %X%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
</ScriptPatternSelector>
|
||||
</PatternLayout>
|
||||
<ThresholdFilter level="trace"/>
|
||||
</Console>
|
||||
|
||||
<!-- Required for printBasicInfo -->
|
||||
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%msg%n" />
|
||||
</Console>
|
||||
|
||||
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
|
||||
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||
<RollingRandomAccessFile name="RollingFile-Appender"
|
||||
fileName="${log-path}/${log-name}.log"
|
||||
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
|
||||
<PatternLayout pattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg %X%n"/>
|
||||
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
<SizeBasedTriggeringPolicy size="10MB"/>
|
||||
</Policies>
|
||||
|
||||
<DefaultRolloverStrategy min="1" max="100">
|
||||
<Delete basePath="${archive}" maxDepth="1">
|
||||
<IfFileName glob="${log-name}*.log.gz"/>
|
||||
<IfLastModified age="60d">
|
||||
<IfAny>
|
||||
<IfAccumulatedFileSize exceeds="10 GB"/>
|
||||
</IfAny>
|
||||
</IfLastModified>
|
||||
</Delete>
|
||||
</DefaultRolloverStrategy>
|
||||
|
||||
</RollingRandomAccessFile>
|
||||
|
||||
<Rewrite name="Console-ErrorCode-Appender">
|
||||
<AppenderRef ref="Console-Appender"/>
|
||||
<ErrorCodeRewritePolicy/>
|
||||
</Rewrite>
|
||||
|
||||
<Rewrite name="Console-ErrorCode-Appender-Println">
|
||||
<AppenderRef ref="Console-Appender-Println"/>
|
||||
<ErrorCodeRewritePolicy/>
|
||||
</Rewrite>
|
||||
|
||||
<Rewrite name="RollingFile-ErrorCode-Appender">
|
||||
<AppenderRef ref="RollingFile-Appender"/>
|
||||
<ErrorCodeRewritePolicy/>
|
||||
</Rewrite>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
</Root>
|
||||
<Logger name="net.corda" level="${defaultLogLevel}" additivity="false">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
<AppenderRef ref="RollingFile-ErrorCode-Appender" />
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
Loading…
Reference in New Issue
Block a user