parent
a24c07d000
commit
8a7219b637
|
@ -6,6 +6,9 @@
|
|||
<LanguageOptions name="Properties">
|
||||
<option name="fileTypeOverride" value="1" />
|
||||
</LanguageOptions>
|
||||
<LanguageOptions name="XML">
|
||||
<option name="fileTypeOverride" value="1" />
|
||||
</LanguageOptions>
|
||||
<LanguageOptions name="__TEMPLATE__">
|
||||
<option name="block" value="false" />
|
||||
</LanguageOptions>
|
||||
|
|
|
@ -54,6 +54,7 @@ pipeline {
|
|||
gradlew,
|
||||
gradle/wrapper/*,
|
||||
templates/build.gradle,
|
||||
templates/module.logback-test.xml,
|
||||
config/**,
|
||||
facades/PC/build/distributions/Terasology.zip,
|
||||
engine/build/resources/main/org/terasology/engine/version/versionInfo.properties,
|
||||
|
@ -76,6 +77,9 @@ pipeline {
|
|||
//
|
||||
// See https://docs.gradle.org/current/userguide/java_testing.html#test_reporting
|
||||
junit testResults: '**/build/test-results/unitTest/*.xml'
|
||||
// Jenkins truncates large test outputs, so archive it as well.
|
||||
tar file: 'build/unitTest-results.tgz', archive: true, compress: true, overwrite: true,
|
||||
glob: '**/build/test-results/unitTest/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +169,9 @@ pipeline {
|
|||
//
|
||||
// See https://docs.gradle.org/current/userguide/java_testing.html#test_reporting
|
||||
junit testResults: '**/build/test-results/integrationTest/*.xml', allowEmptyResults: true
|
||||
// Jenkins truncates large test outputs, so archive it as well.
|
||||
tar file: 'build/integrationTest-results.tgz', archive: true, compress: true, overwrite: true,
|
||||
glob: '**/build/test-results/integrationTest/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,29 @@ plugins {
|
|||
dependencies {
|
||||
"pmd"("net.sourceforge.pmd:pmd-core:6.15.0")
|
||||
"pmd"("net.sourceforge.pmd:pmd-java:6.15.0")
|
||||
|
||||
testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") {
|
||||
because("runtime: to configure logging during tests with logback.xml")
|
||||
}
|
||||
testRuntimeOnly("org.codehaus.janino:janino:3.1.7") {
|
||||
because("allows use of EvaluatorFilter in logback.xml")
|
||||
}
|
||||
testRuntimeOnly("org.slf4j:jul-to-slf4j:1.7.36") {
|
||||
because("redirects java.util.logging (from e.g. junit) through slf4j")
|
||||
}
|
||||
|
||||
add("testImplementation", platform("org.junit:junit-bom:5.8.1"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
|
||||
testImplementation("org.mockito:mockito-inline:3.12.4")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter:3.12.4")
|
||||
|
||||
testImplementation("com.google.truth:truth:1.1.3")
|
||||
testImplementation("com.google.truth.extensions:truth-java8-extension:1.1.3")
|
||||
|
||||
testImplementation("io.projectreactor:reactor-test:3.4.14")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
|
@ -24,7 +47,8 @@ tasks.withType<Test> {
|
|||
// ignoreFailures: Specifies whether the build should break when the verifications performed by this task fail.
|
||||
ignoreFailures = true
|
||||
// showStandardStreams: makes the standard streams (err and out) visible at console when running tests
|
||||
testLogging.showStandardStreams = true
|
||||
// If false, the outputs are still collected and visible in the test report, but they don't spam the console.
|
||||
testLogging.showStandardStreams = false
|
||||
reports {
|
||||
junitXml.isEnabled = true
|
||||
}
|
||||
|
|
|
@ -67,18 +67,7 @@ dependencies {
|
|||
}
|
||||
}
|
||||
|
||||
testRuntimeOnly("org.slf4j:jul-to-slf4j:1.7.21")
|
||||
|
||||
add("testImplementation", platform("org.junit:junit-bom:5.8.1"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
|
||||
testImplementation("org.mockito:mockito-inline:3.12.4")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter:3.12.4")
|
||||
|
||||
testImplementation("com.google.truth:truth:1.1.3")
|
||||
testImplementation("com.google.truth.extensions:truth-java8-extension:1.1.3")
|
||||
// see terasology-metrics for test dependencies
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
/*
|
||||
* Copyright 2020 MovingBlocks
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Copyright 2022 The Terasology Foundation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
class module {
|
||||
def excludedItems = ["engine", "Index", "out", "build"]
|
||||
|
||||
|
@ -58,12 +50,6 @@ class module {
|
|||
}
|
||||
|
||||
def copyInTemplateFiles(File targetDir) {
|
||||
// Copy in the template build.gradle for modules
|
||||
println "In copyInTemplateFiles for module $targetDir.name - copying in a build.gradle then next checking for module.txt"
|
||||
File targetBuildGradle = new File(targetDir, 'build.gradle')
|
||||
targetBuildGradle.delete()
|
||||
targetBuildGradle << new File('templates/build.gradle').text
|
||||
|
||||
// Copy in the template module.txt for modules (if one doesn't exist yet)
|
||||
File moduleManifest = new File(targetDir, 'module.txt')
|
||||
if (!moduleManifest.exists()) {
|
||||
|
@ -73,8 +59,9 @@ class module {
|
|||
println "WARNING: the module ${targetDir.name} did not have a module.txt! One was created, please review and submit to GitHub"
|
||||
}
|
||||
|
||||
refreshGradle(targetDir)
|
||||
|
||||
// TODO: Copy in a module readme template soon
|
||||
// TODO : Add in the logback.groovy from engine\src\test\resources\logback.groovy ? Local dev only, Jenkins will use the one inside engine-tests.jar. Also add to .gitignore
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,14 +81,28 @@ class module {
|
|||
}
|
||||
|
||||
def refreshGradle(File targetDir) {
|
||||
// Copy in the template build.gradle for modules
|
||||
if (!new File(targetDir, "module.txt").exists()) {
|
||||
println "$targetDir has no module.txt, it must not want a fresh build.gradle"
|
||||
if (!(targetDir.canRead() && targetDir.canWrite())) {
|
||||
println "$targetDir: ⛔ not accessible"
|
||||
return
|
||||
}
|
||||
println "In refreshGradle for module $targetDir - copying in a fresh build.gradle"
|
||||
File targetBuildGradle = new File(targetDir, 'build.gradle')
|
||||
targetBuildGradle.delete()
|
||||
targetBuildGradle << new File('templates/build.gradle').text
|
||||
Path targetPath = targetDir.toPath()
|
||||
if (Files.notExists(targetPath.resolve('module.txt'))) {
|
||||
println "$targetDir/module.txt: ❓ not present, it must not want a fresh build.gradle"
|
||||
return
|
||||
}
|
||||
|
||||
Path templates = Path.of('templates')
|
||||
Files.copy(templates.resolve('build.gradle'), targetPath.resolve('build.gradle'),
|
||||
StandardCopyOption.REPLACE_EXISTING)
|
||||
println "$targetDir/build.gradle: ✨ refreshed"
|
||||
|
||||
Path logbackXml = targetPath.resolve('src/test/resources/logback-test.xml')
|
||||
if (Files.notExists(logbackXml)) {
|
||||
Files.createDirectories(logbackXml.parent)
|
||||
Files.copy(templates.resolve('module.logback-test.xml'), logbackXml)
|
||||
println "$logbackXml: ✨ added"
|
||||
} else {
|
||||
println "$logbackXml: already there"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,30 +58,31 @@ dependencies {
|
|||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||
implementation group: 'org.codehaus.plexus', name: 'plexus-utils', version: '3.0.16'
|
||||
implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.16.1'
|
||||
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.11'
|
||||
runtimeOnly('org.codehaus.janino:janino:3.1.7') {
|
||||
because("logback filters")
|
||||
}
|
||||
runtimeOnly group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.21'
|
||||
implementation "org.terasology:reflections:0.9.12-MB"
|
||||
|
||||
implementation("org.terasology.joml-ext:joml-test:0.1.0")
|
||||
|
||||
implementation('org.slf4j:slf4j-api:1.7.36') {
|
||||
because('a backend-independent Logger')
|
||||
}
|
||||
|
||||
testImplementation("ch.qos.logback:logback-classic:1.2.11") {
|
||||
because("implementation: a test directly uses logback.classic classes")
|
||||
}
|
||||
|
||||
|
||||
// Test lib dependencies
|
||||
implementation(platform("org.junit:junit-bom:5.8.1")) {
|
||||
// junit-bom will set version numbers for the other org.junit dependencies.
|
||||
}
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testImplementation('org.mockito:mockito-inline:3.12.4')
|
||||
api("org.junit.jupiter:junit-jupiter-api") {
|
||||
because("we export jupiter Extensions for module tests")
|
||||
}
|
||||
implementation("org.mockito:mockito-inline:3.12.4") {
|
||||
because("classes like HeadlessEnvironment use mocks")
|
||||
}
|
||||
|
||||
implementation('org.mockito:mockito-junit-jupiter:3.12.4')
|
||||
|
||||
implementation("org.terasology.joml-ext:joml-test:0.1.0")
|
||||
|
||||
testImplementation('com.google.truth:truth:1.1.3')
|
||||
testImplementation('com.google.truth.extensions:truth-java8-extension:1.1.3')
|
||||
|
||||
implementation("io.projectreactor:reactor-test:3.4.14")
|
||||
// See terasology-metrics for other test-only internal dependencies
|
||||
}
|
||||
|
||||
task copyResourcesToClasses(type:Copy) {
|
||||
|
|
|
@ -3,20 +3,14 @@
|
|||
|
||||
package org.terasology.engine.integrationenvironment.jupiter;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import ch.qos.logback.core.util.StatusPrinter;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.ParameterContext;
|
||||
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||
import org.junit.jupiter.api.extension.ParameterResolver;
|
||||
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
|
||||
import org.opentest4j.MultipleFailuresError;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.terasology.engine.integrationenvironment.Engines;
|
||||
import org.terasology.engine.integrationenvironment.MainLoop;
|
||||
import org.terasology.engine.integrationenvironment.ModuleTestingHelper;
|
||||
|
@ -24,8 +18,6 @@ import org.terasology.engine.integrationenvironment.Scopes;
|
|||
import org.terasology.engine.registry.In;
|
||||
import org.terasology.unittest.worlds.DummyWorldGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
@ -85,24 +77,12 @@ import java.util.function.Function;
|
|||
* want isolated engine instances try {@link IsolatedMTEExtension}.
|
||||
* <p>
|
||||
* Note that classes marked {@link Nested} will share the engine context with their parent.
|
||||
* <p>
|
||||
* This will configure the logger and the current implementation is not subtle or polite about it, see
|
||||
* {@link #setupLogging()} for notes.
|
||||
*/
|
||||
public class MTEExtension implements BeforeAllCallback, ParameterResolver, TestInstancePostProcessor {
|
||||
public class MTEExtension implements ParameterResolver, TestInstancePostProcessor {
|
||||
|
||||
static final String LOGBACK_RESOURCE = "default-logback.xml";
|
||||
protected Function<ExtensionContext, ExtensionContext.Namespace> helperLifecycle = Scopes.PER_CLASS;
|
||||
protected Function<ExtensionContext, Class<?>> getTestClass = Scopes::getTopTestClass;
|
||||
|
||||
@Override
|
||||
public void beforeAll(ExtensionContext context) {
|
||||
if (context.getRequiredTestClass().isAnnotationPresent(Nested.class)) {
|
||||
return; // nested classes get set up in the parent
|
||||
}
|
||||
setupLogging();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
|
||||
Class<?> type = parameterContext.getParameter().getType();
|
||||
|
@ -190,41 +170,6 @@ public class MTEExtension implements BeforeAllCallback, ParameterResolver, TestI
|
|||
return autoCleaner.engines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply our default logback configuration to the logger.
|
||||
* <p>
|
||||
* Modules won't generally have their own logback-test.xml, so we'll install ours from {@value LOGBACK_RESOURCE}.
|
||||
* <p>
|
||||
* <b>TODO:</b>
|
||||
* <ul>
|
||||
* <li>Only reset the current LoggerContext if it really hasn't been customized by elsewhere.
|
||||
* <li>When there are multiple classes with MTEExtension, do we end up doing this repeatedly
|
||||
* in the same process?
|
||||
* <li>Provide a way to add/change/override what this is doing that doesn't require checking
|
||||
* out the MTE sources and editing default-logback.xml.
|
||||
* </ul>
|
||||
*/
|
||||
void setupLogging() {
|
||||
// This is mostly right out of the book:
|
||||
// http://logback.qos.ch/xref/chapters/configuration/MyApp3.html
|
||||
JoranConfigurator cfg = new JoranConfigurator();
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.reset();
|
||||
cfg.setContext(context);
|
||||
try (InputStream i = getClass().getResourceAsStream(LOGBACK_RESOURCE)) {
|
||||
if (i == null) {
|
||||
throw new RuntimeException("Failed to find " + LOGBACK_RESOURCE);
|
||||
}
|
||||
cfg.doConfigure(i);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error reading " + LOGBACK_RESOURCE, e);
|
||||
} catch (JoranException e) {
|
||||
throw new RuntimeException("Error during logger configuration", e);
|
||||
} finally {
|
||||
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages Engines for storage in an ExtensionContext.
|
||||
* <p>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<!-- Copyright 2022 The Terasology Foundation -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
|
||||
<configuration>
|
||||
|
||||
<!-- Report all changes to this configuration on System.out -->
|
||||
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
|
||||
<evaluator>
|
||||
<matcher>
|
||||
<Name>reflectionsEmpty</Name>
|
||||
<!-- filter out odd numbered statements -->
|
||||
<regex>given scan urls are empty</regex>
|
||||
</matcher>
|
||||
|
||||
<expression>reflectionsEmpty.matches(message)</expression>
|
||||
</evaluator>
|
||||
<OnMismatch>NEUTRAL</OnMismatch>
|
||||
<OnMatch>DENY</OnMatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<root level="${logOverrideLevel:-info}">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
|
||||
<logger name="org.terasology" level="${logOverrideLevel:-info}" />
|
||||
<logger name="org.terasology.engine.i18n" level="warn" />
|
||||
<logger name="org.reflections.Reflections" level="${logOverrideLevel:-warn}" />
|
||||
|
||||
</configuration>
|
|
@ -1,8 +1,8 @@
|
|||
junit.jupiter.testinstance.lifecycle.default=per_method
|
||||
|
||||
# Parallel execution params:
|
||||
junit.jupiter.execution.parallel.enabled=false
|
||||
junit.jupiter.execution.parallel.enabled=false
|
||||
junit.jupiter.execution.parallel.mode.default=SAME_THREAD
|
||||
junit.jupiter.execution.parallel.mode.classes.default=SAME_THREAD
|
||||
|
||||
#junit.jupiter.execution.parallel.config.fixed.parallelism=4
|
||||
#junit.jupiter.execution.parallel.config.fixed.parallelism=4
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
<!-- Copyright 2021 The Terasology Foundation -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
<configuration debug="false">
|
||||
<!-- debug: reports all changes to this configuration on System.out -->
|
||||
|
||||
<configuration>
|
||||
|
||||
<!-- Report all changes to this configuration on System.out -->
|
||||
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
|
||||
<include resource="org/terasology/engine/logback/setup.xml" />
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
<include resource="org/terasology/engine/logback/filter-reflections.xml" />
|
||||
</appender>
|
||||
|
||||
<!-- JUnit collects console output during test execution
|
||||
and include it in the report for the test. -->
|
||||
<root level="${logOverrideLevel:-debug}">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
|
||||
<logger name="org.terasology" level="${logOverrideLevel:-info}" />
|
||||
<logger name="org.terasology.engine.i18n" level="warn" />
|
||||
<logger name="org.reflections.Reflections" level="warn" />
|
||||
|
||||
</configuration>
|
|
@ -112,8 +112,16 @@ dependencies {
|
|||
api group: 'com.miglayout', name: 'miglayout-core', version: '5.0'
|
||||
implementation group: 'de.matthiasmann.twl', name: 'PNGDecoder', version: '1111'
|
||||
|
||||
// Logging and audio
|
||||
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
|
||||
// Logging
|
||||
implementation('org.slf4j:slf4j-api:1.7.36') {
|
||||
because('a backend-independent Logger')
|
||||
}
|
||||
implementation("ch.qos.logback:logback-classic:1.2.11") {
|
||||
because("telemetry implementation uses logback to send to logstash " +
|
||||
"and we bundle org.terasology.logback for the regex filter")
|
||||
}
|
||||
|
||||
// audio
|
||||
implementation group: 'com.projectdarkstar.ext.jorbis', name: 'jorbis', version: '0.0.17'
|
||||
|
||||
// Small-time 3rd party libs we've stored in our Artifactory for access
|
||||
|
@ -144,9 +152,6 @@ dependencies {
|
|||
// Wildcard dependency to catch any libs provided with the project (remote repo preferred instead)
|
||||
api fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.11'
|
||||
runtimeOnly group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.21'
|
||||
|
||||
// TODO: Consider moving this back to the PC Facade instead of having the engine rely on it?
|
||||
implementation group: 'org.terasology.crashreporter', name: 'cr-terasology', version: '4.2.0'
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2022 The Terasology Foundation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.terasology.logback;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.boolex.EventEvaluatorBase;
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Evaluate messages with a regular expression.
|
||||
*
|
||||
* @see RegexFilterAction
|
||||
*/
|
||||
public class RegexEvaluator<E extends ILoggingEvent> extends EventEvaluatorBase<E> {
|
||||
final Pattern prefix;
|
||||
final Pattern matchMessage;
|
||||
|
||||
private final Predicate<String> prefixTest;
|
||||
private final Predicate<String> messageTest;
|
||||
|
||||
/**
|
||||
* @param prefix Test the event's logger name against this pattern.
|
||||
* May be null to apply to all loggers.
|
||||
*
|
||||
* @param matchMessage Test the event's formatted message against this pattern.
|
||||
*/
|
||||
public RegexEvaluator(Pattern prefix, Pattern matchMessage) {
|
||||
this.prefix = prefix;
|
||||
this.matchMessage = matchMessage;
|
||||
|
||||
prefixTest = (prefix == null) ? s -> true : prefix.asPredicate();
|
||||
messageTest = checkNotNull(matchMessage).asPredicate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param prefixString Test the event's logger name against this prefix. This input is a
|
||||
* simple string, <em>not</em> a regex pattern. May be null to apply to all loggers.
|
||||
*
|
||||
* @param messagePattern A regular expression {@linkplain Pattern pattern} to match against the
|
||||
* {@linkplain ILoggingEvent#getFormattedMessage() formatted message} of a logging event.
|
||||
*/
|
||||
public RegexEvaluator(String prefixString, String messagePattern) {
|
||||
this(prefixString == null ? null : Pattern.compile("^" + Pattern.quote(prefixString)),
|
||||
Pattern.compile(checkNotNull(messagePattern)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(E event) {
|
||||
return prefixTest.test(event.getLoggerName()) && messageTest.test(event.getFormattedMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("prefix", prefix)
|
||||
.add("matchMessage", matchMessage)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2022 The Terasology Foundation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.terasology.logback;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Context;
|
||||
import ch.qos.logback.core.boolex.EventEvaluator;
|
||||
import ch.qos.logback.core.filter.EvaluatorFilter;
|
||||
import ch.qos.logback.core.joran.action.Action;
|
||||
import ch.qos.logback.core.joran.spi.ActionException;
|
||||
import ch.qos.logback.core.joran.spi.InterpretationContext;
|
||||
import ch.qos.logback.core.spi.FilterAttachable;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Regex-based logback filter with concise syntax.
|
||||
* <p>
|
||||
* <b>Example:</b>
|
||||
* <p><code>
|
||||
* <denyRegex prefix="org.reflections" message="given scan \w+ are empty" />
|
||||
* </code>
|
||||
* <dl>
|
||||
* <dt>{@code denyRegex}</dt>
|
||||
* <dd>The tag starts with {@code "deny"}, meaning events matching this filter will be dropped.</dd>
|
||||
*
|
||||
* <dt>{@code prefix} <i>(optional)</i></dt>
|
||||
* <dd>This filter will only test events from loggers with names starting {@code "org.reflections"}.<br>
|
||||
* It is {@linkplain FilterReply#NEUTRAL neutral} on other events.</dd>
|
||||
*
|
||||
* <dt>{@code message}</dt>
|
||||
* <dd>Match the {@linkplain ILoggingEvent#getFormattedMessage() formatted message} against
|
||||
* the regular expression {@linkplain java.util.regex.Pattern pattern}
|
||||
* {@code /given scan \w+ are empty/}.</dd>
|
||||
* </dl>
|
||||
* <p>
|
||||
* <b>Example:</b>
|
||||
* <p><code>
|
||||
* <requireRegex message="module:.*-SNAPSHOT" />
|
||||
* </code>
|
||||
* <dl>
|
||||
* <dt>{@code requireRegex}</dt>
|
||||
* <dd>The tag starts with {@code "require"}, meaning events <em>must</em> match the pattern.</dd>
|
||||
*
|
||||
* <dt>{@code message}</dt>
|
||||
* <dd>Match the {@linkplain ILoggingEvent#getFormattedMessage() formatted message} against
|
||||
* the regular expression {@linkplain java.util.regex.Pattern pattern}
|
||||
* {@code /module:.*-SNAPSHOT/}.</dd>
|
||||
* </dl>
|
||||
* No Groovy or Janino required.
|
||||
* <p>
|
||||
* This Action adds an {@link EvaluatorFilter} with a {@link RegexEvaluator} to
|
||||
* the current element, such as an {@link ch.qos.logback.core.Appender Appender}.
|
||||
* <p>
|
||||
* The element must be on the top of the stack and must implement {@link FilterAttachable}.
|
||||
* <p>
|
||||
* <b>Implementation Note:</b> In logback 1.2, we can <em>almost</em> define an
|
||||
* EvaluatorFilter using only logback-core, but core defines no LoggingEvent interface.
|
||||
* We have to link against logback-classic for that.
|
||||
*
|
||||
* @param <E> the type of logging event the filter operates on
|
||||
*/
|
||||
public class RegexFilterAction<E extends ILoggingEvent> extends Action {
|
||||
public static final String PREFIX_ATTRIBUTE = "prefix";
|
||||
public static final String MESSAGE_ATTRIBUTE = "message";
|
||||
public static final String DENY_ELEMENT = "deny";
|
||||
public static final String REQUIRE_ELEMENT = "require";
|
||||
|
||||
protected final BiFunction<String, String, EventEvaluator<E>> evaluatorConstructor = RegexEvaluator::new;
|
||||
|
||||
@Override
|
||||
public void begin(InterpretationContext context, String elementName, Attributes attributes) throws ActionException {
|
||||
if (peekFilterContainer(context) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var evaluator = evaluatorConstructor.apply(
|
||||
findAndSubst(context, attributes, PREFIX_ATTRIBUTE),
|
||||
findAndSubst(context, attributes, MESSAGE_ATTRIBUTE)
|
||||
);
|
||||
|
||||
EvaluatorFilter<E> filter = new RegexFilter<>(
|
||||
getContext(),
|
||||
evaluator,
|
||||
makeFilterName(context, attributes));
|
||||
|
||||
if (elementName.startsWith(REQUIRE_ELEMENT)) {
|
||||
filter.setOnMatch(FilterReply.NEUTRAL);
|
||||
filter.setOnMismatch(FilterReply.DENY);
|
||||
} else if (elementName.startsWith(DENY_ELEMENT)) {
|
||||
filter.setOnMatch(FilterReply.DENY);
|
||||
filter.setOnMismatch(FilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
// Push the new filter to the stack, in case anyone wants to configure it further.
|
||||
context.pushObject(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(InterpretationContext context, String name) throws ActionException {
|
||||
Object top = context.peekObject();
|
||||
EvaluatorFilter<E> filter;
|
||||
|
||||
if (top instanceof EvaluatorFilter) {
|
||||
// ignore Java warning about cast from Object to EvaluatorFilter<E>
|
||||
//noinspection unchecked
|
||||
filter = (EvaluatorFilter<E>) top;
|
||||
// Remove the filter from the stack now that the parser is at the end of it.
|
||||
context.popObject();
|
||||
} else {
|
||||
addError("Top of stack does not have Filter, but this thing: " + top);
|
||||
return;
|
||||
}
|
||||
|
||||
// We expect the stack to be back at the parent container now.
|
||||
FilterAttachable<E> filterContainer = peekFilterContainer(context);
|
||||
if (filterContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter.getOnMatch() == FilterReply.NEUTRAL && filter.getOnMismatch() == FilterReply.NEUTRAL) {
|
||||
addError("onMatch and onMismatch are both neutral; this filter is a no-op: " + filter);
|
||||
}
|
||||
|
||||
filterContainer.addFilter(filter);
|
||||
filter.getEvaluator().start();
|
||||
filter.start();
|
||||
addInfo(String.format("Added %s to %s", filter, filterContainer));
|
||||
}
|
||||
|
||||
protected FilterAttachable<E> peekFilterContainer(InterpretationContext context) {
|
||||
Object parentObject = context.peekObject();
|
||||
if (parentObject instanceof FilterAttachable) {
|
||||
@SuppressWarnings("unchecked") FilterAttachable<E> container =
|
||||
(FilterAttachable<E>) parentObject;
|
||||
return container;
|
||||
} else {
|
||||
addError("Cannot attach filter to top of stack item " + parentObject);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected String makeFilterName(InterpretationContext context, Attributes attributes) {
|
||||
String filterName = findAndSubst(context, attributes, NAME_ATTRIBUTE);
|
||||
|
||||
if (Strings.isNullOrEmpty(filterName)) {
|
||||
// If a name wasn't provided, make one from the attributes
|
||||
// as they appear in the source (before variable substitution).
|
||||
filterName = Strings.nullToEmpty(attributes.getValue(PREFIX_ATTRIBUTE)) + ":"
|
||||
+ attributes.getValue(MESSAGE_ATTRIBUTE);
|
||||
}
|
||||
|
||||
return filterName;
|
||||
}
|
||||
|
||||
private String findAndSubst(InterpretationContext context, Attributes attributes, String name) {
|
||||
return context.subst(attributes.getValue(name));
|
||||
}
|
||||
|
||||
public static class RegexFilter<EE> extends EvaluatorFilter<EE> {
|
||||
RegexFilter(Context context, EventEvaluator<EE> evaluator, String name) {
|
||||
super();
|
||||
setName(name);
|
||||
setContext(context);
|
||||
setEvaluator(evaluator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is the whole reason for the subclass: a useful toString()
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.addValue(getName())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2022 The Terasology Foundation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* logback general evaluators and filters
|
||||
* <p>
|
||||
* These are not specific to {@link org.terasology.engine} at all.
|
||||
*/
|
||||
package org.terasology.logback;
|
|
@ -0,0 +1,14 @@
|
|||
<included>
|
||||
<!-- Expects to be included within an <appender>
|
||||
(or something else that can contain filters). -->
|
||||
|
||||
<!-- Requires org.terasology.logback.RegexFilterAction; see setup.xml -->
|
||||
|
||||
<!-- WONTFIX: gestalt routinely builds empty configs and then adds to them later. -->
|
||||
<denyRegex prefix="org.reflections" message="given scan urls are empty" />
|
||||
|
||||
<!-- WONTFIX: these classes don't need to be reflected on,
|
||||
and they're only on the classpath during tests. -->
|
||||
<denyRegex prefix="org.reflections"
|
||||
message="could not get type for name org\.terasology\.benchmark" />
|
||||
</included>
|
|
@ -0,0 +1,19 @@
|
|||
<included>
|
||||
<!-- Expects to be included in the root <configuration> element. -->
|
||||
|
||||
<!-- JUnit (and potentially other libraries) use java.util.logging, aka JUL.
|
||||
The jul.LevelChangePropagator synchronizes that with this configuration.
|
||||
|
||||
See https://www.slf4j.org/legacy.html#jul-to-slf4j
|
||||
-->
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
|
||||
<!-- map <denyRegex> and <requireRegex> xml elements to RegexFilter -->
|
||||
<newRule pattern="*/appender/denyRegex" actionClass="org.terasology.logback.RegexFilterAction" />
|
||||
<newRule pattern="*/appender/requireRegex" actionClass="org.terasology.logback.RegexFilterAction" />
|
||||
|
||||
<!-- allow include inside appenders (logback 1.2.11 only enabled Include on the root config by default.) -->
|
||||
<newRule pattern="*/appender/include" actionClass="ch.qos.logback.core.joran.action.IncludeAction" />
|
||||
</included>
|
|
@ -71,6 +71,16 @@ dependencies {
|
|||
// TODO: Consider whether we can move the CR dependency back here from the engine, where it is referenced from the main menu
|
||||
implementation(group = "org.terasology.crashreporter", name = "cr-terasology", version = "4.2.0")
|
||||
|
||||
runtimeOnly("ch.qos.logback:logback-classic:1.2.11") {
|
||||
because("to configure logging with logback.xml")
|
||||
}
|
||||
runtimeOnly("org.codehaus.janino:janino:3.1.7") {
|
||||
because("allows use of EvaluatorFilter in logback.xml")
|
||||
}
|
||||
runtimeOnly("org.slf4j:jul-to-slf4j:1.7.36") {
|
||||
because("redirects java.util.logging from miscellaneous dependencies through slf4j")
|
||||
}
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.7.1")) {
|
||||
// junit-bom will set version numbers for the other org.junit dependencies.
|
||||
}
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
<!-- Copyright 2021 The Terasology Foundation -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
<configuration debug="false">
|
||||
<!-- debug: reports all changes to this configuration on System.out -->
|
||||
|
||||
<configuration>
|
||||
|
||||
<!-- Report all changes to this configuration on System.out -->
|
||||
<!--statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /-->
|
||||
|
||||
<!-- Propagate all log level changes to java.util.logging -->
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
<include resource="org/terasology/engine/logback/setup.xml" />
|
||||
|
||||
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
|
||||
<!-- in the absence of the class attribute, it is assumed that the
|
||||
desired discriminator type is MDCBasedDiscriminator -->
|
||||
<discriminator>
|
||||
<discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
|
||||
<key>phase</key>
|
||||
<defaultValue>init</defaultValue>
|
||||
</discriminator>
|
||||
|
@ -37,10 +27,11 @@
|
|||
</appender>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
<include resource="org/terasology/engine/logback/filter-reflections.xml" />
|
||||
</appender>
|
||||
|
||||
<root level="${logOverrideLevel:-debug}">
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<!-- This file, when present as src/test/resources/logback-test.xml,
|
||||
configures log filters during test execution.
|
||||
|
||||
To configure the logs generated while in-game, see
|
||||
facades/PC/src/main/resources/logback.xml
|
||||
-->
|
||||
<configuration debug="false">
|
||||
<!-- debug: reports all changes to this configuration on System.out -->
|
||||
|
||||
<include resource="org/terasology/engine/logback/setup.xml" />
|
||||
|
||||
<!-- JUnit collects console output during test execution
|
||||
and include it in the report for the test. -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
<include resource="org/terasology/engine/logback/filter-reflections.xml" />
|
||||
</appender>
|
||||
|
||||
<root level="${logOverrideLevel:-debug}">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
|
||||
<!-- NOTE You can add fine granular custom log-level overrides here.
|
||||
This works in both ways; you can increase or decrease the log level
|
||||
for specific packages or classes.
|
||||
|
||||
For example:
|
||||
|
||||
<logger name="org.terasology.engine.world" level="debug" />
|
||||
<logger name="org.terasology.module.health.systems.HealthAuthoritySystem" level="error" />
|
||||
-->
|
||||
<logger name="org.terasology" level="${logOverrideLevel:-info}" />
|
||||
<logger name="org.terasology.engine.i18n" level="warn" />
|
||||
<logger name="org.reflections.Reflections" level="warn" />
|
||||
|
||||
</configuration>
|
Loading…
Reference in New Issue