Have you ever tried debugging a problem with your game that only seems to happen in production?
Maybe your game is wrapped in Capacitor for mobile and a user, client, or the Quality Assurance (QA) team finds a bug that is hard to reproduce… at least for you. But they can make it happen like clockwork. 🤷♂️
The Developer Tools Console or Debugger is not an option so what do you do?
One way is to build a secret in-game log console but you don't want to have to write 2 log statements every time log something.
What you need is an advanced logging solution using the Strategy Pattern to decide which logger or loggers to use depending on the environment.
This article will show you how to do that.
The Strategy Pattern is a behavior pattern similar to the State Pattern.
The main difference is that the Strategy Pattern is mostly involved with interchangeable ways of doing something while the State Pattern deals with managing the internal state of objects.
Don't worry if that definition isn't crystal clear. Implementing the advanced logger in this article will help you better understand the Strategy Pattern.
We'll be using TypeScript and Phaser 3 in the example code but the concepts are the same for other languages and frameworks.
TypeScript and Phaser 3 are a great combination for making games on the web. Check out our free ebook to learn more 👇
Learn to make an Infinite Runner in Phaser 3 with TypeScript!
Drop your email into the box below to get this free 90+ page book and join our newsletter.
Create a Logger
Let's start by creating a
Logger class that we'll use to log messages to the Console instead of
The key to this
Logger class is that it will have no concrete logging logic. The actual work of logging will be handled by loggers that get added to a
Notice that we are using the
Map data structure instead of a plain
object or hashmap on line 3.
Map but you don't have to.
The important thing is that we are holding a mapping of keys to
ILogger instances that can be added and removed at runtime using the
log() method goes through each
ILogger instance and instructs them to perform the work of logging.
ILogger interface looks like this 👇
Chances are you'll only need a single instance of the
Logger class and you can do that however you want.
One way is to export a shared instance from
Logger.ts like this:
Then you can import
sharedInstance as logger where you need it.
Create a Console Logging Strategy
Logger class doesn't do any actual logging so we'll need concrete loggers that implement
Let's start with a simple
ConsoleLogger that logs to the browser's Console:
Notice that this simply wraps
console.log(). Nothing fancy.
What's more important is that this
ConsoleLogger is a logging strategy. We can use this with the
Logger in a Phaser 3 Scene like this:
The result of
this.logger.log('hello, world!') will be the same as
So far this seems like a lot of extra code for little benefit!
But don't worry because the magic of the Strategy Pattern comes when you have multiple strategies.
Logging to an In-Game Console
We won't go into hiding/showing an in-game console or making it pretty. For simplicity, we'll just use an
HTMLTextAreaElement that will show messages.
First, we can create an
InGameLogger similar to the
The key thing to note is that we are taking in a
Phaser.GameObjects.DOMElement in the
constructor on line 8.
log() method appends the given
message to the
textarea that is assumed to be an
Here's how we can use the
InGameLogger from a Scene:
Most of this is similar to the previous
The main difference is that we are making a
this.add.dom() and then using it to create an
The example will result in
hello, world! appended to a
Textarea element. Deciding how to hide/show or make it prettier is up to you!
Now you can use
NODE_ENV or however you determine environment to decide which logging strategy to use for different builds.
A side benefit of this system is that it lets you easily adhere to the best practice of avoiding the use of
console.log() in production. Just don't add the
ConsoleLogger for production builds! 🍰
Real World Logging Strategy
There's a limitless number of different loggers you can create for different scenarios but we'll leave you with 1 more that is very useful in production to detect problems.
InGameLogger are good for development and QA testing but it doesn't give you information about issues that only players are having.
One solution is to have a cloud logger that connects to a real-time log aggregation service like Sumo Logic.
You can use this to detect errors from backend deploys, live-ops events, or similar by hooking into
window.onerror and logging those errors to the cloud while players are playing the game!
There is a lot more you can do with this logging system that is specific to the needs of your game. Using the Strategy Pattern helps keep the code clean and easy to maintain. 🤗
Another way to use the pattern is to have different strategies in the
ConsoleLogger depending on what should be logged. We used
message: string but you'll want to log many things in practice including
Array objects in which case maybe
console.table() should be used?
Think about how the Strategy Pattern can help you do that. 🤔
If you enjoyed this article then be sure to subscribe to our newsletter for more game development tips and techniques! 👇
Don't miss any future game development content
Subscribers get exclusive tips & techniques not found on the blog!
Join our newsletter. It's free. We don't spam. Spamming is for jerks.