Logging
Logging is a very useful debugging tool for developers. You can look at the console or the log file to see if everything ran correctly, or if errors occured.
Where you have to be careful though is the fact that writing to the console can create game lag/stuttering due to how the console works. This article goes through the best practices in utilizing the Logger.
Logger Setup
When using BepInEx, you may notice that it typically comes with the following Awake
method:
private void Awake()
{
// Plugin startup logic
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
}
The highlighted line uses the Logger to log to the console and log file that your plugin has loaded. In the console, this may look like the following:
There's an issue currently present, you can only access the Logger
from the Plugin
class. If you want to access it from any patch or behaviour classes, it's currently inaccessible!
To solve that, we have to make a field that can be accessed internally:
internal new static ManualLogSource Logger;
private void Awake()
{
Logger = base.Logger;
// Plugin startup logic
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
}
Two things happen here; first we create the field that can be referenced by our other classes in our mod.
Doing so, we use the internal
access modifier to only allow our project code to access the field. We also use static
to allow our other classes to easily access the logger by doing Plugin.Logger
; and finally we use new
to tell the compiler that we are intentionally overriding the Logger
field from the BaseUnityPlugin
class.
internal new static ManualLogSource Logger;
Next, we set that field to the Logger given by BepInEx. Since we are overriding it, we use base.Logger
to reference Logger
from the BaseUnityPlugin
we inherit from.
private void Awake()
{
Logger = base.Logger;
// Plugin startup logic
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
}
Now that we done that, we can access the Logger!
Using the Logger
So, you have a patch and want to log info to the console during it. For example, we want to log the item name when an item is picked up. We can patch the GrabbableObject.EquipItem
method, and then get the item's name:
[HarmonyPostfix]
[HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.EquipItem))]
private void EquipItemPostfix(GrabbableObject __instance)
{
var itemName = __instance.itemProperties.itemName;
}
Now we can log it! To do so, we just add one line:
[HarmonyPostfix]
[HarmonyPatch(typeof(GrabbableObject), nameof(GrabbableObject.EquipItem))]
private void EquipItemPostfix(GrabbableObject __instance)
{
var itemName = __instance.itemProperties.itemName;
Plugin.Logger.LogDebug(itemName);
}
Notice how we use .LogDebug()
here instead of .LogInfo()
. This is where the best practices come into play.
Best Practices
Debug Logging
Because logging to the console can cause the game to stutter, we'd like to avoid doing so whenever possible. However, we still would like to know what we log!
We can do so by using .LogDebug()
. This will log to the console, but with a lower "priority" so that it is only visible if we specify so in our BepInEx config.
To view debug logs, you can change the following in your BepInEx config located at BepInEx/config/BepInEx.cfg
:
[Logging.Console]
#...
## Which log levels to show in the console output.
# Setting type: LogLevel
# Default value: Fatal, Error, Warning, Message, Info
# Acceptable values: None, Fatal, Error, Warning, Message, Info, Debug, All
# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)
LogLevels = Fatal, Error, Warning, Message, Info
LogLevels = All
[Logging.Disk]
#...
## Which log levels are saved to the disk log output.
# Setting type: LogLevel
# Default value: Fatal, Error, Warning, Message, Info
# Acceptable values: None, Fatal, Error, Warning, Message, Info, Debug, All
# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)
LogLevels = Fatal, Error, Warning, Message, Info
LogLevels = All
By default, if a player uses the BepInEx version pinned on Thunderstore, the [Logging.Disk]
LogLevels
setting will automatically be set to All
, more easily allowing you to debug based off "hidden" logs in the log file.
Warning Logging
Sometimes you may have code that may not be used as intended, but will still function normally. To let users know this has occured, you can use .LogWarning()
.
You may commonly see warnings logged by Unity when landing the ship, and this is a case where something isn't working right, but it can still continue functioning.
Error Logging
Sometimes you need to log errors so that you can easily find it when someone runs into an issue and posts a log file. You can use .LogError()
to do so.
WARNING
LogError()
should only be used when an error occurs.
Often times, there will be C# or Unity errors that display when an error occurs, limiting the need to use the method. But it can still be a useful piece of information when debugging.