This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 355358ea19353ae675bdbf2f8c9f722aa99bc45c Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Dec 1 18:50:32 2023 +0100 Make possible to force logging configuration after JVM startup. This is useful when setting system properties with JVM flags does not work. It seems to be the case with jshell for instance. --- .../main/org/apache/sis/console/Command.java | 95 +++++++++++++++++----- .../main/org/apache/sis/console/CommandRunner.java | 2 +- .../apache/sis/console/ResourcesDownloader.java | 2 +- .../org/apache/sis/util/logging/Initializer.java | 70 ++++++++++------ optional/src/org.apache.sis.gui/bundle/bin/sis | 1 + optional/src/org.apache.sis.gui/bundle/bin/sisfx | 1 + .../bundle/conf/logging.properties | 6 +- 7 files changed, 128 insertions(+), 49 deletions(-) diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java index 5c1bde2fcd..d2033feca6 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java @@ -18,16 +18,20 @@ package org.apache.sis.console; import java.util.Locale; import java.util.logging.LogManager; -import java.util.logging.ConsoleHandler; import java.io.Console; import java.io.PrintStream; import java.io.PrintWriter; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.SQLException; import org.opengis.referencing.operation.TransformException; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.internal.X364; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.logging.Initializer; import org.apache.sis.util.logging.MonolineFormatter; @@ -133,7 +137,7 @@ public final class Command { * @throws InvalidCommandException if an invalid command has been given. * @throws InvalidOptionException if the given arguments contain an invalid option. */ - protected Command(final Object[] args) throws InvalidCommandException, InvalidOptionException { + public Command(final Object[] args) throws InvalidCommandException, InvalidOptionException { int commandIndex = -1; String commandName = null; for (int i=0; i<args.length; i++) { @@ -171,7 +175,70 @@ public final class Command { Errors.Keys.UnknownCommand_1, commandName), commandName); } } - CommandRunner.instance = command; // For ResourcesDownloader only. + } + + /** + * Loads the logging configuration file if not already done, then configures the monoline formatter. + * This method performs two main tasks: + * + * <ol> + * <li>If the {@value Initializer#CONFIG_FILE_PROPERTY} is <em>not</em> set, then try + * to set it to {@code $SIS_HOME/conf/logging.properties} and load that file.</li> + * <li>If the {@code "derby.stream.error.file"} system property is not defined, + * then try to set it to {@code $SIS_HOME/log/derby.log}.</li> + * <li>If the configuration file declares {@link MonolineFormatter} as the console formatter, + * ensures that the formatter is loaded and resets its colors depending on whether X364 + * seems to be supported.</li> + * </ol> + * + * This method can be invoked at initialization time, + * such as the beginning of {@code main(…)} static method. + * + * @since 1.5 + */ + public static void configureLogging() { + final String value = System.getenv("SIS_HOME"); + if (value != null) { + final Path home = Path.of(value).normalize(); + Path file = home.resolve("log"); + if (Files.isDirectory(file)) { + setPropertyIfAbsent("derby.stream.error.file", file.resolve("derby.log")); + } + file = home.resolve("conf").resolve("logging.properties"); + if (Files.isRegularFile(file)) { + if (setPropertyIfAbsent(Initializer.CONFIG_FILE_PROPERTY, file)) try { + Initializer.reload(file); + } catch (IOException e) { + Logging.unexpectedException(null, Command.class, "configureLogging", e); + } + } + } + /* + * The logging configuration is given by the "conf/logging.properties" file in the Apache SIS + * installation directory. By default, that configuration file contains the following line: + * + * java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter + * + * However, this configuration is sometime silently ignored by the LogManager at JVM startup time, + * maybe because the Apache SIS classes were not yet loaded. So we check again if the configuration + * contained that line, and manually re-install the log formatter if that line is present. + */ + final String handler = LogManager.getLogManager().getProperty("java.util.logging.ConsoleHandler.formatter"); + if (MonolineFormatter.class.getName().equals(handler)) { + MonolineFormatter.install().resetLevelColors(X364.isAnsiSupported()); + } + } + + /** + * Sets the specified system property if that property is not already set. + * This method returns whether the property has been set. + */ + private static boolean setPropertyIfAbsent(final String property, final Path value) { + if (System.getProperty(property) == null) { + System.setProperty(property, value.toString()); + return true; + } + return false; } /** @@ -190,12 +257,15 @@ public final class Command { if (command.options.containsKey(Option.HELP)) { command.help(command.commandName.toLowerCase(Locale.US)); } else try { + CommandRunner.instance.set(command); // For ResourcesDownloader only. int status = command.run(); command.flush(); return status; } catch (Exception e) { command.error(null, e); throw e; + } finally { + CommandRunner.instance.remove(); } return 0; } @@ -254,24 +324,7 @@ public final class Command { * @param args command-line options. */ public static void main(final String[] args) { - /* - * The logging configuration is given by the "conf/logging.properties" file in the Apache SIS - * installation directory. By default, that configuration file contains the following line: - * - * java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter - * - * However, this configuration is silently ignored by LogManager at JVM startup time, probably - * because the Apache SIS class is not on the system module path. So we check again for this - * configuration line here, and manually install our log formatter only if the above-cited - * line is present. - */ - final LogManager manager = LogManager.getLogManager(); - if (MonolineFormatter.class.getName().equals(manager.getProperty(ConsoleHandler.class.getName() + ".formatter"))) { - MonolineFormatter.install(); - } - /* - * Now run the command. - */ + configureLogging(); final Command c; try { c = new Command(args); diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java index 44bdc42425..5a5aa9f9b5 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java @@ -55,7 +55,7 @@ abstract class CommandRunner { * We use this static field as a workaround for the fact that {@code ResourcesDownloader} is not * instantiated by us, so we cannot pass the {@code CommandRunner} instance to its constructor. */ - static CommandRunner instance; + static final ThreadLocal<CommandRunner> instance = new ThreadLocal<>(); /** * The name of this command, as specified by the user on the command-line. diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/ResourcesDownloader.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/ResourcesDownloader.java index 9ceaf56f09..9ccedb690f 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/ResourcesDownloader.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/ResourcesDownloader.java @@ -68,7 +68,7 @@ public class ResourcesDownloader extends OptionalInstallations { */ public ResourcesDownloader() { super("text/plain"); - final CommandRunner command = CommandRunner.instance; + final CommandRunner command = CommandRunner.instance.get(); if (command != null) { locale = command.locale; colors = command.colors; diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Initializer.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Initializer.java index 45e3d0bad0..5c2f11e9d8 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Initializer.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/Initializer.java @@ -59,8 +59,8 @@ import java.nio.file.Path; * (i.e. as documented by {@link FileHandler}). * * <h2>Usage</h2> - * This class should not referenced directly by other Java code. - * Instead, it should be specified at JVM startup time like below: + * This class should not be referenced directly by other Java code. + * Instead, it can be specified at JVM startup time like below: * * <pre>java -Djava.util.logging.config.class=org.apache.sis.util.logging.Initializer \ * -Djava.util.logging.config.file=<i>path/to/my/application/conf/logging.properties</i></pre> @@ -68,7 +68,7 @@ import java.nio.file.Path; * See for example the {@code bin/sis} shell script in Apache SIS binary distribution. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.5 * * @see FileHandler * @@ -80,10 +80,18 @@ public class Initializer { * because it may be invoked early while the application is still initializing. */ + /** + * The system property for the logging configuration file. + * The value is defined by {@link LogManager} to {@value}. + * + * @since 1.5 + */ + public static final String CONFIG_FILE_PROPERTY = "java.util.logging.config.file"; + /** * The property for which to replace the {@value #PATTERN} string. */ - private static final String PROPERTY = "java.util.logging.FileHandler.pattern"; + private static final String PATTERN_PROPERTY = "java.util.logging.FileHandler.pattern"; /** * The pattern to replace. @@ -93,7 +101,7 @@ public class Initializer { /** * Configures Java logging using a filtered configuration file. * This constructor gets the configuration file referenced by - * the {@code "java.util.logging.config.file"} system property, + * the {@value #CONFIG_FILE_PROPERTY} system property, * applies the filtering described in class javadoc, * then gives the filtered configuration to {@link LogManager#readConfiguration(InputStream)}. * @@ -103,29 +111,45 @@ public class Initializer { * @throws IOException if an error occurred while reading the configuration file. */ public Initializer() throws IOException { - final String file = System.getProperty("java.util.logging.config.file"); + final String file = System.getProperty(CONFIG_FILE_PROPERTY); if (file != null) { - final Path path = Path.of(file).normalize(); - final StringBuilder buffer = new StringBuilder(600); - for (String line : Files.readAllLines(path)) { - if (!(line = line.trim()).isEmpty() && line.charAt(0) != '#') { - final int base = buffer.length(); - buffer.append(line).append('\n'); - if (line.startsWith(PROPERTY)) { - final int i = buffer.indexOf(PATTERN, base + PROPERTY.length()); - if (i >= 0) { - Path parent = path; - for (int j=Math.min(parent.getNameCount(), 2); --j >= 0;) { - parent = parent.getParent(); - } - String replacement = (parent != null) ? parent.toString() : "."; - replacement = replacement.replace(File.separatorChar, '/'); - buffer.replace(i, i + PATTERN.length(), replacement); + reload(Path.of(file)); + } + } + + /** + * Reloads the logging configuration from the specified file. + * The new configuration replaces the previous one (this is not an update). + * The extended pattern ({@code "%p"}) is parsed as described in the class summary. + * + * @param path path to the logging configuration file. + * @throws IOException if an error occurred while reading the configuration file. + * + * @see LogManager#readConfiguration(InputStream) + * + * @since 1.5 + */ + public static void reload(Path path) throws IOException { + path = path.normalize(); + final StringBuilder buffer = new StringBuilder(600); + for (String line : Files.readAllLines(path)) { + if (!(line = line.trim()).isEmpty() && line.charAt(0) != '#') { + final int base = buffer.length(); + buffer.append(line).append('\n'); + if (line.startsWith(PATTERN_PROPERTY)) { + final int i = buffer.indexOf(PATTERN, base + PATTERN_PROPERTY.length()); + if (i >= 0) { + Path parent = path; + for (int j=Math.min(parent.getNameCount(), 2); --j >= 0;) { + parent = parent.getParent(); } + String replacement = (parent != null) ? parent.toString() : "."; + replacement = replacement.replace(File.separatorChar, '/'); + buffer.replace(i, i + PATTERN.length(), replacement); } } } - LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(buffer.toString().getBytes())); } + LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(buffer.toString().getBytes())); } } diff --git a/optional/src/org.apache.sis.gui/bundle/bin/sis b/optional/src/org.apache.sis.gui/bundle/bin/sis index 1caa9c6359..b3f310a39a 100755 --- a/optional/src/org.apache.sis.gui/bundle/bin/sis +++ b/optional/src/org.apache.sis.gui/bundle/bin/sis @@ -21,6 +21,7 @@ set -o errexit BASE_DIR="`dirname "$( readlink -e "$0"; )";`/.." SIS_DATA="${SIS_DATA:-$BASE_DIR/data}" export SIS_DATA +unset SIS_HOME # Execute SIS with any optional JAR that the user may put in the 'lib' directory. java --module-path "$BASE_DIR/lib:$BASE_DIR/lib/app/org.apache.sis.console.jar" \ diff --git a/optional/src/org.apache.sis.gui/bundle/bin/sisfx b/optional/src/org.apache.sis.gui/bundle/bin/sisfx index 4bbdfcd5b5..68a85b4ff9 100755 --- a/optional/src/org.apache.sis.gui/bundle/bin/sisfx +++ b/optional/src/org.apache.sis.gui/bundle/bin/sisfx @@ -23,6 +23,7 @@ BASE_DIR="`dirname "$( readlink -e "$0"; )";`/.." SIS_DATA="${SIS_DATA:-$BASE_DIR/data}" export SIS_DATA +unset SIS_HOME if [ -z "$PATH_TO_FX" ] then diff --git a/optional/src/org.apache.sis.gui/bundle/conf/logging.properties b/optional/src/org.apache.sis.gui/bundle/conf/logging.properties index 95b2fa71fa..d65c2a4940 100644 --- a/optional/src/org.apache.sis.gui/bundle/conf/logging.properties +++ b/optional/src/org.apache.sis.gui/bundle/conf/logging.properties @@ -35,7 +35,7 @@ handlers = java.util.logging.FileHandler, \ # - By handler (INFO for console, FINE for log file). .level = CONFIG -org.apache.sis.level = FINER +org.apache.sis.level = FINE java.util.logging.FileHandler.level = FINE java.util.logging.ConsoleHandler.level = INFO @@ -63,6 +63,6 @@ java.util.logging.ConsoleHandler.level = INFO # "class:short", "class:long" and "class.method". java.util.logging.FileHandler.pattern = %p/log/sis.log -java.util.logging.FileHandler.formatter = org.apache.sis.util.logging.MonolineFormatter +java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter -org.apache.sis.util.logging.MonolineFormatter.source = logger:long +org.apache.sis.util.logging.MonolineFormatter.source = class:short