All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.kernel.Setup Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel;

import com.google.common.base.Charsets;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggerRepository;
import sirius.kernel.commons.Strings;
import sirius.kernel.commons.Value;
import sirius.kernel.commons.ValueHolder;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;

/**
 * Used to configure the setup of the SIRIUS framework.
 * 

* An instance of this class is passed into {@link Sirius#start(Setup)} to launch the framework. Alternatively * {@link #createAndStartEnvironment(ClassLoader)} can be called which configures and starts SIRIUS based on * system properties. This is utilized by the sirius-ipl framework which provides an application container * (effectively a main class and some control scripts to make it behave like a daemon or Windows Service). *

* To further customize settings, subclass and override the appropriate methods. *

* By default, this class does the following: *

    *
  • Sets the encoding to UTF-8
  • *
  • Sets the DNS cache to 10 seconds - by default this would be 'infinite'
  • *
  • Redirects all Java Logging to Log4J
  • *
  • Creates either a console or file appender for Log4J (PROD = file, DEV = console)
  • *
*/ public class Setup { private static final String LOGS_DIRECTORY = "logs"; private static final String DEFAULT_LOG_FILE_NAME = "application.log"; /** * Determines the mode in which the framework should run. This mainly effects logging and the configuration. */ public enum Mode { DEV, TEST, PROD } protected ClassLoader loader; protected Mode mode; protected boolean logToConsole; protected boolean logToFile; protected Level defaultLevel = Level.INFO; protected String consoleLogFormat = "%d{HH:mm:ss.SSS} %-5p [%t%X{flow}] %c - %m%n"; protected String fileLogFormat = "%d %-5p [%t%X{flow}] %c - %m%n"; /** * Creates a new setup for the given mode and class loader. * * @param mode the mode to run the framework in * @param loader the class loader used for component discovery */ public Setup(Mode mode, ClassLoader loader) { this.mode = mode; this.loader = loader; logToConsole = mode != Mode.PROD || getProperty("console").asBoolean(false); logToFile = mode == Mode.PROD && !getProperty("disableLogfile").asBoolean(false); } /** * Overwrites the settings for the console appender. * * @param flag determines if logging to the console is enabled or not. * @return the setup itself for fluent method calls */ public Setup withLogToConsole(boolean flag) { this.logToConsole = flag; return this; } /** * Overwrites the settings for the file appender. * * @param flag determines if logging to the log file is enabled or not. * @return the setup itself for fluent method calls */ public Setup withLogToFile(boolean flag) { this.logToFile = flag; return this; } /** * Used to set the default log level used by the root logger. *

* Note that each logger can be configured by specifying logging.[NAME] in the * system configuration * * @param level the level to use * @return the setup itself for fluent method calls */ public Setup withDefaultLogLevel(Level level) { this.defaultLevel = level; return this; } /** * Specifies the pattern used to format log messages in the console. *

* Refer to {@link org.apache.log4j.PatternLayout} for available options. * * @param format the template string to use * @return the setup itself for fluent method calls */ public Setup withConsoleLogFormat(String format) { this.consoleLogFormat = format; return this; } /** * Specifies the pattern used to format log messages in the log file. *

* Refer to {@link org.apache.log4j.PatternLayout} for available options. * * @param format the template string to use * @return the setup itself for fluent method calls */ public Setup withFileLogFormat(String format) { this.fileLogFormat = format; return this; } /** * Creates and starts a new setup based on system properties. *

* Essentially this is debug which switches from PROD to DEV and console which enables * log output to the console even if running in PROD mode. * * @param loader the class loader to use */ public static void createAndStartEnvironment(ClassLoader loader) { Sirius.start(new Setup(getProperty("debug").asBoolean(false) ? Mode.DEV : Mode.PROD, loader)); } /** * Provides a main method for debugging purposes. * * @param args the command line arguments (currently ignored) */ public static void main(String[] args) { Sirius.start(new Setup(Mode.DEV, Setup.class.getClassLoader())); } /** * Initializes the Virtual Machine. *

* This modifies the DNS cache, encoding and logging setup... *

* This method is automatically called by {@link sirius.kernel.Sirius#start(Setup)} */ public void init() { setupLogging(); setupDNSCache(); setupEncoding(); outputJVMInfo(); } /** * Outputs the name of the underlying JVM to verify that the correct one was used to start the application */ protected void outputJVMInfo() { RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean(); Sirius.LOG.INFO("%s (%s, %s, %s)", mx.getVmName(), mx.getSpecVersion(), mx.getVmVendor(), mx.getVmVersion()); OperatingSystemMXBean osx = ManagementFactory.getOperatingSystemMXBean(); if (osx.getAvailableProcessors() > 1) { Sirius.LOG.INFO("%s (%s) on %d CPUs (%s)", osx.getName(), osx.getVersion(), osx.getAvailableProcessors(), osx.getArch()); } else { Sirius.LOG.INFO("%s (%s) on a %s CPU", osx.getName(), osx.getVersion(), osx.getArch()); } } /** * Returns the loader to use for component discovery. * * @return the loader to use for component discovery */ public ClassLoader getLoader() { return loader; } /** * Returns the mode the framework was started in. * * @return the mode of the framework */ public Mode getMode() { return mode; } /** * Sets UTF-8 as default encoding */ protected void setupEncoding() { Sirius.LOG.FINE("Setting " + Charsets.UTF_8.name() + " as default encoding (file.encoding)"); System.setProperty("file.encoding", Charsets.UTF_8.name()); Sirius.LOG.FINE("Setting " + Charsets.UTF_8.name() + " as default mime encoding (mail.mime.charset)"); System.setProperty("mail.mime.charset", Charsets.UTF_8.name()); } /** * Sets the DNS cache to a sane value. *

* By default java infinitely caches all DNS entries. Will be changed to 10 seconds... */ protected void setupDNSCache() { Sirius.LOG.FINE("Setting DNS-Cache to 10 seconds..."); java.security.Security.setProperty("networkaddress.cache.ttl", "10"); } /** * Reads the given system property. * * @param property the property to read * @return the contents of the property wrapped as {@link Value} */ protected static Value getProperty(String property) { return Value.of(System.getProperty(property)); } /** * Initializes log4j as logging framework. *

* In development mode, we log everything to the console. In production mode, we use a rolling file appender and * log into the logs directory. */ protected void setupLogging() { final LoggerRepository repository = Logger.getRootLogger().getLoggerRepository(); repository.resetConfiguration(); Logger.getRootLogger().setLevel(defaultLevel); if (shouldLogToConsole()) { ConsoleAppender console = new ConsoleAppender(); console.setLayout(new PatternLayout(consoleLogFormat)); console.setThreshold(Level.DEBUG); console.activateOptions(); Logger.getRootLogger().addAppender(console); } if (shouldLogToFile()) { File logsDirectory = new File(getLogsDirectory()); if (!logsDirectory.exists()) { logsDirectory.mkdirs(); } DailyRollingFileAppender fa = new DailyRollingFileAppender(); fa.setName("FileLogger"); fa.setFile(getLogFilePath()); fa.setLayout(new PatternLayout(fileLogFormat)); fa.setThreshold(Level.DEBUG); fa.setAppend(true); fa.activateOptions(); Logger.getRootLogger().addAppender(fa); } redirectJavaLoggerToLog4j(); } /** * Returns the name of the log directory. * * @return the name of the log directory */ protected String getLogsDirectory() { return LOGS_DIRECTORY; } /** * Computes the effective name for the log file. * * @return computes the log file which is getLogsDirectory() / getLogFileName() */ protected String getLogFilePath() { return getLogsDirectory() + File.separator + getLogFileName(); } /** * Invoked by Sirius itself on a regular basis to clean old log files. * * @param retentionInMillis the desired retention time in milli seconds before a file is deleted. */ public void cleanOldLogFiles(long retentionInMillis) { if (!shouldLogToFile()) { return; } File logsDir = new File(getLogsDirectory()); if (!logsDir.exists()) { return; } File[] children = logsDir.listFiles(); if (children == null) { return; } // The file must start with the log file name, but have an extension (we don't want to delete // the main log file). Predicate validLogFileName = f -> f.getName().startsWith(getLogFileName()) && !f.getName().equals(getLogFileName()); Predicate isOldEnough = f -> System.currentTimeMillis() - f.lastModified() > retentionInMillis; Arrays.stream(children).filter(File::isFile).filter(validLogFileName).filter(isOldEnough).forEach(File::delete); } /** * Returns the name of the log file. * * @return the name of the log file */ protected String getLogFileName() { return DEFAULT_LOG_FILE_NAME; } /** * Determines if a console appender should be installed * * @return true if the framework should log to the console */ protected boolean shouldLogToConsole() { return logToConsole; } /** * Determines if a file appender should be installed * * @return true if the framework should log into a file */ protected boolean shouldLogToFile() { return logToFile; } /** * Redirects all java.logging output to Log4j */ protected void redirectJavaLoggerToLog4j() { final LoggerRepository repository = Logger.getRootLogger().getLoggerRepository(); java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger(""); // remove old handlers for (Handler handler : rootLogger.getHandlers()) { rootLogger.removeHandler(handler); } // add our own Handler handler = new Handler() { private Formatter formatter = new SimpleFormatter(); @Override public void publish(LogRecord record) { repository.getLogger(record.getLoggerName() == null ? "unknown" : record.getLoggerName()) .log(Log.convertJuliLevel(record.getLevel()), formatter.formatMessage(record), record.getThrown()); } @Override public void flush() { // Not required } @Override public void close() { // Not required } }; handler.setLevel(java.util.logging.Level.ALL); rootLogger.addHandler(handler); rootLogger.setLevel(java.util.logging.Level.INFO); } /** * Loads the main application configuration which is shipped with the app. *

* By default this loads "application.conf" from the classpath. Also "application-*.conf * are loaded in case it is splitted into several parts. * * @return the main application config. This will override all component configs but be overridden by developer, * test and instance configs */ @Nonnull public Config loadApplicationConfig() { final ValueHolder result = new ValueHolder<>(ConfigFactory.empty()); // Load component configurations Sirius.getClasspath().find(Pattern.compile("application-([^.]*?)\\.conf")).forEach(value -> { try { Sirius.LOG.INFO("Loading config: %s", value.group()); result.set(result.get().withFallback(ConfigFactory.load(getLoader(), value.group()))); } catch (Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load %s: %s", value, e.getMessage()); } }); if (Sirius.class.getResource("/application.conf") != null) { Sirius.LOG.INFO("using application.conf from classpath..."); try { result.set(ConfigFactory.load(loader, "application.conf").withFallback(result.get())); } catch (Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load application.conf: %s", e.getMessage()); Sirius.LOG.WARN("Cannot load application.conf: %s", e.getMessage()); } } else { Sirius.LOG.INFO("application.conf not present in classpath"); } return result.get(); } /** * Applies the test configuration to the given config object. *

* By default this loads and applies "test.conf" from the classpath. * * @param config the config to enhance * @return the enhanced config */ @Nonnull public Config applyTestConfig(@Nonnull Config config) { if (Sirius.class.getResource("/test.conf") != null) { Sirius.LOG.INFO("using test.conf from classpath..."); try { return ConfigFactory.load(loader, "test.conf").withFallback(config); } catch (Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load test.conf: %s", e.getMessage()); return config; } } else { Sirius.LOG.INFO("test.conf not present in classpath"); return config; } } /** * Loads the configuration of the current test scenario which will overwrite the settings * in test.conf. * * @param scenarioFile the scenario config file to load * @param config the config to enhance * @return the enhanced config */ @Nonnull public Config applyTestScenarioConfig(@Nullable String scenarioFile, @Nonnull Config config) { if (Strings.isEmpty(scenarioFile)) { return config; } Sirius.LOG.INFO("using %s from classpath...", scenarioFile); try { return ConfigFactory.load(loader, scenarioFile).withFallback(config); } catch (Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load test.conf: %s", e.getMessage()); return config; } } /** * Applies the developer configuration to the given config object. *

* By default this loads and applies "develop.conf" from the file system. * * @param config the config to enhance * @return the enhanced config */ @Nonnull public Config applyDeveloperConfig(@Nonnull Config config) { if (new File("develop.conf").exists()) { Sirius.LOG.INFO("using develop.conf from filesystem..."); return ConfigFactory.parseFile(new File("develop.conf")).withFallback(config); } else { Sirius.LOG.INFO("develop.conf not present in work directory"); return config; } } /** * Loads the instance configuration which configures the app for the machine it is running on. *

* By default this loads "instance.conf" from the file system *

* This will later be applied to the overall system configuration and will override all other settings. * * @return the instance configuration or null if no config was found. */ @Nullable public Config loadInstanceConfig() { if (new File("instance.conf").exists()) { Sirius.LOG.INFO("using instance.conf from filesystem..."); try { return ConfigFactory.parseFile(new File("instance.conf")); } catch (Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load instance.conf: %s", e.getMessage()); return null; } } else { Sirius.LOG.INFO("instance.conf not present work in directory"); return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy