Installation

The library is designed to be statically compiled into your project. Just copy the files to your project and add them to your compilation sources, and you're done with the installation. Next 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 define the UVK_LOG_EXPORT_FROM_LIBRARY macro and when you're compiling the larger library make sure the macro UVK_LIB_COMPILE is defined. When compiling with UVK_LIB_COMPILE you export your symbols for Windows using __declspec(dllexport), while if disabled it uses __declspec(dllimport)

No instant error termination

Handling errors will be explained below. In the most basic sense, if the NO_INSTANT_CRASH is defined, instead of instantly crashing on an error, we place an std::cin.get() call before terminating the application so that you can for example, look at the last frame of your application, check the output, etc.

Enabling the dear imgui console widget

You can enable the dear imgui console widget by including UVKLogImGui.h, having imgui.h in your include path and defining the UVK_LOG_IMGUI macro. This widget has 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) - Sets the current file we're logging to, to a different one or initializes a new file
  3. void setLogOperation(LogOperations op) - Sets the current log operation
  4. void log(const char* message, LogType type, args&&... argv) - The log functions, 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, TERMINAL, FILE and FILE_AND_TERMINAL. By default, we use the TERMINAL operation as no resources are needed for it. Please do note that 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 lookup table:

  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 log function of the Logger class 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, so this is how you can print 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);

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.

Create an instance of the class, next 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 but keep in mind that you need to provide a bool& for the X icon on the window's header.

The other 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, which you can use in your own window, or inside the global framebuffer. 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. Simply pass your colour as a 4D vector and a LogType enum member

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 initialize 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 initialized you can pass it to the static member of the UVKLog::ImGuiConsole class' addCommand(const CommandType& cmd) function. After you have added this your command is ready to be used. Here is an 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 static member of UVKLog::ImGuiConsole class' addToMessageLog(const std::string&, LogType type) function. This function 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 to record how much time a task takes. To use the timer instantiate the Timer class.

Call the start function and when you want it to end call the stop function, then get how much time the task took using the get function.

If you still want to record call the stop function again without calling the start function. When you call the start function you restart your 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!");