package ru.gravit.utils.helper; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi.Color; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiOutputStream; import ru.gravit.launcher.Launcher; import ru.gravit.launcher.LauncherAPI; public final class LogHelper { @LauncherAPI public static final String DEBUG_PROPERTY = "launcher.debug"; @LauncherAPI public static final String STACKTRACE_PROPERTY = "launcher.stacktrace"; @LauncherAPI public static final String NO_JANSI_PROPERTY = "launcher.noJAnsi"; @LauncherAPI public static final boolean JANSI; // Output settings private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss", Locale.US); private static final AtomicBoolean DEBUG_ENABLED = new AtomicBoolean(Boolean.getBoolean(DEBUG_PROPERTY)); private static final AtomicBoolean STACKTRACE_ENABLED = new AtomicBoolean(Boolean.getBoolean(STACKTRACE_PROPERTY)); private static final Set OUTPUTS = Collections.newSetFromMap(new ConcurrentHashMap<>(2)); private static final Output STD_OUTPUT; private LogHelper() { } @LauncherAPI public static void addOutput(Output output) { OUTPUTS.add(Objects.requireNonNull(output, "output")); } @LauncherAPI public static void addOutput(Path file) throws IOException { if (JANSI) { addOutput(new JAnsiOutput(IOHelper.newOutput(file, true))); } else { addOutput(IOHelper.newWriter(file, true)); } } @LauncherAPI public static void addOutput(Writer writer) { addOutput(new WriterOutput(writer)); } @LauncherAPI public static void debug(String message) { if (isDebugEnabled()) { log(Level.DEBUG, message, false); } } @LauncherAPI public static void debug(String format, Object... args) { debug(String.format(format, args)); } @LauncherAPI public static void error(Throwable exc) { error(isStacktraceEnabled() ? toString(exc) : exc.toString()); } @LauncherAPI public static void error(String message) { log(Level.ERROR, message, false); } @LauncherAPI public static void error(String format, Object... args) { error(String.format(format, args)); } @LauncherAPI public static void info(String message) { log(Level.INFO, message, false); } @LauncherAPI public static void info(String format, Object... args) { info(String.format(format, args)); } @LauncherAPI public static boolean isDebugEnabled() { return DEBUG_ENABLED.get(); } @LauncherAPI public static void setDebugEnabled(boolean debugEnabled) { DEBUG_ENABLED.set(debugEnabled); } @LauncherAPI public static boolean isStacktraceEnabled() { return STACKTRACE_ENABLED.get(); } @LauncherAPI public static void setStacktraceEnabled(boolean stacktraceEnabled) { STACKTRACE_ENABLED.set(stacktraceEnabled); } @LauncherAPI public static void log(Level level, String message, boolean sub) { String dateTime = DATE_TIME_FORMATTER.format(LocalDateTime.now()); println(JANSI ? ansiFormatLog(level, dateTime, message, sub) : formatLog(level, message, dateTime, sub)); } @LauncherAPI public static void printVersion(String product) { println(JANSI ? ansiFormatVersion(product) : formatVersion(product)); } @LauncherAPI public static void printLicense(String product) { println(JANSI ? ansiFormatLicense(product) : formatLicense(product)); } @LauncherAPI public static synchronized void println(String message) { for (Output output : OUTPUTS) { output.println(message); } } @LauncherAPI public static boolean removeOutput(Output output) { return OUTPUTS.remove(output); } @LauncherAPI public static boolean removeStdOutput() { return removeOutput(STD_OUTPUT); } @LauncherAPI public static void subDebug(String message) { if (isDebugEnabled()) { log(Level.DEBUG, message, true); } } @LauncherAPI public static void subDebug(String format, Object... args) { subDebug(String.format(format, args)); } @LauncherAPI public static void subInfo(String message) { log(Level.INFO, message, true); } @LauncherAPI public static void subInfo(String format, Object... args) { subInfo(String.format(format, args)); } @LauncherAPI public static void subWarning(String message) { log(Level.WARNING, message, true); } @LauncherAPI public static void subWarning(String format, Object... args) { subWarning(String.format(format, args)); } @LauncherAPI public static String toString(Throwable exc) { try (StringWriter sw = new StringWriter()) { try (PrintWriter pw = new PrintWriter(sw)) { exc.printStackTrace(pw); } return sw.toString(); } catch (IOException e) { throw new InternalError(e); } } @LauncherAPI public static void warning(String message) { log(Level.WARNING, message, false); } @LauncherAPI public static void warning(String format, Object... args) { warning(String.format(format, args)); } private static String ansiFormatLog(Level level, String dateTime, String message, boolean sub) { Color levelColor; boolean bright = level != Level.DEBUG; switch (level) { case WARNING: levelColor = Color.YELLOW; break; case ERROR: levelColor = Color.RED; break; default: // INFO, DEBUG, Unknown levelColor = Color.WHITE; break; } // Date-time Ansi ansi = new Ansi(); ansi.fg(Color.WHITE).a(dateTime); // Level ansi.fgBright(Color.WHITE).a(" [").bold(); if (bright) { ansi.fgBright(levelColor); } else { ansi.fg(levelColor); } ansi.a(level).boldOff().fgBright(Color.WHITE).a("] "); // Message if (bright) { ansi.fgBright(levelColor); } else { ansi.fg(levelColor); } if (sub) { ansi.a(' ').a(Ansi.Attribute.ITALIC); } ansi.a(message); // Finish with reset code return ansi.reset().toString(); } private static String ansiFormatVersion(String product) { return new Ansi().bold(). // Setup fgBright(Color.MAGENTA).a("GravitLauncher "). // sashok724's fgBright(Color.BLUE).a("(fork sashok724's Launcher) "). fgBright(Color.CYAN).a(product). // Product fgBright(Color.WHITE).a(" v").fgBright(Color.BLUE).a(Launcher.getVersion().toString()). // Version fgBright(Color.WHITE).a(" (build #").fgBright(Color.RED).a(Launcher.getVersion().build).fgBright(Color.WHITE).a(')'). // Build# reset().toString(); // To string } private static String ansiFormatLicense(String product) { return new Ansi().bold(). // Setup fgBright(Color.MAGENTA).a("License for "). // sashok724's fgBright(Color.CYAN).a(product). // Product fgBright(Color.WHITE).a(" GPLv3").fgBright(Color.WHITE).a(". SourceCode: "). // Version fgBright(Color.YELLOW).a("https://github.com/GravitLauncher/Launcher"). reset().toString(); // To string } private static String formatLog(Level level, String message, String dateTime, boolean sub) { if (sub) { message = ' ' + message; } return dateTime + " [" + level.name + "] " + message; } private static String formatVersion(String product) { return String.format("GravitLauncher (fork sashok724's Launcher) %s v%s", product, Launcher.getVersion().toString()); } private static String formatLicense(String product) { return String.format("License for %s GPLv3. SourceCode: https://github.com/GravitLauncher/Launcher", product); } static { // Use JAnsi if available boolean jansi; try { if (Boolean.getBoolean(NO_JANSI_PROPERTY)) { jansi = false; } else { Class.forName("org.fusesource.jansi.Ansi"); AnsiConsole.systemInstall(); jansi = true; } } catch (ClassNotFoundException ignored) { jansi = false; } JANSI = jansi; // Add std writer STD_OUTPUT = System.out::println; addOutput(STD_OUTPUT); // Add file log writer String logFile = System.getProperty("launcher.logFile"); if (logFile != null) { try { addOutput(IOHelper.toPath(logFile)); } catch (IOException e) { error(e); } } } @LauncherAPI @FunctionalInterface public interface Output { void println(String message); } @LauncherAPI public enum Level { DEBUG("DEBUG"), INFO("INFO"), WARNING("WARN"), ERROR("ERROR"); public final String name; Level(String name) { this.name = name; } @Override public String toString() { return name; } } private static final class JAnsiOutput extends WriterOutput { private JAnsiOutput(OutputStream output) { super(IOHelper.newWriter(new AnsiOutputStream(output))); } } private static class WriterOutput implements Output, AutoCloseable { private final Writer writer; private WriterOutput(Writer writer) { this.writer = writer; } @Override public void close() throws IOException { writer.close(); } @Override public void println(String message) { try { writer.write(message + System.lineSeparator()); writer.flush(); } catch (IOException ignored) { // Do nothing? } } } }