Skip to content

Commit 6d82ff2

Browse files
committed
logging improvements
1 parent eb80cd0 commit 6d82ff2

File tree

5 files changed

+355
-0
lines changed

5 files changed

+355
-0
lines changed

src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.falsepattern.lib.internal.FPLog;
2626
import com.falsepattern.lib.internal.Tags;
2727
import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl;
28+
import com.falsepattern.lib.internal.logging.CrashImprover;
29+
import com.falsepattern.lib.internal.logging.NotEnoughVerbosity;
2830
import com.falsepattern.lib.mapping.MappingManager;
2931
import com.falsepattern.lib.turboasm.MergeableTurboTransformer;
3032
import lombok.Getter;
@@ -75,6 +77,8 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin {
7577
FPLog.LOG.info("Scanned in " + (end - start) / 1000000 + "ms");
7678
//Initializing the rest
7779
MappingManager.initialize();
80+
NotEnoughVerbosity.apply();
81+
CrashImprover.probe();
7882
}
7983

8084
@SuppressWarnings("SameParameterValue")

src/main/java/com/falsepattern/lib/internal/asm/FPTransformer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.falsepattern.lib.StableAPI;
2727
import com.falsepattern.lib.internal.asm.transformers.ConfigOrderTransformer;
28+
import com.falsepattern.lib.internal.asm.transformers.CrashReportImprover;
2829
import com.falsepattern.lib.internal.asm.transformers.MixinPluginTransformer;
2930
import com.falsepattern.lib.internal.asm.transformers.TypeDiscovererModuleInfoSilencer;
3031
import com.falsepattern.lib.internal.impl.optifine.OptiFineTransformerHooksImpl;
@@ -42,6 +43,7 @@ public class FPTransformer extends MergeableTurboTransformer {
4243

4344
public FPTransformer() {
4445
super(Arrays.asList(new MixinPluginTransformer(),
46+
new CrashReportImprover(),
4547
new TypeDiscovererModuleInfoSilencer(),
4648
new ConfigOrderTransformer()));
4749
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* This file is part of FalsePatternLib.
3+
*
4+
* Copyright (C) 2022-2024 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalsePatternLib is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* FalsePatternLib is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Lesser General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Lesser General Public License
21+
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
package com.falsepattern.lib.internal.asm.transformers;
25+
26+
import com.falsepattern.lib.internal.Tags;
27+
import com.falsepattern.lib.mapping.MappingManager;
28+
import com.falsepattern.lib.mapping.types.MappingType;
29+
import com.falsepattern.lib.mapping.types.NameType;
30+
import com.falsepattern.lib.mapping.types.UniversalClass;
31+
import com.falsepattern.lib.turboasm.ClassNodeHandle;
32+
import com.falsepattern.lib.turboasm.TurboClassTransformer;
33+
import lombok.val;
34+
import org.jetbrains.annotations.NotNull;
35+
import org.objectweb.asm.Opcodes;
36+
import org.objectweb.asm.tree.InsnNode;
37+
import org.objectweb.asm.tree.MethodInsnNode;
38+
39+
public class CrashReportImprover implements TurboClassTransformer {
40+
41+
@Override
42+
public String owner() {
43+
return Tags.MODNAME;
44+
}
45+
46+
@Override
47+
public String name() {
48+
return "CrashReportImprover";
49+
}
50+
51+
@Override
52+
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
53+
return "net.minecraft.crash.CrashReport".equals(className);
54+
}
55+
56+
@Override
57+
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
58+
val cn = classNode.getNode();
59+
if (cn == null)
60+
return false;
61+
for (val method: cn.methods) {
62+
switch (method.name) {
63+
case "saveToFile", "func_147149_a" -> {}
64+
default -> {
65+
continue;
66+
}
67+
}
68+
val insnList = method.instructions.iterator();
69+
while (insnList.hasNext()) {
70+
val insn = insnList.next();
71+
if ( insn.getOpcode() == Opcodes.INVOKEVIRTUAL &&
72+
insn instanceof MethodInsnNode call &&
73+
"java/io/FileWriter".equals(call.owner) &&
74+
"close".equals(call.name) &&
75+
"()V".equals(call.desc)) {
76+
insnList.previous();
77+
insnList.add(new InsnNode(Opcodes.DUP));
78+
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/falsepattern/lib/internal/logging/CrashImprover", "injectLatest", "(Ljava/io/FileWriter;)V", false));
79+
return true;
80+
}
81+
}
82+
}
83+
return false;
84+
}
85+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* This file is part of FalsePatternLib.
3+
*
4+
* Copyright (C) 2022-2024 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalsePatternLib is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* FalsePatternLib is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Lesser General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Lesser General Public License
21+
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
package com.falsepattern.lib.internal.logging;
25+
26+
import com.falsepattern.lib.internal.FPLog;
27+
import lombok.val;
28+
29+
import net.minecraft.launchwrapper.Launch;
30+
31+
import java.io.BufferedReader;
32+
import java.io.File;
33+
import java.io.FileInputStream;
34+
import java.io.FileWriter;
35+
import java.io.IOException;
36+
import java.io.InputStream;
37+
import java.io.InputStreamReader;
38+
import java.util.Arrays;
39+
import java.util.List;
40+
import java.util.UUID;
41+
import java.util.stream.Collectors;
42+
43+
public class CrashImprover {
44+
private static final String cookie = UUID.randomUUID().toString();
45+
static {
46+
FPLog.LOG.info("Magic cookie: {}", cookie);
47+
}
48+
public static void probe() {
49+
50+
}
51+
52+
public static void injectLatest(FileWriter writer) {
53+
val potentialLogs = Arrays.asList(new File(Launch.minecraftHome, "logs/fml-client-latest.log"),
54+
new File(Launch.minecraftHome, "logs/fml-server-latest.log"));
55+
for (val file: potentialLogs) {
56+
try(InputStream is = new FileInputStream(file)) {
57+
List<String> lines = readLines(is);
58+
if(lines.stream().anyMatch(l -> l.contains(cookie))) {
59+
writer.append("\n\n").append("Complete log:").append('\n');
60+
for (String line : lines) {
61+
writer.append(line).append('\n');
62+
}
63+
}
64+
} catch (IOException e) {
65+
}
66+
}
67+
}
68+
private static List<String> readLines(InputStream stream) {
69+
return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.toList());
70+
}
71+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* This file is part of FalsePatternLib.
3+
*
4+
* Copyright (C) 2022-2024 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalsePatternLib is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* FalsePatternLib is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Lesser General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Lesser General Public License
21+
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
package com.falsepattern.lib.internal.logging;
25+
26+
import java.io.BufferedReader;
27+
import java.io.File;
28+
import java.io.FileInputStream;
29+
import java.io.IOException;
30+
import java.io.InputStream;
31+
import java.io.InputStreamReader;
32+
import java.net.URI;
33+
import java.net.URL;
34+
import java.util.Collection;
35+
import java.util.Collections;
36+
import java.util.List;
37+
import java.util.UUID;
38+
import java.util.stream.Collectors;
39+
40+
import com.falsepattern.lib.internal.FPLog;
41+
import com.falsepattern.lib.internal.Tags;
42+
import org.apache.logging.log4j.Level;
43+
import org.apache.logging.log4j.LogManager;
44+
import org.apache.logging.log4j.Logger;
45+
import org.apache.logging.log4j.core.LoggerContext;
46+
import org.apache.logging.log4j.core.config.LoggerConfig;
47+
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
48+
import org.apache.logging.log4j.spi.LoggerContextFactory;
49+
50+
import net.minecraft.launchwrapper.Launch;
51+
52+
// This class is a port of NotEnoughVerbosity, licensed under the Unlicense
53+
// It has been merged into FalsePatternLib so that it's available inside more modpacks
54+
// CHANGES:
55+
// This variant will inspect the blackboard for an entry called "notenoughverbosity". If it's present, it will no-op.
56+
// This will allow other people's lib mods to pull in this class without each one of them trying to reapply.
57+
public class NotEnoughVerbosity {
58+
59+
public static final Logger LOGGER = FPLog.LOG;
60+
61+
public static void apply() {
62+
try {
63+
if (needsToRun()) {
64+
reconfigureLog4j();
65+
}
66+
} catch (Throwable t) {
67+
LOGGER.error("NotEnoughVerbosity failed to apply!", t);
68+
}
69+
}
70+
71+
private static boolean needsToRun() {
72+
try {
73+
Class.forName("io.github.legacymoddingmc.notenoughverbosity.NotEnoughVerbosity");
74+
// OG mod already present, do nothing
75+
return false;
76+
} catch (Throwable ignored) {}
77+
78+
if (Launch.blackboard.containsKey("notenoughverbosity")) {
79+
// Other mod already present
80+
return false;
81+
}
82+
83+
boolean foundBadRoot = false;
84+
for (LoggerContext context : getLoggerContexts()) {
85+
LoggerConfig rootConfig = context.getConfiguration().getLoggers().get("");
86+
if(rootConfig != null) {
87+
if(rootConfig.getLevel().intLevel() < Level.ALL.intLevel()) {
88+
LOGGER.warn("Found root logger with verbosity " + rootConfig.getLevel() + ", will try to switch to Forge config.");
89+
foundBadRoot = true;
90+
break;
91+
} else {
92+
LOGGER.debug("Found root logger with verbosity " + rootConfig.getLevel() + ", this is fine.");
93+
}
94+
} else {
95+
throw new RuntimeException("Couldn't find root logger.");
96+
}
97+
}
98+
if(!foundBadRoot) {
99+
LOGGER.debug("Root config seems fine, log4j will not be reconfigured.");
100+
}
101+
return foundBadRoot;
102+
}
103+
104+
public static void reconfigureLog4j() {
105+
URI log4jUri = findForgeLog4j();
106+
if (log4jUri != null) {
107+
LOGGER.info("Reconfiguring logger to use config at " + log4jUri);
108+
LOGGER.info("New messages will go to fml-x-latest.log");
109+
String cookie = UUID.randomUUID().toString();
110+
LOGGER.info("Magic cookie: " + cookie);
111+
setLog4jConfig(log4jUri);
112+
int count = getLoggerContexts().size();
113+
LOGGER.info("Reconfigured logger (" + count + " context" + (count != 1 ? "s" : "") + ") to use config at " + log4jUri);
114+
LOGGER.info("Earlier messages may be located in latest.log");
115+
116+
List<String> latestLogLines = readLatestLog(cookie);
117+
if(latestLogLines != null) {
118+
LOGGER.info("Found earlier messages:\n-----Begin latest.log-----\n" + String.join("\n", latestLogLines) + "\n-----End latest.log-----");
119+
}
120+
Launch.blackboard.put("notenoughverbosity", Tags.MODID);
121+
} else {
122+
LOGGER.warn("Could not find Forge's log4j2.xml on classpath, doing nothing");
123+
}
124+
}
125+
126+
private static List<String> readLatestLog(String cookie) {
127+
File latestLog = new File(Launch.minecraftHome, "logs/latest.log");
128+
if(!latestLog.exists()) {
129+
LOGGER.debug("Couldn't find latest.log at " + latestLog.getAbsolutePath());
130+
return null;
131+
}
132+
133+
long lastModified = latestLog.lastModified();
134+
if(lastModified < System.currentTimeMillis() - 1000 * 60) {
135+
LOGGER.debug("latest.log at " + latestLog.getAbsolutePath() + " is too old (timestamp: " + lastModified + ")");
136+
return null;
137+
}
138+
139+
try(InputStream is = new FileInputStream(latestLog)) {
140+
List<String> lines = readLines(is);
141+
if(lines.stream().anyMatch(l -> l.contains(cookie))) {
142+
return lines;
143+
} else {
144+
LOGGER.debug("Failed to find magic cookie in latest.log at " + latestLog.getAbsolutePath());
145+
}
146+
} catch (IOException e) {
147+
LOGGER.error("Failed to read latest.log at " + latestLog.getAbsolutePath(), e);
148+
}
149+
150+
return null;
151+
}
152+
153+
private static void setLog4jConfig(URI uri) {
154+
for (LoggerContext context : getLoggerContexts()) {
155+
context.setConfigLocation(uri);
156+
context.reconfigure();
157+
}
158+
}
159+
160+
private static Collection<LoggerContext> getLoggerContexts() {
161+
LoggerContextFactory loggerContextFactory = LogManager.getFactory();
162+
if (loggerContextFactory instanceof Log4jContextFactory) {
163+
return ((Log4jContextFactory) loggerContextFactory).getSelector().getLoggerContexts();
164+
} else {
165+
throw new IllegalStateException("Logger context factory is not a Log4jContextFactory");
166+
}
167+
}
168+
169+
private static URI findForgeLog4j() {
170+
try {
171+
List<URL> candidates = Collections.list(NotEnoughVerbosity.class.getClassLoader().getResources("log4j2.xml"));
172+
173+
LOGGER.info("Logger configs on classpath: " + candidates);
174+
175+
for(URL url : candidates) {
176+
try {
177+
if(readLines(url.openStream()).stream().anyMatch(l -> l.contains("Root level=\"all\""))) {
178+
return url.toURI();
179+
}
180+
} catch(Exception e) {
181+
LOGGER.error("Failed to read inspect logger config at URL " + url, e);
182+
}
183+
}
184+
} catch(Exception e) {
185+
LOGGER.error("Exception enumerating logger configs", e);
186+
}
187+
return null;
188+
}
189+
190+
private static List<String> readLines(InputStream stream) {
191+
return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.toList());
192+
}
193+
}

0 commit comments

Comments
 (0)