Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change how logging works to make configuration easier #119

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dependencies {
testCompileOnly("org.jetbrains:annotations:${jetbrains_ann_version}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${junit_version}")
testImplementation("org.junit.jupiter:junit-jupiter-engine:${junit_version}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${junit_version}")
testImplementation("org.powermock:powermock-core:${powermock_version}")
testImplementation("org.powermock:powermock-reflect:${powermock_version}")
testRuntimeOnly("org.apiguardian:apiguardian-api:${apiguardian_version}")
Expand Down Expand Up @@ -92,7 +93,7 @@ test {
useJUnitPlatform()

systemProperty 'testJars.location', testsJar.archiveFile.get().asFile

jvmArgs(
'--module-path', classpath.asPath,
'--add-modules', 'ALL-MODULE-PATH',
Expand Down
19 changes: 17 additions & 2 deletions src/main/java/cpw/mods/modlauncher/ArgumentHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
package cpw.mods.modlauncher;

import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.util.UriConverter;
import joptsimple.*;
import joptsimple.util.*;
import org.jetbrains.annotations.NotNull;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.*;
import java.util.function.*;
Expand All @@ -37,17 +40,19 @@ public class ArgumentHandler {
private OptionSpec<String> nonOption;
private OptionSpec<String> launchTarget;
private OptionSpec<String> uuidOption;
private OptionSpec<URI> loggingConfigOption;

record DiscoveryData(Path gameDir, String launchTarget, String[] arguments) {}
record DiscoveryData(Path gameDir, String launchTarget, List<URI> loggingConfigs, String[] arguments) {}

DiscoveryData setArgs(String[] args) {
this.args = args;
final OptionParser parser = new OptionParser();
final var gameDir = parser.accepts("gameDir", "Alternative game directory").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)).defaultsTo(Path.of("."));
final var launchTarget = parser.accepts("launchTarget", "LauncherService target to launch").withRequiredArg();
final var loggingConfig = parser.accepts("loggingConfig", "Log4j configuration files to composite together").withOptionalArg().withValuesConvertedBy(new UriConverter()).withValuesSeparatedBy(',').defaultsTo(getDefaultLoggingConfiguration());
parser.allowsUnrecognizedOptions();
final OptionSet optionSet = parser.parse(args);
return new DiscoveryData(optionSet.valueOf(gameDir), optionSet.valueOf(launchTarget), args);
return new DiscoveryData(optionSet.valueOf(gameDir), optionSet.valueOf(launchTarget), optionSet.valuesOf(loggingConfig), args);
}

void processArguments(Environment env, Consumer<OptionParser> parserConsumer, BiConsumer<OptionSet, BiFunction<String, OptionSet, ITransformationService.OptionResult>> resultConsumer) {
Expand All @@ -59,6 +64,7 @@ void processArguments(Environment env, Consumer<OptionParser> parserConsumer, Bi
minecraftJarOption = parser.accepts("minecraftJar", "Path to minecraft jar").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.READABLE)).withValuesSeparatedBy(',');
uuidOption = parser.accepts("uuid", "The UUID of the logging in player").withRequiredArg();
launchTarget = parser.accepts("launchTarget", "LauncherService target to launch").withRequiredArg();
loggingConfigOption = parser.accepts("loggingConfig", "Log4j configuration files to composite together").withOptionalArg().withValuesConvertedBy(new UriConverter()).withValuesSeparatedBy(',').defaultsTo(getDefaultLoggingConfiguration());

parserConsumer.accept(parser);
nonOption = parser.nonOptions();
Expand All @@ -68,9 +74,18 @@ void processArguments(Environment env, Consumer<OptionParser> parserConsumer, Bi
env.computePropertyIfAbsent(IEnvironment.Keys.ASSETSDIR.get(), f -> this.optionSet.valueOf(assetsDirOption));
env.computePropertyIfAbsent(IEnvironment.Keys.LAUNCHTARGET.get(), f -> this.optionSet.valueOf(launchTarget));
env.computePropertyIfAbsent(IEnvironment.Keys.UUID.get(), f -> this.optionSet.valueOf(uuidOption));
env.computePropertyIfAbsent(IEnvironment.Keys.LOGGING_CONFIG.get(), f -> this.optionSet.valuesOf(loggingConfigOption));
resultConsumer.accept(this.optionSet, this::optionResults);
}

private static URI getDefaultLoggingConfiguration() {
try {
return Objects.requireNonNull(ArgumentHandler.class.getClassLoader().getResource("log4j2.xml")).toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

Path[] getSpecialJars() {
return this.optionSet.valuesOf(minecraftJarOption).toArray(new Path[0]);
}
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/cpw/mods/modlauncher/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@

import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.log.MarkerLogLevelFilter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.*;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.filter.MarkerFilter;

import java.net.URI;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -74,6 +83,7 @@ public static void main(String... args) {
JVM information: %s %s %s
""", props.getProperty("java.vm.vendor"), props.getProperty("java.vm.name"), props.getProperty("java.vm.version"));
}

LogManager.getLogger().info(MODLAUNCHER,"ModLauncher running: args {}", () -> LaunchServiceHandler.hideAccessToken(args));
LogManager.getLogger().info(MODLAUNCHER, "JVM identified as {} {} {}", props.getProperty("java.vm.vendor"), props.getProperty("java.vm.name"), props.getProperty("java.vm.version"));
new Launcher().run(args);
Expand All @@ -85,6 +95,7 @@ public final TypesafeMap blackboard() {

private void run(String... args) {
final ArgumentHandler.DiscoveryData discoveryData = this.argumentHandler.setArgs(args);
reconfigureLogger(discoveryData.loggingConfigs());
this.transformationServicesHandler.discoverServices(discoveryData);
final var scanResults = this.transformationServicesHandler.initializeTransformationServices(this.argumentHandler, this.environment, this.nameMappingServiceHandler)
.stream().collect(Collectors.groupingBy(ITransformationService.Resource::target));
Expand All @@ -109,6 +120,61 @@ private void run(String... args) {
this.launchService.launch(this.argumentHandler, this.moduleLayerHandler.getLayer(IModuleLayerManager.Layer.GAME).orElseThrow(), this.classLoader, this.launchPlugins);
}

private void reconfigureLogger(List<URI> configurationFiles) {
final var configurations = configurationFiles.stream()
.map(ConfigurationSource::fromUri)
.map(source -> ConfigurationFactory.getInstance().getConfiguration(LoggerContext.getContext(), source))
.peek(config -> {
if (!(config instanceof AbstractConfiguration))
throw new RuntimeException("Configuration not an instance of AbstractConfiguration");
})
.map(config -> (AbstractConfiguration)config)
.collect(Collectors.toList());

final var levelConfigBuilder = ConfigurationBuilderFactory.newConfigurationBuilder()
.setConfigurationName("MODLAUNCHER-LOGLEVELS");
System.getProperties().entrySet().stream()
.map(entry -> (Map.Entry<String, String>)(Map.Entry<?,?>)entry)
.filter(entry -> entry.getKey().startsWith("logging.loglevel."))
.forEach(entry -> {
final var loggerName = entry.getKey().substring("logging.loglevel.".length());
final var level = Level.getLevel(entry.getValue());
if (loggerName.equals("default")) {
levelConfigBuilder.add(levelConfigBuilder.newRootLogger(level));
} else {
levelConfigBuilder.add(levelConfigBuilder.newLogger(loggerName, level));
}
});

final var markerConfigBuilder = ConfigurationBuilderFactory.newConfigurationBuilder()
.setConfigurationName("MODLAUNCHER-MARKERS");
System.getProperties().entrySet().stream()
.map(entry -> (Map.Entry<String, String>)(Map.Entry<?, ?>)entry)
.filter(entry -> entry.getKey().startsWith("logging.marker."))
.forEach(entry -> {
final var markerName = entry.getKey().substring("logging.marker.".length());
final var minimumLevel = Level.getLevel(entry.getValue());
markerConfigBuilder.add(markerConfigBuilder.newFilter("MarkerLogLevelFilter", Filter.Result.ACCEPT, Filter.Result.DENY)
.addAttribute(MarkerLogLevelFilter.ATTR_MARKER, markerName)
.addAttribute(MarkerLogLevelFilter.ATTR_MINIMUM_LEVEL, minimumLevel));
});

// These are the default markers; they have to be specified at the very end so that they have the lowest priority.
markerConfigBuilder.add(markerConfigBuilder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
.addAttribute(MarkerFilter.ATTR_MARKER, "NETWORK_PACKETS"))
.add(markerConfigBuilder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
.addAttribute(MarkerFilter.ATTR_MARKER, "CLASSLOADING"))
.add(markerConfigBuilder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
.addAttribute(MarkerFilter.ATTR_MARKER, "LAUNCHPLUGIN"))
.add(markerConfigBuilder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
.addAttribute(MarkerFilter.ATTR_MARKER, "CLASSDUMP"));

configurations.add(levelConfigBuilder.build());
configurations.add(markerConfigBuilder.build());
Configurator.reconfigure(new CompositeConfiguration(configurations));
LogManager.getLogger().trace(MODLAUNCHER, "Logger reconfigured from {} sources", configurations.size());
}

public Environment environment() {
return this.environment;
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/cpw/mods/modlauncher/api/IEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;

import java.net.URI;
import java.nio.file.*;
import java.util.*;
import java.util.function.*;
Expand Down Expand Up @@ -117,6 +118,11 @@ final class Keys {
* True if we can compute secured JAR state. JVMs < 8.0.61 do not have this feature because reasons
*/
public static final Supplier<TypesafeMap.Key<Boolean>> SECURED_JARS_ENABLED = buildKey("securedJarsEnabled", Boolean.class);

/**
* Logging configurations to be composited together when configuring logging.
*/
public static final Supplier<TypesafeMap.Key<List<URI>>> LOGGING_CONFIG = buildKey("loggingConfig", List.class);
}


Expand Down
136 changes: 136 additions & 0 deletions src/main/java/cpw/mods/modlauncher/log/MarkerLogLevelFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package cpw.mods.modlauncher.log;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.filter.AbstractFilter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;

@Plugin(name = "MarkerLogLevelFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
@PerformanceSensitive("allocation")
public class MarkerLogLevelFilter extends AbstractFilter {
public static final String ATTR_MARKER = "marker";
public static final String ATTR_MINIMUM_LEVEL = "minimumLevel";

private final String marker;
private final String minimumLevel;

protected MarkerLogLevelFilter(String marker, String minimumLevel, Result onMatch, Result onMismatch) {
super(onMatch, onMismatch);
this.marker = marker;
this.minimumLevel = minimumLevel;
}

private Result filter(Marker marker, Level level) {
if (marker == null || level == null || !marker.isInstanceOf(this.marker))
return Result.NEUTRAL;

final var comparedLevel = Level.getLevel(this.minimumLevel);
if (comparedLevel.isLessSpecificThan(level))
return onMatch;

return onMismatch;
}

@Override
public Result filter(LogEvent event) {
return filter(event.getMarker(), event.getLevel());
}

@Override
public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8) {
return filter(marker, level);
}

@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9) {
return filter(marker, level);
}

@Override
public String toString() {
return "MarkerLogLevelFilter{" + "marker='" + marker + '\'' + ", minimumLevel=" + minimumLevel + '}';
}

@PluginFactory
public static MarkerLogLevelFilter createFilter(
@PluginAttribute(ATTR_MARKER) final String marker,
@PluginAttribute(ATTR_MINIMUM_LEVEL) final String minLevel,
@PluginAttribute(AbstractFilterBuilder.ATTR_ON_MATCH) final Result match,
@PluginAttribute(AbstractFilterBuilder.ATTR_ON_MISMATCH) final Result mismatch) {

if (marker == null) {
LOGGER.error("A marker must be provided for MarkerLogLevelFilter");
return null;
}

if (minLevel == null) {
LOGGER.error("A minimum level must be provided for MarkerLogLevelFilter");
return null;
}

return new MarkerLogLevelFilter(marker, minLevel, match, mismatch);
}
}
22 changes: 22 additions & 0 deletions src/main/java/cpw/mods/modlauncher/util/UriConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cpw.mods.modlauncher.util;

import joptsimple.ValueConverter;

import java.net.URI;

public class UriConverter implements ValueConverter<URI> {
@Override
public URI convert(String value) {
return URI.create(value);
}

@Override
public Class<? extends URI> valueType() {
return URI.class;
}

@Override
public String valuePattern() {
return "[scheme:]scheme-specific-part[#fragment]";
}
}
Loading