Releasing GameMaker games: automating bug reports like a pro Part 2

Yuan Gao (Meseta)
10 min readFeb 23, 2019

--

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:

Gotta catch ’em all

Basic messages

Aside from handling GM exceptions, GMSentry can also send a range of custom messages to sentry.io.

This is a test

Sending the above sentry_capture_message("This is a test"); generates the below event in your project’s dashboard on sentry.io:

This is the test

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:

The report for the test

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 sending
  • level[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 game
  • vars [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:

An aged meme being captured

Results in this dashboard entry:

Dashboard entry for leonidas

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:

Capturing the variables from the player instance

Results in this dashboard entry:

Grey flag for debug items

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 purposes
  • level[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 message
  • data* [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

Breadcrumb without data, note the string is being assembled dynamically
Breadcrumb with additional data attached

Produces the following when added to a report:

Breadcrumbs section of 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 add
  • value[string, real, or array] required, this is the value to add
  • is_map* [bool] optional, if set to true, the value added is added as a map, allowing a ds_map’s worth of data to be added. NOTE: if adding a map to extras, running sentry_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 run sentry_clear_extra()
Example of adding extra data to reports
A ds_map can be added like this

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:

Adding different types of extra data to the report

Results in the following section appearing in the “Here’s some data” report:

Extra data in 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:

Again, not a real DSN

This results in the entry appearing in the tags section of a report:

Foo is bar.

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.

What it looks like when you have a lot of users experiencing a bug

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 use undefined 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 use undefined 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 use undefined as an argument if you don’t want to include this field
  • map* [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:

Yes, my real name is Coder McCodeface

This results in a User section in the report, tags, and icons:

User data being added to report

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:

Asks to send report, deletes if “no”

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 running sentry_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 running sentry_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!

--

--

Yuan Gao (Meseta)
Yuan Gao (Meseta)

Written by Yuan Gao (Meseta)

🤖 Build robots, code in python. Former Electrical Engineer 👨‍💻 Programmer, Chief Technology Officer 🏆 Forbes 30 Under 30 in Enterprise Technology

No responses yet