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 all commits
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
10 changes: 8 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(',');
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(',');

parserConsumer.accept(parser);
nonOption = parser.nonOptions();
Expand Down
77 changes: 76 additions & 1 deletion src/main/java/cpw/mods/modlauncher/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,19 @@

import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.log.MarkerLogLevelFilter;
import cpw.mods.modlauncher.util.LoggingUtils;
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 @@ -58,6 +68,7 @@ private Launcher() {
environment.computePropertyIfAbsent(IEnvironment.Keys.MLIMPL_VERSION.get(), s->IEnvironment.class.getPackage().getImplementationVersion());
environment.computePropertyIfAbsent(IEnvironment.Keys.MODLIST.get(), s->new ArrayList<>());
environment.computePropertyIfAbsent(IEnvironment.Keys.SECURED_JARS_ENABLED.get(), k-> ProtectionDomainHelper.canHandleSecuredJars());
environment.computePropertyIfAbsent(IEnvironment.Keys.LOGGING_CONFIG.get(), k -> new ArrayList<>(LoggingUtils.getConfigurationSources(this.moduleLayerHandler.getLayer(IModuleLayerManager.Layer.BOOT).orElseThrow())));
this.transformStore = new TransformStore();
this.transformationServicesHandler = new TransformationServicesHandler(this.transformStore, this.moduleLayerHandler);
this.argumentHandler = new ArgumentHandler();
Expand All @@ -74,6 +85,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,14 +97,19 @@ public final TypesafeMap blackboard() {

private void run(String... args) {
final ArgumentHandler.DiscoveryData discoveryData = this.argumentHandler.setArgs(args);
reconfigureLogger(discoveryData.loggingConfigs());
this.transformationServicesHandler.discoverServices(discoveryData);
reconfigureLogger(discoveryData.loggingConfigs());
final var scanResults = this.transformationServicesHandler.initializeTransformationServices(this.argumentHandler, this.environment, this.nameMappingServiceHandler)
.stream().collect(Collectors.groupingBy(ITransformationService.Resource::target));
scanResults.getOrDefault(IModuleLayerManager.Layer.PLUGIN, List.of())
.stream()
.<SecureJar>mapMulti((resource, action) -> resource.resources().forEach(action))
.forEach(np->this.moduleLayerHandler.addToLayer(IModuleLayerManager.Layer.PLUGIN, np));
this.moduleLayerHandler.buildLayer(IModuleLayerManager.Layer.PLUGIN);
final var pluginLayer = this.moduleLayerHandler.buildLayer(IModuleLayerManager.Layer.PLUGIN);
var sources = LoggingUtils.getConfigurationSources(pluginLayer.layer());
this.environment.getProperty(IEnvironment.Keys.LOGGING_CONFIG.get()).ifPresent(lc -> lc.addAll(sources));
reconfigureLogger(discoveryData.loggingConfigs());
final var gameResults = this.transformationServicesHandler.triggerScanCompletion(this.moduleLayerHandler)
.stream().collect(Collectors.groupingBy(ITransformationService.Resource::target));
final var gameContents = Stream.of(scanResults, gameResults)
Expand All @@ -105,10 +122,68 @@ private void run(String... args) {
this.launchService.validateLaunchTarget(this.argumentHandler);
final TransformingClassLoaderBuilder classLoaderBuilder = this.launchService.identifyTransformationTargets(this.argumentHandler);
this.classLoader = this.transformationServicesHandler.buildTransformingClassLoader(this.launchPlugins, classLoaderBuilder, this.environment, this.moduleLayerHandler);
reconfigureLogger(discoveryData.loggingConfigs());
Thread.currentThread().setContextClassLoader(this.classLoader);
this.launchService.launch(this.argumentHandler, this.moduleLayerHandler.getLayer(IModuleLayerManager.Layer.GAME).orElseThrow(), this.classLoader, this.launchPlugins);
}

private void reconfigureLogger(List<URI> additionalConfigurationFiles) {
final var configurations = this.environment.getProperty(IEnvironment.Keys.LOGGING_CONFIG.get()).orElseThrow().stream()
.map(ConfigurationSource::fromUri)
.map(source -> ConfigurationFactory.getInstance().getConfiguration(LoggerContext.getContext(), source))
.map(AbstractConfiguration.class::cast)
.collect(Collectors.toList());

additionalConfigurationFiles.stream()
.map(ConfigurationSource::fromUri)
.map(source -> ConfigurationFactory.getInstance().getConfiguration(LoggerContext.getContext(), source))
.map(AbstractConfiguration.class::cast)
.forEach(configurations::add);

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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import cpw.mods.modlauncher.util.LoggingUtils;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import joptsimple.*;
import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -58,6 +59,8 @@ List<ITransformationService.Resource> initializeTransformationServices(ArgumentH
TransformingClassLoader buildTransformingClassLoader(final LaunchPluginHandler pluginHandler, final TransformingClassLoaderBuilder builder, final Environment environment, final ModuleLayerHandler layerHandler) {
final List<Function<String, Optional<URL>>> classLocatorList = serviceLookup.values().stream().map(TransformationServiceDecorator::getClassLoader).filter(Objects::nonNull).collect(Collectors.toList());
final var layerInfo = layerHandler.buildLayer(IModuleLayerManager.Layer.GAME, (cf, parents)->new TransformingClassLoader(transformStore, pluginHandler, builder, environment, cf, parents));
var sources = LoggingUtils.getConfigurationSources(layerInfo.layer());
Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.LOGGING_CONFIG.get()).ifPresent(lc -> lc.addAll(sources));
layerHandler.updateLayer(IModuleLayerManager.Layer.PLUGIN, li->li.cl().setFallbackClassLoader(layerInfo.cl()));
return (TransformingClassLoader) layerInfo.cl();
}
Expand Down Expand Up @@ -128,6 +131,8 @@ void discoverServices(final ArgumentHandler.DiscoveryData discoveryData) {
LOGGER.debug(MODLAUNCHER, "Found additional transformation services from discovery services: {}", ()->additionalPaths.stream().map(ap->Arrays.toString(ap.paths())).collect(Collectors.joining()));
additionalPaths.forEach(np->layerHandler.addToLayer(IModuleLayerManager.Layer.SERVICE, np));
var serviceLayer = layerHandler.buildLayer(IModuleLayerManager.Layer.SERVICE);
var sources = LoggingUtils.getConfigurationSources(serviceLayer.layer());
Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.LOGGING_CONFIG.get()).ifPresent(lc -> lc.addAll(sources));
earlyDiscoveryServices.forEach(s->s.earlyInitialization(discoveryData.launchTarget(), discoveryData.arguments()));
serviceLookup = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(serviceLayer.layer(), ITransformationService.class), sce -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation service, expect problems", sce))
.collect(Collectors.toMap(ITransformationService::name, TransformationServiceDecorator::new));
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
Loading