Преглед на файлове

Replaced simple logger by CollectedLogger

Dominik Fuchß преди 4 години
родител
ревизия
119ab694e5

+ 0 - 4
jplag.cli/pom.xml

@@ -16,10 +16,6 @@
             <groupId>net.sourceforge.argparse4j</groupId>
             <artifactId>argparse4j</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-simple</artifactId>
-        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 4 - 0
jplag.cli/src/main/java/de/jplag/CLI.java

@@ -7,6 +7,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Random;
 
+import de.jplag.exceptions.RootDirectoryException;
 import net.sourceforge.argparse4j.ArgumentParsers;
 import net.sourceforge.argparse4j.inf.ArgumentParser;
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
@@ -27,6 +28,7 @@ import de.jplag.options.Verbosity;
 import de.jplag.reporting.JsonReport;
 import de.jplag.reporting.Report;
 import de.jplag.strategy.ComparisonMode;
+import de.jplag.logger.CollectedLogger;
 
 /**
  * Command line interface class, allows using via command line.
@@ -62,8 +64,10 @@ public class CLI {
             JPlagResult result = program.run();
             Report report = new JsonReport();
             report.saveReport(result, arguments.getString(RESULT_FOLDER.flagWithoutDash()));
+            CollectedLogger.finalizeInstances();
         } catch (ExitException exception) {
             logger.error(exception.getMessage(), exception);
+            CollectedLogger.finalizeInstances();
             System.exit(1);
         }
     }

+ 280 - 0
jplag.cli/src/main/java/de/jplag/logger/CollectedLogger.java

@@ -0,0 +1,280 @@
+package de.jplag.logger;
+
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MarkerIgnoringBase;
+import org.slf4j.helpers.MessageFormatter;
+import org.slf4j.spi.LocationAwareLogger;
+
+import java.io.PrintStream;
+import java.io.Serial;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+/**
+ * This logger is able to collect errors and print them at the end.
+ * Mainly adopted from org.slf4j.impl.SimpleLogger
+ *
+ * @author Dominik Fuchss
+ */
+public final class CollectedLogger extends MarkerIgnoringBase {
+
+	private static final ConcurrentLinkedDeque<CollectedLogger> instances = new ConcurrentLinkedDeque<>();
+
+	public static void finalizeInstances() {
+		List<CollectedLogger> copy = new ArrayList<>(instances);
+		copy.forEach(instances::remove);
+		copy.forEach(CollectedLogger::printAllErrorsForLogger);
+	}
+
+	@Serial
+	private static final long serialVersionUID = -1278670638921140275L;
+
+	private static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
+	private static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
+	private static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
+	private static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
+	private static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
+
+	private final int currentLogLevel = LOG_LEVEL_INFO;
+
+	/**
+	 * The short name of this simple log instance
+	 */
+	private transient String shortLogName = null;
+
+	/**
+	 * Indicator whether finalization is in progress.
+	 * @see #printAllErrorsForLogger()
+	 */
+	private transient boolean isFinalizing = false;
+
+	private final transient SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-hh:mm:ss_SSS");
+
+	private final ConcurrentLinkedDeque<Triple<String, Throwable, Date>> allErrors = new ConcurrentLinkedDeque<>();
+
+	CollectedLogger(String name) {
+		this.name = name;
+		instances.add(this);
+	}
+
+	private void log(int level, String message, Throwable t) {
+		log(level, message, t, null);
+	}
+
+	private void log(int level, String message, Throwable t, Date timeOfError) {
+		if (!isLevelEnabled(level)) {
+			return;
+		}
+
+		if (level == LOG_LEVEL_ERROR && !isFinalizing) {
+			// Buffer errors for the final output
+			allErrors.add(new Triple<>(message, t, new Date()));
+			return;
+		}
+
+		StringBuilder buf = new StringBuilder(32);
+
+		// Append date-time
+		buf.append(sdf.format(timeOfError == null ? new Date() : timeOfError)).append(' ');
+
+		// Append current thread name
+		buf.append('[').append(Thread.currentThread().getName()).append("] ");
+		// Append current Level
+		buf.append('[').append(renderLevel(level)).append(']').append(' ');
+
+		// Append the name of the log instance
+		if (shortLogName == null)
+			shortLogName = computeShortName();
+		buf.append(shortLogName).append(" - ");
+		// Append the message
+		buf.append(message);
+
+		write(buf, t);
+	}
+
+	private void printAllErrorsForLogger() {
+		this.isFinalizing = true;
+		// Copy errors to prevent infinite recursion
+		var errors = new ArrayList<>(this.allErrors);
+		if (errors.isEmpty())
+			return;
+
+		info("Summary of all Errors:");
+		errors.forEach(error -> log(LOG_LEVEL_ERROR, error.first(), error.second(), error.third()));
+		isFinalizing = false;
+	}
+
+	@SuppressWarnings("java:S106")
+	void write(StringBuilder buf, Throwable t) {
+		PrintStream targetStream = System.out;
+
+		targetStream.println(buf.toString());
+		writeThrowable(t, targetStream);
+		targetStream.flush();
+	}
+
+	private void writeThrowable(Throwable t, PrintStream targetStream) {
+		if (t != null) {
+			t.printStackTrace(targetStream);
+		}
+	}
+
+	private String computeShortName() {
+		return name.substring(name.lastIndexOf(".") + 1);
+	}
+
+	private boolean isLevelEnabled(int logLevel) {
+		return logLevel >= currentLogLevel;
+	}
+
+	private String renderLevel(int level) {
+		return switch (level) {
+			case LOG_LEVEL_TRACE -> "TRACE";
+			case LOG_LEVEL_DEBUG -> "DEBUG";
+			case LOG_LEVEL_INFO -> "INFO";
+			case LOG_LEVEL_WARN -> "WARN";
+			case LOG_LEVEL_ERROR -> "ERROR";
+			default -> throw new IllegalStateException("Unrecognized level [" + level + "]");
+		};
+	}
+
+	public boolean isTraceEnabled() {
+		return isLevelEnabled(LOG_LEVEL_TRACE);
+	}
+
+	@Override
+	public void trace(String msg) {
+		log(LOG_LEVEL_TRACE, msg, null);
+	}
+
+	public void trace(String format, Object param1) {
+		formatAndLog(LOG_LEVEL_TRACE, format, param1, null);
+	}
+
+	public void trace(String format, Object param1, Object param2) {
+		formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
+	}
+
+	public void trace(String format, Object... argArray) {
+		formatAndLog(LOG_LEVEL_TRACE, format, argArray);
+	}
+
+	public void trace(String msg, Throwable t) {
+		log(LOG_LEVEL_TRACE, msg, t);
+	}
+
+	public boolean isDebugEnabled() {
+		return isLevelEnabled(LOG_LEVEL_DEBUG);
+	}
+
+	public void debug(String msg) {
+		log(LOG_LEVEL_DEBUG, msg, null);
+	}
+
+	public void debug(String format, Object param1) {
+		formatAndLog(LOG_LEVEL_DEBUG, format, param1, null);
+	}
+
+	public void debug(String format, Object param1, Object param2) {
+		formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
+	}
+
+	public void debug(String format, Object... argArray) {
+		formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
+	}
+
+	public void debug(String msg, Throwable t) {
+		log(LOG_LEVEL_DEBUG, msg, t);
+	}
+
+	public boolean isInfoEnabled() {
+		return isLevelEnabled(LOG_LEVEL_INFO);
+	}
+
+	public void info(String msg) {
+		log(LOG_LEVEL_INFO, msg, null);
+	}
+
+	public void info(String format, Object arg) {
+		formatAndLog(LOG_LEVEL_INFO, format, arg, null);
+	}
+
+	public void info(String format, Object arg1, Object arg2) {
+		formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
+	}
+
+	public void info(String format, Object... argArray) {
+		formatAndLog(LOG_LEVEL_INFO, format, argArray);
+	}
+
+	public void info(String msg, Throwable t) {
+		log(LOG_LEVEL_INFO, msg, t);
+	}
+
+	public boolean isWarnEnabled() {
+		return isLevelEnabled(LOG_LEVEL_WARN);
+	}
+
+	public void warn(String msg) {
+		log(LOG_LEVEL_WARN, msg, null);
+	}
+
+	public void warn(String format, Object arg) {
+		formatAndLog(LOG_LEVEL_WARN, format, arg, null);
+	}
+
+	public void warn(String format, Object arg1, Object arg2) {
+		formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
+	}
+
+	public void warn(String format, Object... argArray) {
+		formatAndLog(LOG_LEVEL_WARN, format, argArray);
+	}
+
+	public void warn(String msg, Throwable t) {
+		log(LOG_LEVEL_WARN, msg, t);
+	}
+
+	public boolean isErrorEnabled() {
+		return isLevelEnabled(LOG_LEVEL_ERROR);
+	}
+
+	public void error(String msg) {
+		log(LOG_LEVEL_ERROR, msg, null);
+	}
+
+	public void error(String format, Object arg) {
+		formatAndLog(LOG_LEVEL_ERROR, format, arg, null);
+	}
+
+	public void error(String format, Object arg1, Object arg2) {
+		formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
+	}
+
+	public void error(String format, Object... argArray) {
+		formatAndLog(LOG_LEVEL_ERROR, format, argArray);
+	}
+
+	public void error(String msg, Throwable t) {
+		log(LOG_LEVEL_ERROR, msg, t);
+	}
+
+	private void formatAndLog(int level, String format, Object arg1, Object arg2) {
+		if (!isLevelEnabled(level)) {
+			return;
+		}
+		FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
+		log(level, tp.getMessage(), tp.getThrowable());
+	}
+
+	private void formatAndLog(int level, String format, Object... arguments) {
+		if (!isLevelEnabled(level)) {
+			return;
+		}
+		FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
+		log(level, tp.getMessage(), tp.getThrowable());
+	}
+}

+ 48 - 0
jplag.cli/src/main/java/de/jplag/logger/CollectedLoggerFactory.java

@@ -0,0 +1,48 @@
+package de.jplag.logger;
+
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * The factory for {@link CollectedLogger}.
+ *
+ * @author Dominik Fuchss
+ */
+public final class CollectedLoggerFactory implements ILoggerFactory {
+
+	private final ConcurrentMap<String, Logger> loggerMap;
+
+	public CollectedLoggerFactory() {
+		loggerMap = new ConcurrentHashMap<>();
+	}
+
+	/**
+	 * Return an appropriate {@link CollectedLogger} instance by name.
+	 */
+	public Logger getLogger(String name) {
+		Logger simpleLogger = loggerMap.get(name);
+		if (simpleLogger != null) {
+			return simpleLogger;
+		} else {
+			Logger newInstance = new CollectedLogger(name);
+			Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
+			return oldInstance == null ? newInstance : oldInstance;
+		}
+	}
+
+	/**
+	 * Clear the internal logger cache.
+	 * <p>
+	 * This method is intended to be called by classes (in the same package) for
+	 * testing purposes. This method is internal. It can be modified, renamed or
+	 * removed at any time without notice.
+	 * <p>
+	 * You are strongly discouraged from calling this method in production code.
+	 */
+	void reset() {
+		loggerMap.clear();
+	}
+}

+ 4 - 0
jplag.cli/src/main/java/de/jplag/logger/Triple.java

@@ -0,0 +1,4 @@
+package de.jplag.logger;
+
+public record Triple<A, B, C>(A first, B second, C third) {
+}

+ 80 - 0
jplag.cli/src/main/java/org/slf4j/impl/StaticLoggerBinder.java

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ * <p>
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ * <p>
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ * <p>
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.slf4j.impl;
+
+import de.jplag.logger.CollectedLoggerFactory;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.LoggerFactoryBinder;
+
+/**
+ * The binding of {@link LoggerFactory} class with an actual instance of
+ * {@link ILoggerFactory} is performed using information returned by this class.
+ * The class has been adopted from SimpleLogger.
+ *
+ * @author Dominik Fuchss
+ */
+public class StaticLoggerBinder implements LoggerFactoryBinder {
+
+	/**
+	 * The unique instance of this class.
+	 */
+	private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
+
+	/**
+	 * Return the singleton of this class.
+	 *
+	 * @return the StaticLoggerBinder singleton
+	 */
+	public static StaticLoggerBinder getSingleton() {
+		return SINGLETON;
+	}
+
+	/**
+	 * Declare the version of the SLF4J API this implementation is compiled against.
+	 * The value of this field is modified with each major release.
+	 */
+	// to avoid constant folding by the compiler, this field must *not* be final
+	public static String REQUESTED_API_VERSION = "1.6.99"; // !final
+
+	private static final String loggerFactoryClassStr = CollectedLoggerFactory.class.getName();
+
+	/**
+	 * The ILoggerFactory instance returned by the {@link #getLoggerFactory}
+	 * method should always be the same object
+	 */
+	private final ILoggerFactory loggerFactory;
+
+	private StaticLoggerBinder() {
+		loggerFactory = new CollectedLoggerFactory();
+	}
+
+	public ILoggerFactory getLoggerFactory() {
+		return loggerFactory;
+	}
+
+	public String getLoggerFactoryClassStr() {
+		return loggerFactoryClassStr;
+	}
+}