/* * This file is part of FalsePatternLib. * * Copyright (C) 2022-2025 FalsePattern * All Rights Reserved * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * FalsePatternLib is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, only version 3 of the License. * * FalsePatternLib 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FalsePatternLib. If not, see . */ package com.falsepattern.lib.internal.logging; import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Tags; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.spi.LoggerContextFactory; import net.minecraft.launchwrapper.Launch; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; // This class is a port of NotEnoughVerbosity, licensed under the Unlicense // It has been merged into FalsePatternLib so that it's available inside more modpacks // CHANGES: // This variant will inspect the blackboard for an entry called "notenoughverbosity". If it's present, it will no-op. // This will allow other people's lib mods to pull in this class without each one of them trying to reapply. public class NotEnoughVerbosity { public static final Logger LOGGER = FPLog.LOG; public static void apply() { try { if (needsToRun()) { reconfigureLog4j(); } } catch (Throwable t) { LOGGER.error("NotEnoughVerbosity failed to apply!", t); } } private static boolean needsToRun() { try { Class.forName("io.github.legacymoddingmc.notenoughverbosity.NotEnoughVerbosity"); // OG mod already present, do nothing return false; } catch (Throwable ignored) {} if (Launch.blackboard.containsKey("notenoughverbosity")) { // Other mod already present return false; } boolean foundBadRoot = false; for (LoggerContext context : getLoggerContexts()) { LoggerConfig rootConfig = context.getConfiguration().getLoggers().get(""); if(rootConfig != null) { if(rootConfig.getLevel().intLevel() < Level.ALL.intLevel()) { LOGGER.warn("Found root logger with verbosity " + rootConfig.getLevel() + ", will try to switch to Forge config."); foundBadRoot = true; break; } else { LOGGER.debug("Found root logger with verbosity " + rootConfig.getLevel() + ", this is fine."); } } else { throw new RuntimeException("Couldn't find root logger."); } } if(!foundBadRoot) { LOGGER.debug("Root config seems fine, log4j will not be reconfigured."); } return foundBadRoot; } public static void reconfigureLog4j() { URI log4jUri = findForgeLog4j(); if (log4jUri != null) { LOGGER.info("Reconfiguring logger to use config at " + log4jUri); LOGGER.info("New messages will go to fml-x-latest.log"); String cookie = UUID.randomUUID().toString(); LOGGER.info("Magic cookie: " + cookie); setLog4jConfig(log4jUri); int count = getLoggerContexts().size(); LOGGER.info("Reconfigured logger (" + count + " context" + (count != 1 ? "s" : "") + ") to use config at " + log4jUri); LOGGER.info("Earlier messages may be located in latest.log"); List latestLogLines = readLatestLog(cookie); if(latestLogLines != null) { LOGGER.info("Found earlier messages:\n-----Begin latest.log-----\n" + String.join("\n", latestLogLines) + "\n-----End latest.log-----"); } Launch.blackboard.put("notenoughverbosity", Tags.MODID); } else { LOGGER.warn("Could not find Forge's log4j2.xml on classpath, doing nothing"); } } private static List readLatestLog(String cookie) { File latestLog = new File(Launch.minecraftHome, "logs/latest.log"); if(!latestLog.exists()) { LOGGER.debug("Couldn't find latest.log at " + latestLog.getAbsolutePath()); return null; } long lastModified = latestLog.lastModified(); if(lastModified < System.currentTimeMillis() - 1000 * 60) { LOGGER.debug("latest.log at " + latestLog.getAbsolutePath() + " is too old (timestamp: " + lastModified + ")"); return null; } try(InputStream is = new FileInputStream(latestLog)) { List lines = readLines(is); if(lines.stream().anyMatch(l -> l.contains(cookie))) { return lines; } else { LOGGER.debug("Failed to find magic cookie in latest.log at " + latestLog.getAbsolutePath()); } } catch (IOException e) { LOGGER.error("Failed to read latest.log at " + latestLog.getAbsolutePath(), e); } return null; } private static void setLog4jConfig(URI uri) { for (LoggerContext context : getLoggerContexts()) { context.setConfigLocation(uri); context.reconfigure(); } } private static Collection getLoggerContexts() { LoggerContextFactory loggerContextFactory = LogManager.getFactory(); if (loggerContextFactory instanceof Log4jContextFactory) { return ((Log4jContextFactory) loggerContextFactory).getSelector().getLoggerContexts(); } else { throw new IllegalStateException("Logger context factory is not a Log4jContextFactory"); } } private static URI findForgeLog4j() { try { List candidates = Collections.list(NotEnoughVerbosity.class.getClassLoader().getResources("log4j2.xml")); LOGGER.info("Logger configs on classpath: " + candidates); for(URL url : candidates) { try { if(readLines(url.openStream()).stream().anyMatch(l -> l.contains("Root level=\"all\""))) { return url.toURI(); } } catch(Exception e) { LOGGER.error("Failed to read inspect logger config at URL " + url, e); } } } catch(Exception e) { LOGGER.error("Exception enumerating logger configs", e); } return null; } private static List readLines(InputStream stream) { return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.toList()); } }