Releasing GameMaker games: automating bug reports like a pro Part 2
This is a supplementary section to Part 1 which discusses setting up sentry.io, GMSentry and YellowAfterlife’s catch_error for GMS2 to automatically submit bug reports from released games. This post describes example use cases.
Capturing events
Whenever GMSentry sends data to sentry.io, it is an “event”. This is your main means of getting log data uploaded. This section describes the two ways to submit events: sentry_capture_exception
and sentry_capture_message
GM Exceptions
The most basic usage for GMSentry is to accept a pre-canned GM error message as a string. An easy way (only known way currently) to do this is through YAL’s catch_error as linked above:
Basic messages
Aside from handling GM exceptions, GMSentry can also send a range of custom messages to sentry.io.
Sending the above sentry_capture_message("This is a test");
generates the below event in your project’s dashboard on sentry.io:
Notice that “This is the test” is the name of the event; and below it is the event and object or script where the capture took place — it’s the first part of the stacktrace. In my case, I stashed the above code in a Key Up - up
event in a tests
objects.
The report looks like this:
GMSentry is set up to send over a bunch of default metadata including your game’s build information (such as the project version and GM version) and the player’s OS details. This is to make it possible to search and filter issues by platform. It also contains the IP address by default; IP address storage can be scrubbed by sentry.io if you want to preserve anonymity.
Notice that the “This is a test” message shows up in both the title and the Message sections of the report. Also notice that the Stacktrace is automatically inserted — this tells you where in your project the message was sent from, including scripts, events, and line numbers.
Advanced messages
The sentry_capture_message(message, level*, logger*, vars*)
function accepts several optional parameters:
message
[string] required, this is the message you are sendinglevel
[string] optional, this is the severity level of the log, can be: “fatal”, “error”, “warning”, “info”, and “debug”. Macros have been provided for these strings:SENTRY_FATAL
,SENTRY_ERROR
,SENTRY_WARNING
,SENTRY_INFO
,SENTRY_DEBUG
logger
[string] optional, this is the logger name, can be used to separate logging activities to different subsystems of your gamevars
[instance number] optional, this can be used to fetch all the instance variables of a targeted instance and attach them to the message. Must be an instance ID, cannot be an object ID
For example, this code:
Results in this dashboard entry:
Note the lovely red flag for fatal errors; and the logger name leonidas
now appears in place of the default value logger
; logger names can be used to help you separate and filter logs from different subsystems.
In another example, this code:
Results in this dashboard entry:
And the report now includes the player’s instance variables alongside the metadata that is included by default. Note: built-in instance variables are not collected.
Adding additional data to events
Additional data can be attached to events to be sent alongside the report. This allows other logging information and data to be sent to help you debug issues.
Breadcrumbs
Breadcrumbs are an extremely important tool for debugging the cause of error messages. Breadcrumbs can be considered to be a recent log of events that can help you trace the sequence of events up to the time an event or exception is captured, a literal breadcrumb trail.
GMSentry by default maintains a 100 entry breadcrumb trail; when the breadcrumb trail exceeds 100, GMSentry will begin deleting the oldests ones, ensuring the breadcrumb trail doesn’t take up too much memory, or cause reports to become too big.
Creating a breadcrumb only stores that breadcrumb in memory with a timestamp and any additional data, and does not transmit it to sentry.io. It’s only when a sentry_capture_exception()
or sentry_capture_message()
is called, that the recent breadcrumbs will be included with the report and transmitted.
Breadcrumbs are created using thesentry_add_breadcrumb(category, level, message, data*)
function with the following parameters:
category
[string] required, this is a categorisation you can freely use to help distinguish different breadcrumbs that you use for different purposeslevel
[string] required, as with before, this is the severity level of the breadcrumb, can be: “fatal”, “error”, “warning”, “info”, and “debug”. Macros have been provided for these strings:SENTRY_FATAL
,SENTRY_ERROR
,SENTRY_WARNING
,SENTRY_INFO
,SENTRY_DEBUG
message
[string] required, this is the main body of the breadcrumb that allows you to add a messagedata*
[ds_map number] optional, this can be used to attach a ds_map to the breadcrumb to store additional data along with the breadcrumb. Must be a ds_map, and nested entities or arrays are not supported, make sure the values are strings or numbers.
For example: the following two examples of breadcrumbs can be used
Produces the following when added to a report:
Note the icons change depending on severity level; data saved in the breadcrumb is added; and breadcrumbs are full-text searchable. The last breadcrumb item is event itself; here I’m using the same Player info
message as the previous section.
Extra data
Just as breadcrumbs can accept additional data, so too can events. Extra data can be added to events by using sentry_add_extra(key, value, is_map*)
key
[string] required, this is the name of the extra data value to addvalue
[string, real, or array] required, this is the value to addis_map*
[bool] optional, if set to true, thevalue
added is added as a map, allowing a ds_map’s worth of data to be added. NOTE: if adding a map to extras, runningsentry_clear_extra()
will also destroy the map; conversely destroying the map manually later will cause the inserted map to become a hanging reference, resulting in potentially unexpected behaviour or errors. It is recommended to not manually destroy the map, and allow GMSentry to do it next time you runsentry_clear_extra()
Every report sent will include all extra data added. When it is no longer desirable to send the extra data, it can be cleared using sentry_clear_extra()
. If extra data is only needed for one report, it is appropriate to set up the extra data, call a capture, and then clear the data.
For example, the following code:
Results in the following section appearing in the “Here’s some data” report:
Tags
Part of sentry.io’s power comes from being able to search events by tags, allowing you to filter out different error reports based on certain conditions, such as a specific game version; a specific GM engine version; a specific OS version; or any of the tags added to the report for you by GMSentry.
You can also add custom tags to be included with reports. These can be added using the sentry_add_tag(key, value)
function, where key
and value
are the name and value of the tag. No functionality is provided to clear tags, as it is expected that these are set up at the start of the game and left untouched.
Tags are best added just after initialising GMSentry:
This results in the entry appearing in the tags section of a report:
Importantly this means it is also possible to search all events recorded in sentry.io by this tag and value (simply clicking on the tag on sentry.io will search for all other events with the same tag).
User info
sentry.io can also keep track of different users in case you wanted to know who was submitting all of your bug reports. If your game has a log-in system or cloud-save system, the logged-in user could be attached to the report, or you could generate UIDs for your users to store locally. Identifying your users helps you determine how widespread an error is — whether one user is experiencing an issue multiple times; or many users are experiencing it.
By default, if no user info is provided, sentry.io uses the submitter’s IP address in place of the user. To specify the user’s details yourself, you can use sentry_set_user(id, email, username, map*)
id
[string, real, or undefined] required, this is a unique identifier for your user. You may useundefined
as an argument if you don’t want to include this field.email
[string, real, or undefined] required, this is the email address for your user. You may useundefined
as an argument if you don’t want to include this field.username
[string, real, or undefined] required, this is the username for your user. You may useundefined
as an argument if you don’t want to include this fieldmap*
[real] optional, if provided, all of the map data is included as properties of the user. NOTE: if adding a map, re-runningsentry_set_user()
will also destroy the previously added map; conversely destroying the map manually later will cause the inserted map to become a hanging reference, resulting in potentially unexpected behaviour or errors. It is recommended to not manually destroy the map.
For example, I could add the following user:
This results in a User section in the report, tags, and icons:
Like tags, users become searchable, allowing you to see what reports an individual user has generated.
Saving log files
GMSentry’s debault behavior is to dump the events to file before sending (controlled by the backup_before_send
option), and to delete the dumped file after it confirmes sentry.io has received it. This means if an event is generated, but sentry.io cannot be reached, the event is saved to file and can be sent later.
A few functions are provided to make it easier to handle: sentry_saved_count()
returns the number of saved events, while sentry_saved_send()
automatically re-sends all the saved events. sentry_saved_delete()
erases the saved events (this is typically not necessary as GMSentry will erase saved events as they’re received by sentry.io, but useful if you have backup_autoclean
turned off).
A simple example application of this:
Checking the return code of sending the report
sentry_capture_exception()
and sentry_capture_message()
both return a request ID. It is possible to check for the success or failure of the report sending.
GMSentry maintains internal success and failure maps, and will automatically insert the request ID of a request into one of them when the send completes successfully, or if the send fails.
For example, store the return value in an instance variable:
reqId = sentry_capture_except(s)
Then later on, check whether this reqId is in the success or errors list:
if (sentry_error_exists(reqId)) {
var errorCode = sentry_error_pop(reqId);
// do something with errorCode
}
else if (sentry_result_exists(reqId)) {
var result = sentry_result_pop(reqId);
// do something with result
}
Using this mechanism, it is possible to detect HTTP errors, such as Sentry’s 429 rate limit error; as well as make loading spinners submitting errors.
Configuration
GMSentry has a few configuration options, which can be set up using sentry_set_option(option, value)
, where option
is a name of a GMSentry option, and value
is the value to set it to.
The currently available options are:
backup_before_send
[default:true
], GMSentry by default dumps the events to file before sending. This allows you to ask users to dig out the event reports and send them in manually. This behaviour can be turned off by runningsentry_set_option("backup_before_send", false)
backup_autoclean
[default:true
], GMSentry by default deletes the dumped events after it confirms sentry.io has received it. This behaviour can be turned off by runningsentry_set_option("backup_before_send", false)
breadcrumbs_max
[default:100
], this is the maximum number of breadcrumbs to keep in memory before starting to delete the old ones.
Other Sentry.io features
sentry.io has a host of other features not discussed in this or Part 1 of this guide, which are worth exploring. Take a look at sentry.io’s About, or Tutorial’s section
Happy bug reporting!