Installation

The library is designed to be statically compiled into your project. Simply copy the files to your project and add them to your compilation sources. After that, add an include directive for UVKLog.h in one of your files and you're done

Additional Features

Compiling for a library

Compiling the logger as part of a larger library and want to export the symbols? To do that you need to:

  1. Define the UVK_LOG_EXPORT_FROM_LIBRARY
  2. Make sure the UVK_LIB_COMPILE macro is defined when compiling the larger library

When compiling with UVK_LIB_COMPILE, you export your symbols for Windows using __declspec(dllexport), and when it's disabled, it uses __declspec(dllimport).

No instant error termination

Handling errors will be explained below. Most of the time, an error will crash the application immediately, except if the NO_INSTANT_CRASH macro is defined. It does the following:

  1. If defined: inserts a call to std::cin.get(), before calling std::terminate
  2. If not defined: does nothing

This is useful in debug cases where you want to check the last frame of your running program before completely crashing.

Enabling the dear imgui console widget

To enable the dear imgui console widget do the following:

  1. Include UVKLogImGui.h
  2. Have imgui.h in your include path
  3. Define the UVK_LOG_IMGUI macro

This widget provides a simple console interface where every log is recorded to a buffer, which you can view in dear imgui. Additionally, you can also add your own commands. Using the widget is explained below.

Standard Logging

There is a single class you can use for all your logging needs, this class is the Logger class, part of the UVKLog namespace in the UVKLog.h header. The Logger class contains the following static functions that you can use for logging:

  1. void setCrashOnError(bool bError) - If set to true, every time you call the log function with an error as the type it will terminate the application
  2. void setCurrentLogFile(const char* file) - Changes the current file we're logging to and or creates a new file
  3. void setLogOperation(LogOperations op) - Sets the current log operation
  4. void log(const char* message, LogType type, args&&... argv) - Logs a message. Given a message, a log type and an optional list of templated variadic arguments, will log a message using the current log operation

Log Operations

Log operations are set using the void setLogOperation(LogOperations op) static member function of the Logger class. To change the log operation, pass this function an argument of type LogOperations. This type is the following enum:

enum LogOperations
{   
    // This is the default operation
    UVK_LOG_OPERATION_TERMINAL,
    UVK_LOG_OPERATION_FILE,
    UVK_LOG_OPERATION_FILE_AND_TERMINAL,
};

as you can see, we have 3 log operations:

  1. TERMINAL - default operation
  2. FILE
  3. FILE_AND_TERMINAL.

Note

Using the setCurrentLogFile function while in the TERMINAL operation will not change the operation to FILE or FILE_AND_TERMINAL. You're responsible for changing your operation type!

Log Types

There are currently 6 types of log messages that you can pass to the log function. They're stored in the LogType enum, which looks like this:

enum LogType
{
    UVK_LOG_TYPE_WARNING = 1,
    UVK_LOG_TYPE_ERROR = 2,
    UVK_LOG_TYPE_NOTE = 4, 
    UVK_LOG_TYPE_SUCCESS = 0,
    UVK_LOG_TYPE_MESSAGE = 3
};

The types other than ERROR don't have any functionality, other than being displayed with a different title and colour in the terminal/GUI console.

By default, the ERROR type also works like the others, but you can enable termination on errors, which will terminate your process when an error is logged.

The different log types are represented with different colours in the terminal/GUI console. Here is a cheat sheet:

  1. SUCCESS - Green
  2. NOTE - Blue
  3. MESSAGE - White/Grey
  4. WARNING - Yellow/Orange
  5. ERROR - Red

Logging

To log a message simply use the Logger::log function by passing a message, a LogType, and an optional list of templated variadic arguments.

For example, here is how you can print Hello, World! as a success:

UVKLog::Logger::log("Hello, World!", UVK_LOG_TYPE_SUCCESS);

Additionally, you can provide a list of optional templated variadic arguments.

Printing a mix of strings and numbers:

UVKLog::Logger::log("Hello, World!", UVK_LOG_TYPE_SUCCESS, " Here we have a regular integer: ", 5, "; And here we have a float: ", 10.5f);

Note

Any type that supports outputting to std::ostream can be used with the logger.

Using the dear imgui console widget

To use the console widget, first go back here to see how you can enable it.

Now that you have it enabled you can use the UVKLog::ImGuiConsole class:

  1. Create an instance of the class
  2. In you game loop, decide on how you want to display it

If you want a separate ImGui window, you can use the void displayFull(bool& bOpen, bool* bInteractingWithTextbox) member function.

The second argument is a bool* that you can use to check if the text box is currently active. If you don't need that functionality you can leave it as nullptr.

If you don't want a full window, you can also use the void display(bool* bInteractingWithTextBox) function. The function takes the same bool* for checking, whether you're interacting with the textbox or not. If you're not using this feature leave it as nullptr.

Changing the colours

To change the colours you can use the void setLogColour(ImVec4 colours, LogType type) function.

Adding Commands

By default, there are 2 commands in the console, clear and help which are self-explanatory.

To add your own commands, you can initialise a struct of type CommandType, which looks like this:

struct CommandType
{
    std::string cmd;
    std::string cmdHint;
    std::function<void(const std::string&)> func;
};

The cmd string is the name of the command. We check if the command passed in the text box starts with the contents of the cmd string.

The cmdHint string defines the string to be shown in the help message.

The func function pointer is called when the command is found. It takes a const std::string& which you can use to parse additional arguments.

After you have your struct initialised, you can pass it to the UVKLog::ImGuiConsole::addCommand(const CommandType& cmd) function.

After you have added this, your command is ready to be used. Example:

const UVKLog::CommandType customCommand =
{
    .cmd = "test",
    .cmdHint = "Just a test command",
    .func = [&](const std::string&){ UVKLog::ImGuiConsole::addToMessageLog("Test", UVK_LOG_TYPE_MESSAGE); }
};

UVKLog::ImGuiConsole::addCommand(customCommand);

Adding messages to the log without formatting

Instead of using standard logging, you can also use the UVKLog::ImGuiConsole::addToMessageLog(const std::string&, LogType type) function. It adds a string with a log type for colour, but unlike the normal log function, it doesn't have any formatting. Here is an example:

UVKLog::ImGuiConsole::addToMessageLog("Test", UVK_LOG_TYPE_MESSAGE);

Utility classes

The Timer class

Together with the logging library and console widget, we also include a Timer class that is used to record how much time a task takes.

Usage:

  1. Create an instance of the Timer class
  2. Call the start function to start the timer
  3. Call the stop function to stop the timer
  4. Get the time using the get function

If you want to continue recording, call the stop function again without calling the start function. In reality the start function simply resets the timer, so not calling it allows you to use the same timer object multiple times.

Here's an example:

Timer timer;
timer.start();

doStuff();

timer.stop()
UVKLog::Logger::log("doStuff took ", UVK_LOG_TYPE_NOTE, timer.get(), " milliseconds!");

doStuff2();

UVKLog::Logger::log("doStuff and doStuff2 took ", UVK_LOG_TYPE_NOTE, timer.get(), " milliseconds!");