🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21.8
In programming, even the best are bound to encounter issues, bugs and mistakes.
This guide outlines some general steps that you can take to potentially identify and resolve those, even without the help of others. Solving problems on your own can teach you many things, and will also feel rewarding.
However, if you are stuck, unable to find fixes by yourself, there is no problem in asking others for help!
The simplest and fastest way to locate problems is logging to the console.
Values can be printed there at runtime, informing the developer about the current state of the code, and making it easy to analyze changes and potential mistakes.
In the ModInitializer
-implementing entrypoint class of the mod, a LOGGER
is defined by default to print the desired output to the console.
public class FabricDocsReferenceDebug implements ModInitializer {
public static final String MOD_ID = "i-am-your-mod-id";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
// ...
}
Whenever you need to know a value for something at any point in the code, use this LOGGER
by passing a String
to its methods.
FabricDocsReferenceDebug.LOGGER.info("You interacted with an entity!");
The logger supports multiple modes of printing text to the console. Depending on which mode you use, the logged line will be displayed in different colors.
FabricDocsReferenceDebug.LOGGER.info("Neutral, informative text...");
FabricDocsReferenceDebug.LOGGER.warn("Non-critical issues...");
FabricDocsReferenceDebug.LOGGER.error("Critical exceptions, bugs...");
INFO
All logger modes support multiple overloads; this way you can provide more information like a stack trace!
For example, let's make sure that, when the TestItem
is used on an entity, it will output its current state in console.
@Override
public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) {
World world = user.getWorld();
// Values are used in a String to provide more information in the console
String output = "Is Client World: %s | Health: %s / %s | The item was used with the %s"
.formatted(user.getWorld().isClient(), entity.getHealth(), entity.getMaxHealth(), hand.name());
FabricDocsReferenceDebug.LOGGER.info(output);
if (!user.getWorld().isClient()) {
// you can log non-critical issues differently as a warning
FabricDocsReferenceDebug.LOGGER.warn("Don't touch that!");
// The LOGGER can print the Stacktrace too in addition to the logging message
if (stack.getCount() > 1) {
IllegalArgumentException exception = new IllegalArgumentException("Only one item is allowed");
FabricDocsReferenceDebug.LOGGER.error("Error while interacting with an entity", exception);
throw exception;
}
}
return ActionResult.SUCCESS;
}
In the logged line, you can find:
Time
- The time when the logged information was printedThread
- The thread in which it was printed. You will often see a server thread and a render thread; this tells you on which side the code had been executedName
- The name of the logger. It is best practice to use your mod id here, so that logs and crashes show which mod loggedMessage
- This should be concise but descriptive. Include relevant values or contextStack Trace
- If provided with an exception's stack trace, the logger can print that tooKeep in mind that all of these will also be printed if the mod is used in any other environment.
The following is completely optional, but I like to create a custom LOGGER
method and use it to avoid printing data in production, when it is only needed during development.
// This method will only log if the Minecraft instance
// is running in a Development Environment, like your IDE
public static void devLogger(String loggerInput) {
if (!FabricLoader.getInstance().isDevelopmentEnvironment()) {
return;
}
LOGGER.info("DEV - [ %s ]".formatted(loggerInput));
}
If you are unsure whether to log outside a debugging session, a good rule of thumb is to only log if something went wrong. Modpack devs and users don't care too much about, for example, items initializing; they would rather know if, for example, a datapack failed to load correctly.
The logger prints your mod's ID in front of the line. You can search (⌘/CTRL+F) for it to highlight it.
Missing assets and textures (the Purple & Black placeholder) also log a warning to the console, and usually mention the expected values.
A more sophisticated way of debugging is using breakpoints in an IDE. As their name suggests, they are used to halt the executed code at specific points to allow for inspecting and modifying the state of the software.
TIP
To use breakpoints, you must execute the instance using the Debug
option instead of the Run
option:
Let's use our custom item as an example again. The item's CUSTOM_NAME
DataComponentType
is supposed to change if it is used on any Stone block. However, in this example, the item always seems to trigger a "success" hand animation, yet cobblestone doesn't seem to change the name.
How can we resolve these two issues? Let's investigate...
// problematic example code:
public class TestItem extends Item {
public TestItem(Settings settings) {
super(settings);
}
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
World world = context.getWorld();
PlayerEntity user = context.getPlayer();
BlockPos targetPos = context.getBlockPos();
ItemStack itemStack = context.getStack();
BlockState state = world.getBlockState(targetPos);
if (state.isIn(ConventionalBlockTags.STONES)) {
Text newName = Text.literal("[").append(state.getBlock().getName()).append(Text.literal("]"));
itemStack.set(DataComponentTypes.CUSTOM_NAME, newName);
if (user != null) {
user.sendMessage(Text.literal("Changed Item Name"), true);
}
}
return ActionResult.SUCCESS;
}
}
Place a breakpoint by clicking on the line number. You can place more than one at once if needed. The breakpoint will stop right before executing the selected line.
Then let the running Minecraft instance execute this part of the code. You can place breakpoints while the game is running, too.
In this case, the custom item needs to be used on a block. The Minecraft window should freeze, and in IntelliJ a yellow arrow will appear right next to the breakpoint, indicating that the debugger has reached that point.
At the bottom, a Debug
window should open, and the Threads & Variables
view should be selected automatically. You can use the arrow controls in the Debug
window to move the execution point. This way of moving through the code is called "stepping".
Action | Description |
---|---|
Step Over | Steps to the next executed line, basically moving the instance along in the logic |
Step Into | Steps into a method to show what is happening inside. If there are multiple methods on one line, you can choose which to step into by clicking it. This is also necessary for lambdas. |
Run To Cursor | Steps through the logic until it reaches your cursor in the code. This is useful for skipping large chunks of code. |
Show Execution Point | Moves the view of your coding window to the point where the debugger is currently at. This also works if you are currently working in other files and tabs. |
INFO
The "Step Over" F8 and "Step Into" F7 actions are the most common ones, so try to get used to the shortcuts!
If you are done with the current inspection, you can press the green Resume Program
button (F9). This will unfreeze the Minecraft instance, and further testing can be done until another breakpoint is hit. But let's keep looking at the Debug
window for now.
On the top, you can see all currently running instances. If both are running, you can switch between the client and the server instance. Below that, you have the debug actions and controls. You can also switch to the Console
view if you need to take a look at the logs.
On the left side, you can see the currently active thread, and the stack trace below that.
On the right side, you can inspect and manipulate loaded values and objects. You can also hover over the values in the code: if they are in scope and are still loaded, a pop-up window will show their specific values too.
If you are interested in the content of a specific object, you can use the small arrow icon next to it. This will unfold all nested data.
Values are obviously not loaded if the execution point didn't pass by them or if they are located in a completely different context.
You can use the input line in the Debug
window for many different things, for example you can access currently loaded objects and use methods on them. This will add a new entry below, showing the requested data.
Let's step over in our example, so that the BlockState
variable is loaded. We can now check if the BlockState
of the targeted block is actually in the Block
tag.
TIP
Press the +
icon on the right side of the input line to pin the result for the current debug session.
As we can see, the ConventionalBlockTags.STONES
tag does not include cobblestone because there is a separate tag for that.
Sometimes you only need to halt code when certain conditions are met. For that, create a breakpoint and right-click it to open its settings. In there you can set a boolean statement as the condition.
Hollow breakpoint icons indicate inactive breakpoints, which won't halt the active Minecraft instance. You can toggle breakpoints either in the breakpoint's settings pop-up window or simply by middle-clicking the breakpoint itself.
All breakpoints will be listed in IntelliJ's Bookmarks
window.
You can make limited changes to the code while an instance is running, using the Build > Build Project
action with the hammer icon. You can also put the icon next to the Run Configuration
drop-down element by right-clicking the empty space on IntelliJ's top menu bar.
This process, also called "hotswapping", requires the Minecraft instance to be started in Debug
mode instead of the Run
mode (see above).
With this, you don't need to restart the Minecraft instance again. It also makes testing screen element alignment and other feature balancing faster. IntelliJ will notify you if the "hotswap" was successful.
Mixins are an exception. You can set up your Run Configuration to allow them to change at runtime too. For more information, check out Hotswapping Mixins.
Other changes can be reloaded in-game.
assets/
folder -> press F3+Tdata/
folder -> use the /reload
commandTo finish up with the example from earlier, let's add a condition to the statement. Once we hit the breakpoint, we can see that we always get a "success" hand animation because we never returned anything else.
Apply the fixes and use hotswapping to see the changes in the game instantly.
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
World world = context.getWorld();
PlayerEntity user = context.getPlayer();
BlockPos targetPos = context.getBlockPos();
ItemStack itemStack = context.getStack();
BlockState state = world.getBlockState(targetPos);
if (state.isIn(ConventionalBlockTags.STONES) || state.isIn(ConventionalBlockTags.COBBLESTONES)) {
Text newName = Text.literal("[").append(state.getBlock().getName()).append(Text.literal("]"));
itemStack.set(DataComponentTypes.CUSTOM_NAME, newName);
if (user != null) {
user.sendMessage(Text.literal("Changed Item Name"), true);
}
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
}
Consoles of previously executed instances are exported as log files, located in the Minecraft instance's logs
directory. The newest log is usually called latest.log
.
Users will be able to share this file with the mod's developer for further inspection, as explained in Uploading Logs.
In the development environment, you can find previous logs in the Project
window's run > logs
folder, and crash reports in the run > crash-reports
folder.
Still couldn't figure out what's going on? You can join the Fabric Discord Server and have a chat with the community!
You may also want to check out the Official Fabric Wiki for more advanced queries.