Make your logs interactive and squash bugs faster

With modern debuggers and testing tools, the need for logging is not as great as it once was. But there are times when debuggers won’t do (for example, on players’ machines, or when hunting down bugs that occur sporadically). For these instances, logging can still be useful, and with modern web technology, you can build a powerful logger that is more than just an endless stream of text.

This article deals with ways to make logs more useful by using HTML, CSS, and JavaScript. This extends the ideas proposed by James Boer in An HTML-Based Logging and Debugging System (Game Programming Gems 4 ).

I assume that you already know basic HTML (including tags and attributes), that you know basic CSS, and that you know some basic JavaScript. I also assume that you know how to use all of them together. If you don’t, you can try out HTML Dog’s tutorials on HTML and CSS, and take a look at Simon Wilson’s A re-introduction to JavaScript at the Mozilla Developer Network. (You need only a very basic understanding, nothing close to what you would need to develop full-fledged websites.)

A discrete event log is used to track events that happen infrequently in your game. You might log when a network connection has been established, when an AI agent switches its state, when the game starts and ends, and so on.

The traditional way to do this would be to write out each event to a text file. But text-based logs can be cumbersome:

  • They are difficult to interpret.
  • Important information is difficult to spot.
  • It is hard to trace back a log entry to a line in your source code.

Fortunately, it’s quite simple to address these issues. HTML, CSS, and a bit of JavaScript can help you to create a visually rich, interactive log that makes it quick and easy to find what you are looking for.

To give you an idea of where I’m heading with this, here’s an example of such a log (this example works best in Chrome).

You need to develop three components:

  1. A Logger class in the programming language of your game engine. This class should write the HTML code of your log to a file, and should provide functions that you can use in the rest of the game’s code to log events.
  2. A Cascading Style Sheet to format the HTML page visually.
  3. A JavaScript file to enable you to hide or show elements in the HTML page interactively.

Here are some tips to guide you:

1. Use HTML

This tip makes all of the others possible. HTML allows us to do the following:

  • Visually format the contents of the log to clarify the structure.
  • Add JavaScript to make the log interactive.
  • Add screenshots.
  • Add other helpful components.

And you can use your browser to view your log.

You can decide on how you want to use HTML to create your log. I like a minimalist approach: I simply wrap each log message in paragraph tags, like this:

<p>Logger started</p>
<p>Path found</p>
<p>Connected to server</p>

I don’t bother with adding html, head and body tags. Besides, the game might crash before it gets a chance to close these tags. As long as my favourite browser can render the page, I’m happy.

My logs usually also include these helpful bits:

  • date and time (The time helps you to be sure that you are looking at the most recent log.)
  • version or system information
<h1 class="date">12/30/2010 4:57:31 PM</h1>
<h2>Windows Standalone</h2>
<p>Logger started</p>
<p>Path found</p>
<p>Connected to server</p>

2. Use class attributes to label different types of log entries

To show you what I mean, here’s a list of classes that I often use:

system input network
framework graphics client
debug physics server
gamelogic ai error
gui audio warning

There are also game-specific classes, such as vehicle, gun, and cargo. The classes that will be the most useful to you will depend on your game, on the part of your game that you are logging, and on the purpose of the log.

<h1 class="date">12/30/2010 4:57:31 PM</h1>
<h2>Windows Standalone</h2>
<p class=”system”>Logger started</p>
<p class=”ai”>Path found</p>
<p class=”network”>Connected to server</p>

The class attributes enable us to specify visual formatting for each type of log entry. And that’s where the next two tips come into play.

3. Use CSS to distinguish different types of log entries visually

When we format the various types of log entries visually, it enables us to see, at a glance, which messages relate to which parts of the game. This makes it much easier to find information that might be relevant to a specific problem, and it helps to highlight important messages, such as errors or warnings.

Here’s a snippet of CSS that specifies the styles for some of the log entries above. The snippet only shows the styles for all paragraphs and for the system class.

p {
  font-family:"Calibri";
  margin:0;
}

.system {
  background-color:#6600AA;
  color:#FFFFFF;
  display:block;
}

4. Use JavaScript to hide or show details

For a decent run of your game, even colour coding might not be enough. Colour coding also becomes less useful as the number of colours increases. The more colours there are, the harder it becomes to distinguish between similar colours. JavaScript to the rescue! To reduce the clutter in your log file, you can add a few buttons that enable you to hide or show all details or log entries of a certain type.

You can do this by including a few script tags at the top of your HTML page. These tags should call external JavaScript files that add the required functionality to the buttons.

The following function will hide or show tags of a particular class:

function toggleVisibilityOfClass(className)
{
  elements = getElementsByClass(className);
  pattern = new RegExp("(^|\\s)Button(\\s|$)");

  for(i = 0; i < elements.length; i++)
  {
    if(!pattern.test(elements[i].className))
    {
      if(elements[i].style.display != 'none')
      {
        elements[i].style.display = 'none'
      }
      else
      {
        elements[i].style.display = 'block'
      }
    }
  }
}

The code checks whether the CSS display property is set to none. If it is, it is changed to block. Otherwise, it is changed to none.

To call this function, you can add three buttons to the top of your HTML page (this should also be done in the Logger class). The above code makes use of the following function to get all the relevant tags:

function getElementsByClass(searchClass, node, tag)
{
  var classElements = new Array();

  if (node == null)
  {
    node = document;
  }
  if (tag == null )
  {
    tag = '*';
  }

  var element = node.getElementsByTagName(tag);
  var elementLength = element.length;
  var pattern = new RegExp("(^|\\s)" + searchClass + "(\\s|$)");

  for (i = 0, j = 0; i < elementLength; i++)
  {
    if (pattern.test(els[i].className))
    {
      classElements[j] = element[i];
      j++;
    }
  }

  return classElements;
}

The modified HTML that should be written by the Logger now looks like this:

<input type="button" value="System" class="system button"
      onclick="hide_class('system')" />
<input type="button" value="AI" class="ai button"
      onclick="hide_class('ai')" />
<input type="button" value="Networking" class="networking button"
      onclick="hide_class('networking')" />
<h1 class="date">12/30/2010 4:57:31 PM</h1>
<h2>Windows Standalone</h2>
<p class=”system”>Logger started</p>
<p class=”ai”>Path found</p>
<p class=”network”>Connected to server</p>

To format the buttons visually, you need to add another class selector to your CSS file:

.button {
  display:inline;
}

The style rules for the system, ai, and network classes have already been defined for the regular log entries. The buttons share these classes, so that, for example, the system button will have the same colours as the system log messages.

You can use additional CSS-magic to keep the buttons at the top while the page scrolls (wrap the buttons in a div tag, set position to fixed, and add some padding to the h1 at the top).

5. Add a time stamp to each log entry

Although the stream of log entries already gives you a timeline of the events in your game, adding a time stamp to each log entry helps you to avoid causal fallacies. For example, you might think that the event just before an error is the cause of the error; but then you might see that the event happened several seconds before the error, which probably means that the event could not have caused the error.

6. Add a stack trace to each log entry

To see where each log entry comes from, you can add a stack trace to it. Stack traces will take up a lot of space in the HTML page, but you can hide or show them by using JavaScript and CSS.

It’s best to add a hide or show button to each individual stack trace. The following JavaScript function will toggle the visibility of tags with unique IDs. (Your Logger class should assign appropriate IDs to the HTML tags.)

function hide(id)
{
  element_style = document.getElementById(id).style;
  status = element_style.display;

  if (status != 'block')
  {
    element_style.display = 'block';
  }
  else //status == visible
  {
    element_style.display = 'none';
  }
}

7. Add structure

Sometimes it is useful to assign a level to a log entry. In other words, you can label a log entry based on its priority, on its call-depth, or on the system layer to which it relates. To make it easy to identify each of these levels – you guessed it – you can use CSS to adjust the indentation and the font size, and you can use JavaScript to hide or show the levels that you are interested in. To label the log entries according to levels, just add more classes to the entries’ class attributes:

<p class=”priority_high ai”> AI Started</p>

You can take this idea as far as you’d like. By assigning multiple classes to log entries, you can have multiple views of the same data.

When you use multiple classes for each log entry, the code that enables you to hide or show messages of a certain type will be a bit more complex. You will need to think carefully about how that will work.

8. Add screenshots

Hook up your screenshot function to your logging system, so that when a screenshot is taken, it will also appear in the HTML log. Treat screenshots as normal log entries with time stamps and stack traces. Just like other log entries, you can make it possible to hide or show screenshots using CSS and JavaScript.

Including screenshots in your log will make it easier to see what was happening at a given time and to relate this to other log entries. And if you add a shortcut key to your game that enables you to take a screenshot when you see something weird, having that screenshot among the other log entries can be invaluable.

9. Write once, use everywhere

’Nough said.

10. Use the logger effectively

  • Do not log anything that happens on every frame. (A follow-up article will describe continuous event logs that can be used for this purpose instead.)
  • Sometimes you might need to log something to debug a specific issue (especially when circumstances might prevent you from using the debugger). For these situations, it might be a good idea to add a special function that calls your regular logging function and also prints a warning in the console that will help you to remember to remove these calls when you have resolved the issue.
  • There are some drawbacks to logging. See Jeff Atwood’s article for a discussion.

Happy logging!

  • http://japskua.wordpress.com Japskua

    Hey, thanks man! A really interesting approach for logging and catching those annoying errors. I really have to give this thing a shot!

    • Herman Tulleken

      Cool :) When you do, let us know in your blog how it has worked out for you!

  • deadthreaddev

    hello i was wondering what is the license for the current source code. i know i could unroll my own solution from scratch, but tweaking from your source code seems a better (and faster) way : )

    • Dev.Mag

      Hi deadthreaddev,

      Please use the code anyway you please, as long as you note that it has been given for educational purposes and comes with no guarantee whatsoever. Don’t sue us :P

      ht

  • Pingback: 50 Tips for Working with Unity (Best Practices) » devmag.org.za

  • Farrell

    This is awesome. Thank you.

  • Nezabyte

    Great article, thanks. Was able to implement it without much trouble with the given example, including the screenshot functionality.