diff --git a/base/src/main/java/de/staropensource/sosengine/base/Engine.java b/base/src/main/java/de/staropensource/sosengine/base/Engine.java index 13b2913..a14fdb6 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/Engine.java +++ b/base/src/main/java/de/staropensource/sosengine/base/Engine.java @@ -19,7 +19,6 @@ package de.staropensource.sosengine.base; -import de.staropensource.sosengine.base.classes.Event; import de.staropensource.sosengine.base.classes.SubsystemMainClass; import de.staropensource.sosengine.base.classes.helpers.EventHelper; import de.staropensource.sosengine.base.data.info.EngineInformation; @@ -28,6 +27,7 @@ import de.staropensource.sosengine.base.events.EngineShutdownEvent; import de.staropensource.sosengine.base.events.LogEvent; import de.staropensource.sosengine.base.events.internal.InternalEngineShutdownEvent; import de.staropensource.sosengine.base.logging.CrashHandler; +import de.staropensource.sosengine.base.logging.Logger; import de.staropensource.sosengine.base.logging.LoggerInstance; import de.staropensource.sosengine.base.types.CodePart; import de.staropensource.sosengine.base.types.LogIssuer; @@ -110,6 +110,9 @@ public final class Engine implements SubsystemMainClass { // Precompute event listeners precomputeEventListeners(); + // Start threads + startThreads(); + // Initialize variables logger = new LoggerInstance(new LogIssuer(getClass(), CodePart.ENGINE)); }); @@ -199,6 +202,15 @@ public final class Engine implements SubsystemMainClass { EventHelper.precomputeEventListeners(LogEvent.class); } + /** + * Starts base engine threads. + * + * @since 1-alpha1 + */ + public void startThreads() { + Logger.startLoggingThread(); + } + /** * Shuts the engine and JVM down. * @@ -213,6 +225,7 @@ public final class Engine implements SubsystemMainClass { logger.verb("Notifying subsystems about shutdown"); new InternalEngineShutdownEvent().callEvent(); logger.verb("Shutting down JVM with code " + exitCode); + Logger.flushLogMessages(); Runtime.getRuntime().exit(exitCode); } diff --git a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java index eb158af..6a854bf 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java +++ b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java @@ -185,10 +185,27 @@ public final class EngineConfiguration implements SubsystemConfiguration { */ private boolean loggerForceStandardOutput; + /** + * Determines how fast the logger will poll for messages. + * Only applies if {@code optimizeLogging} is turned on. + * + * @see EngineConfiguration#optimizeLogging + * @since 1-alpha0 + * + * -- GETTER -- + * Gets the value for {@code loggerForceStandardOutput}. + * + * @return variable value + * @see EngineConfiguration#loggerForceStandardOutput + * @since 1-alpha0 + */ + private int loggerPollingSpeed; + /** * If enabled, will makes the {@link Logger} work asynchronous. * Don't disable unless you want your application to run extremely slowly. * + * @see EngineConfiguration#loggerPollingSpeed * @since 1-alpha0 * * -- GETTER -- @@ -262,6 +279,7 @@ public final class EngineConfiguration implements SubsystemConfiguration { case "loggerTemplate" -> loggerTemplate = parser.getString(group + property); case "loggerImmediateShutdown" -> loggerImmediateShutdown = parser.getBoolean(group + property); case "loggerForceStandardOutput" -> loggerForceStandardOutput = parser.getBoolean(group + property); + case "loggerPollingSpeed" -> loggerPollingSpeed = parser.getInteger(group + property, true); case "optimizeLogging" -> optimizeLogging = parser.getBoolean(group + property); case "optimizeEvents" -> optimizeEvents = parser.getBoolean(group + property); @@ -293,6 +311,7 @@ public final class EngineConfiguration implements SubsystemConfiguration { loggerTemplate = "%log_color_primary%[%time_hour%:%time_minute%:%time_second%] [%log_level% %log_path%%log_info%] %log_color_secondary%%log_message%"; loggerImmediateShutdown = false; loggerForceStandardOutput = false; + loggerPollingSpeed = 25; optimizeLogging = true; optimizeEvents = true; @@ -328,6 +347,9 @@ public final class EngineConfiguration implements SubsystemConfiguration { case "loggerForceStandardOutput" -> { return loggerForceStandardOutput; } + case "loggerPollingSpeed" -> { + return loggerPollingSpeed; + } case "optimizeLogging" -> { return optimizeLogging; diff --git a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java index 3cb7fed..1a18e92 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java +++ b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java @@ -31,8 +31,10 @@ import de.staropensource.sosengine.base.utility.PlaceholderEngine; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -70,6 +72,28 @@ public final class Logger { @Setter private static LoggerImpl loggerImplementation = new DefaultLoggerImpl(); + /** + * Refers to the logging thread. + * + * @since 1-alpha1 + * + * -- GETTER -- + * Returns the logging thread. + * + * @return logging thread + * @since 1-alpha1 + */ + @Getter + @Nullable + private static Thread loggingThread; + + /** + * A list of {@link QueuedLogMessage}s. + * + * @since 1-alpha1 + */ + private static final LinkedList queuedMessages = new LinkedList<>(); + /** * Constructor. * @@ -77,60 +101,115 @@ public final class Logger { */ public Logger() {} + /** + * Starts the logging thread. + * + * @since 1-alpha1 + * @see Logger#getLoggingThread() + */ + public static void startLoggingThread() { + if (loggingThread == null) { + loggingThread = Thread.ofPlatform().start(() -> { + while (true) { + if (Engine.getInstance().isShuttingDown()) + return; + + flushLogMessages(); + + try { + //noinspection BusyWait + Thread.sleep(EngineConfiguration.getInstance().getLoggerPollingSpeed()); + } catch (Exception ignored) { + } + + if (Engine.getInstance().isShuttingDown()) + return; + } + }); + } else { + if (!loggingThread.isAlive()) + loggingThread.start(); + } + } + + /** + * Prints all queued log messages. + * + * @since 1-alpha1 + */ + public static synchronized void flushLogMessages() { + if (!queuedMessages.isEmpty()) { + //noinspection unchecked + LinkedList queuedMessagesCloned = (LinkedList) queuedMessages.clone(); + queuedMessages.clear(); + + for (QueuedLogMessage queuedLogMessage : queuedMessagesCloned) {// Check if level is allowed + processLogMessage(queuedLogMessage.getIssuer(), queuedLogMessage.getLevel(), queuedLogMessage.getMessage()); + } + } + } + + /** + * Processes a log message. + * + * @param issuer log issuer + * @param level log level + * @param message log message + * @since 1-alpha1 + */ + private static void processLogMessage(@NotNull LogIssuer issuer, @NotNull LogLevel level, @NotNull String message) { + if (level.compareTo(EngineConfiguration.getInstance().getLoggerLevel()) < 0) + return; + + // Template for now, final log message later + String base = EngineConfiguration.getInstance().getLoggerTemplate(); + + // Execute LoggerImpl#prePlaceholder + base = loggerImplementation.prePlaceholder(level, issuer, base); + + // Create list of temporary placeholders + List<@NotNull Placeholder> temporaryPlaceholders = new ArrayList<>(); + temporaryPlaceholders.add(new LogMessage(message)); // log_message is out of order to allow for placeholder usage + + temporaryPlaceholders.add(new LogColorPrimary(level)); + temporaryPlaceholders.add(new LogColorSecondary(level)); + temporaryPlaceholders.add(new LogClass(issuer)); + temporaryPlaceholders.add(new LogColorPrimary(level)); + temporaryPlaceholders.add(new LogInfo(issuer)); + temporaryPlaceholders.add(new de.staropensource.sosengine.base.logging.placeholders.logger.LogLevel(level)); + temporaryPlaceholders.add(new LogPackage(issuer)); + temporaryPlaceholders.add(new LogPath(issuer)); + + // Replace placeholders + base = PlaceholderEngine.getInstance().process(base, temporaryPlaceholders); + + // Execute LoggerImpl#postPlaceholder + base = loggerImplementation.postPlaceholder(level, issuer, base); + + // Call event + if (!issuer.getClazz().getName().equals("de.staropensource.sosengine.slf4j_compat.CompatibilityLogger")) + new LogEvent().callEvent(level, issuer, message); + + // Print log message + loggerImplementation.print(level, issuer, base); + } + /** * Handler for all log messages. * - * @param level log level - * @param logIssuer log issuer - * @param message log message + * @param issuer log issuer + * @param level log level + * @param message log message * @since 1-alpha0 */ - private static void log(@NotNull LogLevel level, @NotNull LogIssuer logIssuer, @NotNull String message) { - Runnable loggingCode = () -> { - // Check if engine has initialized - if (Engine.getInstance() == null) return; - - // Check if level is allowed - if (level.compareTo(EngineConfiguration.getInstance().getLoggerLevel()) < 0) - return; - - // Template for now, final log message later - String base = EngineConfiguration.getInstance().getLoggerTemplate(); - - // Execute LoggerImpl#prePlaceholder - base = loggerImplementation.prePlaceholder(level, logIssuer, base); - - // Create list of temporary placeholders - List<@NotNull Placeholder> temporaryPlaceholders = new ArrayList<>(); - temporaryPlaceholders.add(new LogMessage(message)); // log_message is out of order to allow for placeholder usage - - temporaryPlaceholders.add(new LogColorPrimary(level)); - temporaryPlaceholders.add(new LogColorSecondary(level)); - temporaryPlaceholders.add(new LogClass(logIssuer)); - temporaryPlaceholders.add(new LogColorPrimary(level)); - temporaryPlaceholders.add(new LogInfo(logIssuer)); - temporaryPlaceholders.add(new de.staropensource.sosengine.base.logging.placeholders.logger.LogLevel(level)); - temporaryPlaceholders.add(new LogPackage(logIssuer)); - temporaryPlaceholders.add(new LogPath(logIssuer)); - - // Replace placeholders - base = PlaceholderEngine.getInstance().process(base, temporaryPlaceholders); - - // Execute LoggerImpl#postPlaceholder - base = loggerImplementation.postPlaceholder(level, logIssuer, base); - - // Call event - if (!logIssuer.getClazz().getName().equals("de.staropensource.sosengine.slf4j_compat.CompatibilityLogger")) - new LogEvent().callEvent(level, logIssuer, message); - - // Print log message - loggerImplementation.print(level, logIssuer, base); - }; + private static synchronized void log(@NotNull LogIssuer issuer, @NotNull LogLevel level, @NotNull String message) { + // Check if engine has initialized + if (Engine.getInstance() == null) return; if (EngineConfiguration.getInstance().isOptimizeLogging()) - Thread.ofVirtual().start(loggingCode); + queuedMessages.add(new QueuedLogMessage(issuer, level, message)); else - loggingCode.run(); + processLogMessage(issuer, level, message); } /** @@ -141,7 +220,7 @@ public final class Logger { * @since 1-alpha0 */ public static void diag(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.DIAGNOSTIC, logIssuer, message); + log(logIssuer, LogLevel.DIAGNOSTIC, message); } /** @@ -152,7 +231,7 @@ public final class Logger { * @since 1-alpha0 */ public static void verb(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.VERBOSE, logIssuer, message); + log(logIssuer, LogLevel.VERBOSE, message); } /** @@ -163,7 +242,7 @@ public final class Logger { * @since 1-alpha0 */ public static void sarn(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.SILENT_WARNING, logIssuer, message); + log(logIssuer, LogLevel.SILENT_WARNING, message); } /** @@ -174,7 +253,7 @@ public final class Logger { * @since 1-alpha0 */ public static void info(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.INFORMATIONAL, logIssuer, message); + log(logIssuer, LogLevel.INFORMATIONAL, message); } /** @@ -185,7 +264,7 @@ public final class Logger { * @since 1-alpha0 */ public static void warn(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.WARNING, logIssuer, message); + log(logIssuer, LogLevel.WARNING, message); } /** @@ -196,7 +275,7 @@ public final class Logger { * @since 1-alpha0 */ public static void error(@NotNull LogIssuer logIssuer, @NotNull String message) { - log(LogLevel.ERROR, logIssuer, message); + log(logIssuer, LogLevel.ERROR, message); } /** diff --git a/base/src/main/java/de/staropensource/sosengine/base/logging/QueuedLogMessage.java b/base/src/main/java/de/staropensource/sosengine/base/logging/QueuedLogMessage.java new file mode 100644 index 0000000..d019739 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/logging/QueuedLogMessage.java @@ -0,0 +1,90 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.sosengine.base.logging; + +import de.staropensource.sosengine.base.types.LogIssuer; +import de.staropensource.sosengine.base.types.LogLevel; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a queued log message. + * + * @since 1-alpha1 + */ +@SuppressWarnings({ "unused", "JavadocDeclaration", "JavadocBlankLines" }) +@Getter +public class QueuedLogMessage { + /** + * The log message's issuer. + * + * @since 1-alpha1 + * + * -- GETTER -- + * Returns the log message's issuer. + * + * @return issuer + * @since 1-alpha1 + */ + @NotNull + private final LogIssuer issuer; + + /** + * The log message's level. + * + * @since 1-alpha1 + * + * -- GETTER -- + * Returns the log message's level. + * + * @return level + * @since 1-alpha1 + */ + @NotNull + private final LogLevel level; + + /** + * The log message. + * + * @since 1-alpha1 + * + * -- GETTER -- + * Returns the log message. + * + * @return message + * @since 1-alpha1 + */ + @NotNull + private final String message; + + /** + * Constructor. + * + * @param issuer log message issuer + * @param level log message level + * @param message log message + * @since 1-alpha1 + */ + public QueuedLogMessage(@NotNull LogIssuer issuer, @NotNull LogLevel level, @NotNull String message) { + this.issuer = issuer; + this.level = level; + this.message = message; + } +}