Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Recently my team has programmed a custom developer console in a video game which

ID: 654957 • Letter: R

Question

Recently my team has programmed a custom developer console in a video game which can easily be hidden or displayed, because it's more comfortable and less of a hassle.

The Console class contains a log(string) function which should be easily accessible from anywhere outside of it, without having to reference it everywhere.

How could we design a method that can be easily accessed from the outside of the class? For example printing a message to the console in C++ is as simple as:

cout << "Message";
Well we want something similar at best, maybe Console::log(), but we are clueless how to achieve a proper way of doing it.

Cheers.

Explanation / Answer

The proper way to do this is to use a logging framework. The Apache (or ones modeled on) 'log4xyz' (where xyz is the language choice - log4j for java, log4net for .net, log4cpp for C++) is one of the more common styles.

Since this is specifically about C++, I'll be referencing log4cpp (though I'm most familiar with log4j).

log4cpp has a number of log levels - emerg, fatal, alert, crit, error, warn, notice, info, and debug (docs).

You set up the appenders (an appender is a way to log something - out to a database is one appender, to a file is another, to a file rotating every 24h is another one, to a file rotating every 1 Megabyte of data is another one, etc...) and the categories (a category is a group of things that log the same way). This can be done either in raw code, or in a property file.

The raw code would look something like this:

int main(int argc, char** argv) {
log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
appender1->setLayout(new log4cpp::BasicLayout());

log4cpp::Appender *appender2 = new log4cpp::FileAppender("default", "program.log");
appender2->setLayout(new log4cpp::BasicLayout());

log4cpp::Category& root = log4cpp::Category::getRoot();
root.setPriority(log4cpp::Priority::WARN);
root.addAppender(appender1);
root.addAppender(appender2);

...
}
A property file example can be seen on the webpage for the project.

In this code, you've got two appenders that are attached to the root category - one hooked up to an output stream, the other to a file. The root category is set to log things at the warn level or higher.

To invoke the loggers you would do things such as:

root.warn("%d + %d == %s ?", 1, 1, "two");
root << log4cpp::Priority::ERROR << "Streamed root error";
root << log4cpp::Priority::INFO << "Streamed root info";
And here you have three messages sent to the root logger, one with warn, one with error priority, one with info. As the info is lower than the warn error, it wouldn't get logged. Note the two different styles of logging.

Under this model, you would create an appender that logs to your Console object and hook it up to some category. Adjusting the log level on that category would then allow you to determine what shows up on the console.

Code for getting an arbitrary category looks something like:

log4cpp::Category& root = log4cpp::Category::getRoot();

log4cpp::Category& sub1 =
log4cpp::Category::getInstance(std::string("sub1"));
This could be invoked from anywhere. This allows you to abstract away the actual connection of the code to the console (or other logger). You could do things such as set up a remote logger (it logs to something listening on the network) so that you could have an entire screen of logging going on on a different computer (and to a file) as you test the game - without having to open up a local console to capture that information.