0
\$\begingroup\$

I am currently developing a game in C++, using SDL2. For logging, I wrote this Log class:

Log.h

#ifndef LOG_H
#define LOG_H

#include "Singleton.h"

#include <fstream>
#include <string>

class Log : public Singleton<Log>
{
public:

    Log();
    ~Log();

    void regular_log(const std::string& msg); // writes to "file"
    void warning_log(const std::string& msg); // writes to "file", but adds [WARNING] prefix
    void fatal_log(const std::string& msg); // writes to "file", but adds [FATAL] prefix and throws runtime exception

private:

    std::string get_datetime(std::string format) const;

    std::ofstream file;

};
#endif

Log.cpp

#include "Log.h" 

#include <chrono>
#include <ctime>
#include <filesystem>
#include <exception>

Log::Log()
{
    std::filesystem::create_directory("Logs\\");
    file.open("Logs\\" + get_datetime("%F %H-%M-%S.txt"), std::ios::out);
}

Log::~Log()
{
    file.close();
}

void Log::regular_log(const std::string& msg)
{
    file << "[" << get_datetime("%X") << "]" << " - " << msg << "\n";
}

void Log::warning_log(const std::string& msg)
{
    file << "[" << get_datetime("%X") << "]" << "[WARNING]" << " - " << msg << std::endl;
}

void Log::fatal_log(const std::string& msg)
{
    file << "[" << get_datetime("%X") << "]" << "[FATAL]" << " - " << msg << std::endl;
    throw std::runtime_error(msg);
}

std::string Log::get_datetime(const std::string format) const
{
    std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());

    char buf[100] = { 0 };
    std::strftime(buf, sizeof(buf), format.c_str(), std::localtime(&now));

    return buf;
}

My problem is that I am not entirely sure what to log in my code. Is every little altering of the game state worth a regular log, things like "Object created", "Object moved from xyz to xyz", "Inventory opened by player 123"? Or should logging be reserved for more important causes? Where do I draw the line?

\$\endgroup\$
8
  • 1
    \$\begingroup\$ Why would you want to log anything? \$\endgroup\$
    – Vaillancourt
    Sep 5 at 11:48
  • \$\begingroup\$ @Vaillancourt Because logs can be very useful in tracking down errors in your application. To identify what went wrong, it is nice to know what exactly happened, isnt it? \$\endgroup\$
    – Tom Gebel
    Sep 5 at 11:54
  • 1
    \$\begingroup\$ I think Vaillancourt is offering you a hint you can use to reason about what to log in your game: what kinds of problem are arising in the game you're making? What information would you want to have access to in order to track down those problems? Once you answer those questions, the answer to what to log falls out. But these are questions that you can answer better than we can, because you're the one with first-hand experience in your codebase and the problems it's exhibited so far. \$\endgroup\$
    – DMGregory
    Sep 5 at 12:33
  • 1
    \$\begingroup\$ Could you not add logs reactively when a new problem arises and you find you need more information to track it down, then remove those logs once you've solved the problem and no longer need that info? Logging code is not carved in stone, it can change as easily as any other code you write over the development cycle of your game. \$\endgroup\$
    – DMGregory
    Sep 5 at 12:39
  • 1
    \$\begingroup\$ Irregular, you say? Welcome to game development! 😉 But to be less glib: it sounds like you're thinking of sprinkling in logs proactively, according to some principle, the way you might structure and comment code for legibility as you go. While that might make sense for obvious error states, I don't think it makes sense for most routine successful operations. Otherwise you'd end up with a bloated log full of millions of confirmations of stuff that went right, making the search for the one thing that went wrong a challenge of spotting a needle in a haystack. I'll elaborate in an answer shortly… \$\endgroup\$
    – DMGregory
    Sep 5 at 12:56
4
\$\begingroup\$

Logs serve a purpose, which is to help the developers detect, diagnose, isolate, and fix problems. When everything is running perfectly, you're probably not even reading the log.

So the question of what to log reduces to the question of what helps you solve problems.

Stuff that definitely isn't right: Errors

As you code a system, you'll naturally encounter possibilities that should never arise at runtime - like calling the Attack() function with no target, or popping a game/UI/behaviour state off the stack and leaving it empty, or encountering an unknown/invalid message in an asset file or network packet. These are natural places to log an error to alert the developer to the problem (or, to halt on an assertion / throw an uncaught exception and crash the game on the spot).

Stuff that looks suspicious, but might be OK for now: Warnings

Other scenarios might be more ambiguous. You're missing an important parameter, but you can carry on with a semi-reasonable default. Or you're over-budget for a particular asset class or performance metric, but it might be tolerable if it's just happening in a limited part of the game or while work is in progress.

These are good places to log a warning to highlight the presence of an issue that merits a closer look, especially if it might not otherwise be visible (because of sensible defaults or because it's just one of many cumulative issues that are only game-breaking if they pile up).

You might, however, want to add a flag that lets you check if you've already warned about this and skip repeating it, so that you don't spam the log with warnings about situations that are actually OK.

(As an aside: it's also important to be disciplined about fixing warnings that turn out to be real problems, and exempting the situations that are permissible, so that you don't get in the habit of ignoring warnings in your console/logs. Seeing the project go from 0 warnings to 3 will catch your attention and you'll find what changed. Seeing it go from 103 warnings to 106 might not be so alarming, letting bad code/content creep in unnoticed.)

Stuff that helps you isolate a current or recurring class of problem: Logs

If it's not an error and not suspicious, then your default position should be: don't log it.

Logging has a cost, both in performance, and in attention. You might be surprised how easy it is to bog down a game by doing string processing and file IO in the middle of a reasonably hot loop somewhere. But also, excessive logging can actually make it harder to spot what's going on, because every system in the game is spamming the chat with conversations unrelated to the thread you're trying to follow.

So, if a current problem you're having would benefit from some logs to help you trace how the game state evolves into/through the problem case, add logs tactically to the places that inform that search. Once you're sure you've solved that problem, go back and remove / disable those logs.

If you find you have to come back to a system to re-add/enable logging repeatedly, that's a good sign those logs are useful for tracking down a class of recurring problems, and are worth keeping around more permanently.

For complex games, you might want to introduce a logging flag system, where via a launch argument or console command you can enable or disable specific subsets of log messages. So an AI programmer can launch with -ai_log_enable for their routine debugging, and the graphics programmer with -gfx_log_enable, and neither one is distracted by spam from systems they're not working on. Or even further, the AI programmer might want to enable -ai_log_verbose to get super granular logs of individual actions/transitions when tracking down a thorny issue, that are otherwise too spammy to keep on by default. You can filter as specifically as you want, like EnableLog(Sound_Played_Events), if that helps solve the kinds of problems your game projects tend to have.

\$\endgroup\$
1
\$\begingroup\$

Years ago I had this exact same question, as I was trying to be proactive about it, as you are now.

In the end? No. You can't log specific categories of things, because these categories change from one game to the next, especially depending on where the complexity lies, but more importantly, they also change from one dev phase to the next - and of such phases, there may be hundreds or thousands before a game is released. As others have suggested, only you can know this about your current game under development.

It's more important to:

  • make the process of selecting and logging specific categories as painless as possible, that is, make it as easy as you can to get a meaningful log entry for any given thing by inserting the minimum possible information at the necessary codepoint;
  • make the logs as filterable as possible so you don't have to go about deleting / commenting former log statements just to be able to find entries pertaining to what is currently going on.

Existing systems can be a source of inspiration:

  • Unity does an alright job, but uses only 3 categories: info, warn, and error. This is a single-level categorisation system that focuses on log entry criticality.

  • Android, in conjunction with its IDEs, actually does a reasonable job of doing a multi-level categorisation by using tags in addition to criticality, which can be filtered through the IDE's UI, while the basic system within Android remains relatively conceptually simple - a Good Thing.

I'd suggest you write (or source) a suitable logging system based on ease of adding new lines to generate logs, and filtering. If it has support for logging via varied output streams, even better.

\$\endgroup\$
1
\$\begingroup\$

I think I may have something small to contribute. Other answers appear to cover more the development side of "logging". However, this:

Inventory opened by player 123

...hints me that you might be in the process of developing a multiplayer game.

I unfortunately don't have first-hand experience with developing such games, but as I understand from multiple sources I've seen over the years, in this context, you should "log everything".

What is "everything"? It's mostly related to the information your devs and your GMs need to

  • trace why quest X is buggy
  • how could player Y scam player Z
  • how could payer A duplicate item B
  • how could player C illegally teleport from D to E
  • assert that player F is actually a racist bully that ruins the game experience for everyone
  • how guild G manages to win raids every weekend while never dying and making the server crash at the same time
  • etc.

As it's been said before, you'll need to balance what you log and how you log it in a non-invasive way to not slow down your game servers, and in a practical way to help your devs and your GMs find quickly and easily the information they need. It's a nice challenge.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.