aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0724-Add-System.out-err-catcher.patch
blob: 4753e9648c1b0d820776f6d605167dc90b52ed32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: underscore11code <minecrafter11mrt@gmail.com>
Date: Fri, 23 Jul 2021 23:01:42 -0700
Subject: [PATCH] Add System.out/err catcher


diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..76d0d00cd6742991e3f3ec827a75ee87d856b6c9
--- /dev/null
+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
@@ -0,0 +1,94 @@
+package io.papermc.paper.logging;
+
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+public final class SysoutCatcher {
+    private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags");
+    // Nanoseconds between nag at most; if interval is caught first, this is reset.
+    // <= 0 for disabling.
+    private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos(
+        Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L)));
+    // Count since last nag; if timeout is first, this is reset.
+    // <= 0 for disabling.
+    private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L);
+
+    // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis.
+    // The records are also pretty small, so let's just go for a size of 64 to start...
+    //
+    // Content: Plugin name => nag object
+    //  Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability.
+    //  Why not clean on reload?: Effort.
+    private final ConcurrentMap<String, PluginNag> nagRecords = new ConcurrentHashMap<>(64);
+
+    public SysoutCatcher() {
+        System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] "));
+        System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] "));
+    }
+
+    private final class WrappedOutStream extends PrintStream {
+        private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+        private final Level level;
+        private final String prefix;
+
+        public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) {
+            super(out);
+            this.level = level;
+            this.prefix = prefix;
+        }
+
+        @Override
+        public void println(@Nullable final String line) {
+            final Class<?> clazz = STACK_WALKER.getCallerClass();
+            try {
+                final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz);
+
+                // Instead of just printing the message, send it to the plugin's logger
+                plugin.getLogger().log(this.level, this.prefix + line);
+
+                if (SysoutCatcher.SUPPRESS_NAGS) {
+                    return;
+                }
+                if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) {
+                    final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag());
+                    final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0
+                        && (nagRecord.lastNagTimestamp == Long.MIN_VALUE
+                        || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime());
+                    final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0
+                        && (nagRecord.messagesSinceNag == Long.MIN_VALUE
+                        || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL);
+                    if (!hasMessagesPassed && !hasTimePassed) {
+                        return;
+                    }
+                    nagRecord.lastNagTimestamp = System.nanoTime();
+                    nagRecord.messagesSinceNag = 0;
+                }
+                Bukkit.getLogger().warning(
+                    String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. "
+                            + "Please use your plugin's logger instead (JavaPlugin#getLogger).",
+                        plugin.getDescription().getAuthors(),
+                        plugin.getName())
+                );
+            } catch (final IllegalArgumentException | IllegalStateException e) {
+                // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc
+                // Just print out normally, with some added information
+                Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line));
+            }
+        }
+    }
+
+    private static class PluginNag {
+        private long lastNagTimestamp = Long.MIN_VALUE;
+        private long messagesSinceNag = Long.MIN_VALUE;
+    }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index f4a7b1b45d8284a5552dce67979282ce09e71a36..0adb686d690e1be0c5540625efbecc46bac5d045 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -18,6 +18,7 @@ import com.mojang.serialization.Lifecycle;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufOutputStream;
 import io.netty.buffer.Unpooled;
+import io.papermc.paper.logging.SysoutCatcher;
 import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
 import java.awt.image.BufferedImage;
 import java.io.File;
@@ -290,6 +291,7 @@ public final class CraftServer implements Server {
     public int reloadCount;
     private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
     public static Exception excessiveVelEx; // Paper - Velocity warnings
+    private final SysoutCatcher sysoutCatcher = new SysoutCatcher(); // Paper
 
     static {
         ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);