Logging in Minecraft - The good and better way
In diesem Blogpost geht es um das Loggen und das Schreiben von Informationen in die Konsole von Minecraft. Es gibt eine Menge Möglichkeiten, Daten in die Minecraft-Konsole zu schreiben. Und es gibt eine Menge schlechter oder falscher Wege, in die Minecraft-Konsole zu schreiben. Wir fangen damit an, uns einige schlechte Praktiken anzusehen, gehen dann zum eingebauten Plugin-Logger über und schließen mit einem Blick auf slf4j.
Die fünf Arten des Loggings
Wie bereits erwähnt, gibt es mehrere Möglichkeiten (mindestens fünf), in die Konsole zu schreiben. Schauen wir uns diese an, bevor ich sie erkläre.
public final class MyPlugin extends JavaPlugin {
private static final Logger log = LoggerFactory.getLogger(MyPlugin.class);
@Override
public void onEnable() {
System.out.println("Writing via std out");
Bukkit.getConsoleSender().sendMessage("Writing via console sender");
Bukkit.getLogger().info("Writing via bukkit logger");
getLogger().info("Writing via plugin logger");
log.info("Writing via slf4j logger");
}
}
Daraus ergeben sich die folgenden Ergebnisse:
[15:39:35 INFO]: [MyPlugin] [STDOUT] Writing via std out
[15:39:35 WARN]: Nag author(s): '[]' of 'MyPlugin v1.0.0' about their usage of System.out/err.print. Please use your plugin's logger instead (JavaPlugin#getLogger).
[15:39:35 INFO]: Writing via console sender
[15:39:35 INFO]: Writing via bukkit logger
[15:39:35 INFO]: [MyPlugin] Writing via plugin logger
[15:39:35 INFO]: [dev.chojo.myplugin.MyPlugin] Writing via slf4j logger
Vielleicht hast du schon einige wichtige Unterschiede bemerkt, aber lass uns einen genaueren Blick darauf werfen.
Schlecht
Alle Logging-Methoden in diesem Abschnitt sind Don'ts. Wenn du nur daran interessiert bist, wie man es richtig macht, spring direkt zu gut
Standard output
So nicht!
System.out.println
ist zwar eine gute Möglichkeit, wenn du eine schnelle Antwort von deiner Anwendung brauchst, aber es gibt oft viel bessere Wege.
Generell sollte dies immer vermieden werden.
Es ist wahrscheinlich der schlechteste Weg, wenn du Spigot verwendest, und immer noch ein schlechter Weg, wenn du Paper verwendest.
Paper erfasst alles, was ein Plugin nach std schreibt, und verwendet den plugin logger, um es zu schreiben.
Auf diese Weise weiß der Leser, woher die Nachricht stammt.
Das ist bei der Verwendung von Spigot nicht der Fall.
Die Leute bekommen dann nur eine Nachricht wie bei der Methode console sender oder bukkit logger.
Du weißt nicht, woher diese Nachricht kommt.
Wenn du die Standardausgabe zum Schreiben verwendest, bekommst du auch eine schöne Nachricht auf Paperservern:
[15:39:35 INFO]: [MyPlugin] [STDOUT] Writing via std out
[15:39:35 WARN]: Nag author(s): '[]' of 'MyPlugin v1.0.0' about their usage of System.out/err.print. Please use your plugin's logger instead (JavaPlugin#getLogger).
Es schlägt bereits etwas anderes vor, den plugin logger, den wir uns später ansehen werden.
Console sender
So nicht!
Der Console Sender ist genauso schlecht wie die Standardausgabe, nur dass er dir wirklich nicht sagt, woher die Nachricht kommt. Du bekommst nur einen einfachen Text mit einer Nachricht, die du vielleicht verstehst, vielleicht aber auch nicht.
Bukkit Logger
So nicht!
Der Bukkit-Logger ist zwar ein Logger und kann auch als solcher verwendet werden, aber es fehlt ihm die Information, welches Plugin die Nachricht gesendet hat. Der einzige Vorteil ist, dass er Fehler richtig darstellen kann. Ansonsten ist er immer noch genauso schlecht wie die Methoden console sender und standard out.
Gut
Dies sind die richtigen Wege, wenn du etwas in die Konsole schreiben willst.
Plugin Logger
Die Klasse Plugin
bietet eine Methode namens #getLogger()
.
Diese Methode gibt einen Logger zurück, mit dem du auf verschiedenen Leveln schreiben und auch Fehler richtig ausgeben kannst.
Sie fügt außerdem den Namen deines Plugins am Anfang der Nachricht ein, so dass jeder sehen kann, welches Plugin die Nachricht gesendet hat.
Um auf verschiedenen Leveln zu schreiben, kannst du diese Methoden verwenden:
Logger#info(String)
Logger#warning(String)
Logger#severe(String)
Es gibt noch weitere Levels wie config
, fine
, finer
und finest
, aber wenn du Paper oder Spigot verwendest, werden diese Nachrichten nicht in deiner Konsole oder Logdatei erscheinen.
Es gibt Umgehungsmöglichkeiten, z. B. die Implementierung eines benutzerdefinierten Loggers, der stattdessen einfach an die Info-Ebene delegiert, oder die Verwendung von Reflections, um die Konfiguration des Loggers zu ändern, aber das würde den Rahmen dieses Beitrags sprengen.
Wenn du den Logger verwendest, sieht das so aus:
getLogger().info("This is a info");
getLogger().warning("This is a warning");
getLogger().severe("This is an error");
[16:13:08 INFO]: [MyPlugin] This is a info
[16:13:08 WARN]: [MyPlugin] This is a warning
[16:13:08 ERROR]: [MyPlugin] This is an error
Logging Exceptions
Obwohl es in den meisten Fällen ausreicht, einfach nur Nachrichten zu schreiben, werden wir wahrscheinlich irgendwann Fehler ausgeben wollen.
Du hast vielleicht das schon in vielen Fällen gesehen:
try {
throw new RuntimeException("This is not good");
} catch (Exception e) {
e.printStackTrace();
}
Output
[16:13:08 WARN]: java.lang.RuntimeException: This is not good
[16:13:08 WARN]: at myplugin-1.0.0.jar//dev.chojo.myplugin.MyPlugin.onEnable(MyPlugin.java:25)
[16:13:08 WARN]: at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:281)
[16:13:08 WARN]: at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:189)
[16:13:08 WARN]: at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104)
[16:13:08 WARN]: at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507)
[16:13:08 WARN]: at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugin(CraftServer.java:640)
[16:13:08 WARN]: at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugins(CraftServer.java:551)
[16:13:08 WARN]: at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636)
[16:13:08 WARN]: at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435)
[16:13:08 WARN]: at net.minecraft.server.dedicated.DedicatedServer.e(DedicatedServer.java:308)
[16:13:08 WARN]: at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:1101)
[16:13:08 WARN]: at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318)
[16:13:08 WARN]: at java.base/java.lang.Thread.run(Thread.java:833)
Das ist schlecht, tu es nicht!
Stattdessen musst du die Methode #log(Level, String, Throwable)
deines Loggers verwenden.
try {
throw new RuntimeException("This is not good");
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Something went wrong", e);
}
Die Klasse Level ist eine Java-Klasse, die Teil des Pakets java.util.logging
ist.
Wenn du diese Methode aufrufst, wird dein Fehler schön ausgegeben. Deine Ausgabe enthält deine Nachricht, die Fehlermeldung und einen Stacktrace.
Achtung
Wenn du das nicht lesen und verstehen kannst, empfehle ich dir, diesen Stackoverflow-Beitrag zu lesen.
[16:10:43 ERROR]: [MyPlugin] Something went wrong
java.lang.RuntimeException: This is not good
at dev.chojo.myplugin.MyPlugin.onEnable(MyPlugin.java:25) ~[myplugin-1.0.0.jar:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:281) ~[paper-api-1.20.1-R0.1-SNAPSHOT.jar:?]
at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:189) ~[paper-1.20.1.jar:git-Paper-117]
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.1.jar:git-Paper-117]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugin(CraftServer.java:640) ~[paper-1.20.1.jar:git-Paper-117]
at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugins(CraftServer.java:551) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.1.jar:git-Paper-117]
at java.lang.Thread.run(Thread.java:833) ~[?:?]
Der SLF4J logger
Intern verwendet Paper and Spigot ein Framework namens slf4j mit einer Implementierung namens log4j. Auch wenn du den plugin logger verwendest, benutzt du immer noch das slf4j-Framework. Du kannst es also auch direkt verwenden. Anstatt deiner Nachricht den Namen des Plugins voranzustellen, stellt der slf4j-Logger deiner Ausgabe den Namen der Klasse voran, welche die Nachricht sendet. Dadurch erhältst du noch mehr Informationen darüber, woher die Nachricht kommt.
Abgesehen von seinem praktischen Einsatz in Minecraft wird slf4j auch in anderen Projekten häufig verwendet und du wirst es auch außerhalb von Minecraft sehr häufig antreffen. Deshalb ist es generell gut zu kennen, wenn du mit anderen Frameworks und Bibliotheken arbeiten willst.
Um eine Logger-Instanz für deine Klasse zu erhalten, brauchst du die Klasse LoggerFactory
, rufst die Methode #getLogger(Class)
auf und übergibst deine aktuelle Klasse.
Da dies langweilige Handarbeit ist, kannst du einfach ein Live Template dafür erstellen, wenn du IntelliJ verwendest.
Das Live-Template sieht wie folgt aus (eine Importvorlage findest du im Spoiler unten):
XML Template zum importieren
Um dies zu importieren, erstelle eine neue Java Live-Vorlage, kopiere den obigen XML-Code und füge ihn in deine neu erstellte Vorlage ein.<template name="log" value="private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger($class$.class);" description="insert a default logger" toReformat="false" toShortenFQNames="true" useStaticImport="true">
<variable name="class" expression="className()" defaultValue="className()" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
Wenn du dies mit der Abkürzung log
registrierst, musst du nur noch log
in deine Klasse eingeben und der Logger wird eingefügt.
Du bekommst dann etwas wie das hier:
Die Verwendung dieser Logger-Instanz ist der Verwendung des plugin logger sehr ähnlich, aber es gibt einige Unterschiede.
Schauen wir uns zuerst das grundlegende Logging an.
[16:32:03 INFO]: [dev.chojo.myplugin.MyPlugin] This is a info
[16:32:03 WARN]: [dev.chojo.myplugin.MyPlugin] This is a warning
[16:32:03 ERROR]: [dev.chojo.myplugin.MyPlugin] This is an error
Du siehst, dass wir jetzt die Klasse als Präfix vor unserer Nachricht haben.
Statt warning
verwenden wir jetzt warn
und statt severe
verwenden wir error
.
Logging exceptions
Die Ausgabe von Fehlern ist mit slf4j einfacher. Wir müssen ihn nur als zweites Argument nach unserer Nachricht übergeben.
try {
throw new RuntimeException("This is not good");
} catch (Exception e) {
log.error("Something went wrong", e);
}
Die Ausgabe bleibt dieselbe wie bei plugin logger, nur dass wir jetzt wieder den Klassenpräfix haben.
Ausgabe
[16:34:49 ERROR]: [dev.chojo.myplugin.MyPlugin] Something went wrong
java.lang.RuntimeException: This is not good
at dev.chojo.myplugin.MyPlugin.onEnable(MyPlugin.java:40) ~[myplugin-1.0.0.jar:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:281) ~[paper-api-1.20.1-R0.1-SNAPSHOT.jar:?]
at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:189) ~[paper-1.20.1.jar:git-Paper-117]
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.1.jar:git-Paper-117]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugin(CraftServer.java:640) ~[paper-1.20.1.jar:git-Paper-117]
at org.bukkit.craftbukkit.v1_20_R1.CraftServer.enablePlugins(CraftServer.java:551) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.1.jar:git-Paper-117]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.1.jar:git-Paper-117]
at java.lang.Thread.run(Thread.java:833) ~[?:?]
Verwendung von Platzhaltern in Nachrichten
slf4j hat eine nette Funktion für Platzhalter.
Um deiner Nachricht zusätzliche Informationen hinzuzufügen, kannst du Platzhalter mit {}
definieren und die Werte in der richtigen Reihenfolge nach deiner Nachricht übergeben.
Du kannst sehen, dass die Werte einfach in deine Nachricht eingefügt werden.
Das funktioniert auch mit Fehlern. Achte nur darauf, dass dein letztes Argument der Fehler selbst ist und dass alle anderen Werte zuerst angegeben werden.
var first = 5;
var second = 0;
try {
var result = first / second;
} catch (Exception e) {
log.error("I tried to divide {} through {} and it went up in flames", first, second, e);
}
[16:39:49 ERROR]: [dev.chojo.myplugin.MyPlugin] I tried to divide 5 through 0 and it went up in flames
java.lang.ArithmeticException: / by zero
at dev.chojo.myplugin.MyPlugin.onEnable(MyPlugin.java:51) ~[myplugin-1.0.0.jar:?]
at ... #Shortened by me
Funktioniert wie ein Zauber und macht es sehr einfach, zusätzliche Informationen zu deiner Nachricht hinzuzufügen!
Hinzufügen eines Präfixes
Das Hinzufügen eines Präfixes, wenn du eine der Methoden im Abschnitt schlecht verwendest, hilft zwar ein bisschen, ist aber trotzdem schlecht. Warum einen schlechten Weg benutzen, wenn es gute Wege gibt.
Vielen Dank!
Jetzt weißt du, wie du dich richtig in deinem Minecraft-Projekt loggen kannst!
Danke fürs Lesen! Wenn dir dieser Beitrag gefallen hat oder du Fragen hast, kannst du gerne in meinem Discord vorbeikommen und mit mir reden!
Oder schreib mir eine Mail an mail [at] chojo [dot] dev
.
Wenn du mich unterstützen willst, kannst du mich über GitHub unterstützen.