Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Logger System for any C++ project #2989

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions C++/logger/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.16)

project(Logger)

set (CMAKE_CXX_STANDARD 23)

add_executable(${PROJECT_NAME}
./log_categories.h
./debug_logger_component.h
./project_definitions.h
./singleton.h
./main.cpp)
9 changes: 9 additions & 0 deletions C++/logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
How to build and run from the terminal:

1. mkdir build
2. cmake -G "Unix Makefiles" .. && make
3. ./Logger

Requirements:
cmake 3.16 or any version after
gcc or another compiler that supports C++23(you can downgrade the version in the CMakeLists.txt file in the root down to C++17)
260 changes: 260 additions & 0 deletions C++/logger/debug_logger_component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
#pragma once

/*
*
* debug_logger_component.h is a collection of global variadic templated functions
* with different overload options(color, time/date, and more to come).
* The functionalities get compiled ONLY when DEBUG_MODE is defined in CMake,
* otherwise the funcitons bodies are compiled empty to avoid Debug Logs in RELEASE_MODE(optimization).
* TODO(Alex): debug_logger_component is planned to be a 'core' header that every class in the engine will have.
*
* !!! WARNINGS !!!
* Shipped products(in RELEASE_MODE) should not rely on these functions as they are only compiled in DEBUG_MODE
* However you do not need to delete them when shipping.
* (warning)inline global function with external linkage have undefined behavior.
*
*/

/* needed outside DEBUG_MODE to compile in all modes (EPrintColor)*/
#include "project_definitions.h"

#ifdef DEBUG_MODE

#include <iostream>
#include <chrono>
#include <ctime>

#include "log_categories.h"

#endif /* DEBUG_MODE */

/**
* @brief Logs debug information to the console in debug mode.
*
* This function prints the provided arguments to the console, but only if
* the application is compiled with `DEBUG_MODE` enabled. It uses variadic
* templates to accept a flexible number of arguments and formats them as
* a single line of output.
*
* @tparam Args Variadic template parameter representing the types of the arguments to be logged.
* @param args The arguments to be printed. These can be of any type that is compatible with `std::ostream` (e.g., `std::cout`).
*
* @note
* - This function only works when the `DEBUG_MODE` macro is defined during compilation.
* If `DEBUG_MODE` is not defined, the function has no effect.
* - Before printing, the function checks whether the default logging category (`ELogCategory::Default`)
* is enabled. If it is disabled, no output is printed.
* - The logging format starts with a prefix (`>>>`), followed by the arguments, each printed in sequence,
* and ends with a newline.
*
* @details
* - The function uses the `LogManager` singleton to check whether the default log category is disabled.
* If the category is disabled, the function returns early without printing anything.
* - The use of a fold expression `([&] { std::cout << args; } (), ...)` ensures that all arguments
* are printed in sequence, with no separator between them.
* - This function is marked `noexcept` to ensure that it does not throw exceptions.
*
* Example usage:
* @code
* Debug_Log("This is a debug message with a number: ", 42);
* // Output: >>> This is a debug message with a number: 42
* @endcode
*/
template<class... Args>
inline void Debug_Log(Args&&... args) noexcept
{
#ifdef DEBUG_MODE
// Get the LogManager instance and check if the default logging category is disabled
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(ELogCategory::Default))
{
return;
}
std::cout << ">>> ";
([&]
{
std::cout << args;
} (), ...);
std::cout << std::endl;
#endif /* DEBUG_MODE */
}

/**
* @brief Print on console dynamic number of args with a print category
*
* The body of the function is only compiled in DEBUG_MODE(RELEASE_MODE optimization)
*
* @param category: print category
* @param ...args: dinamic number of arguments to print regardless of their type
*
* Example usage:
* Debug_Log("Loading next level", 69, 420.69);
*
* @return void
*/
template<class... Args>
inline void Debug_Log(ELogCategory category, Args&&... args) noexcept
{
#ifdef DEBUG_MODE
/* Do not print disabled categories */
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(category))
{
return;
}
std::cout << ">>> ";
([&]
{
std::cout << args;
} (), ...);
std::cout << std::endl;
#endif /* DEBUG_MODE */
}

/**
* @brief Print on console dynamic number of args with color
*
* The body of the function is only compiled in DEBUG_MODE(RELEASE_MODE optimization)
*
* @param color: print color
* @param ...args: dinamic number of arguments to print regardles of their type
*
* Example usage:
* Debug_Log(EPrintColor::Red, "Loading next level", 69, 420.69);
*
* @return void
*/
template<class... Args>
inline void Debug_Log(const EPrintColor color, Args&&... args) noexcept
{
#ifdef DEBUG_MODE
/* Do not print if default category is disabled */
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(ELogCategory::Default))
{
return;
}
std::string color_code = Color_To_Ansi(color);
std::cout << ">>> " << color_code;
([&]
{
std::cout << args;
} (), ...);
std::cout << UNIX_COLOR_END_TAG << std::endl;
#endif /* DEBUG_MODE */
}

/**
* @brief Print on console dynamic number of args with color and a category
*
* The body of the function is only compiled in DEBUG_MODE(RELEASE_MODE optimization)
*
* @param category: category to print in
* @param color: print color
* @param ...args: dinamic number of arguments to print regardles of their type
*
* Example usage:
* Debug_Log(EPrintColor::Red, "Loading next level", 69, 420.69);
*
* @return void
*/
template<class... Args>
inline void Debug_Log(const ELogCategory category, const EPrintColor color, Args&&... args) noexcept
{
#ifdef DEBUG_MODE
/* Do not print disabled categories */
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(category))
{
return;
}
std::string color_code = Color_To_Ansi(color);
std::cout << ">>> " << color_code;
([&]
{
std::cout << args;
} (), ...);
std::cout << UNIX_COLOR_END_TAG << std::endl;
#endif /* DEBUG_MODE */
}

/**
* @brief Print on console dynamic number of args with color and time option
*
* The body of the function is only compiled in DEBUG_MODE(RELEASE_MODE optimization)
*
* @param color: print color
* @param bShowTime: show date and time of function call
* @param ...args: dinamic number of arguments to print regardles of their type
*
* Example usage:
* Debug_Log(EPrintColor::Red, true, "Loading next level", 69, 420.69);
*
* @return void
*/
template<class... Args>
inline void Debug_Log(const EPrintColor color, const bool bShowTime, Args&&... args) noexcept
{
#ifdef DEBUG_MODE
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(ELogCategory::Default))
{
return;
}
if(bShowTime)
{
auto call_time = std::chrono::high_resolution_clock::now();
auto time_struct = std::chrono::system_clock::to_time_t(call_time);
std::cout << std::ctime(&time_struct);
}
std::string color_code = Color_To_Ansi(color);
std::cout << ">>> " << color_code;
([&]
{
std::cout << args;
} (), ...);
std::cout << UNIX_COLOR_END_TAG << std::endl;
#endif /* DEBUG_MODE */
}

/**
* @brief Print on console dynamic number of args with color, time and category option
*
* The body of the function is only compiled in DEBUG_MODE(RELEASE_MODE optimization)
*
* @param category: print category
* @param color: print color
* @param bShowTime: show date and time of function call
* @param ...args: dinamic number of arguments to print regardles of their type
*
* Example usage:
* Debug_Log(ELogCategory::Engine, EPrintColor::Red, true, "Loading next level", 69, 420.69);
*
* @return void
*/
template<class... Args>
inline void Debug_Log(const ELogCategory category, const EPrintColor color, const bool bShowTime, Args&&... args) noexcept
{
#ifdef DEBUG_MODE
/* Do not print disabled categories */
LogManager* logManager = LogManager::GetInstance();
if(logManager->IsCategoryDisabled(category))
{
return;
}
if(bShowTime)
{
auto call_time = std::chrono::high_resolution_clock::now();
auto time_struct = std::chrono::system_clock::to_time_t(call_time);
std::cout << std::ctime(&time_struct);
}
std::string color_code = Color_To_Ansi(color);
std::cout << ">>> " << color_code;
([&]
{
std::cout << args;
} (), ...);
std::cout << UNIX_COLOR_END_TAG << std::endl;
#endif /* DEBUG_MODE */
}

115 changes: 115 additions & 0 deletions C++/logger/log_categories.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#pragma once

/*
* TODO(Alex): Add API for runtine disabling/enabling of the categories. Maybe with IMGUI
* TODO(Alex): Add name support for each category(optionally show the category name)
*/

#include <map>
#include "singleton.h"
#include "project_definitions.h"

/**
* @brief Manages logging categories and their states.
*
* The `LogManager` class is responsible for managing the enabled or disabled
* state of various logging categories. It allows enabling or disabling specific
* logging categories and checking their current state. This class is a singleton
* and inherits from the `Singleton` class template.
*
* @note
* - This class uses the `ELogCategory` enum to represent different logging categories
* and `ELogCategoryState` to represent their enabled/disabled state.
* - All logging categories are initialized as enabled by default.
*
* @tparam LogManager A singleton that ensures only one instance of the `LogManager` class exists.
*
* Example usage:
* @code
* LogManager* logManager = LogManager::GetInstance();
* logManager->DisableCategory(ELogCategory::Debug); // Disable debug category
* if (logManager->IsCategoryEnabled(ELogCategory::Default)) {
* std::cout << "Default logging is enabled." << std::endl;
* }
* @endcode
*/
class LogManager : public Singleton<LogManager>
{
public:

/**
* @brief Constructs the `LogManager` and initializes all categories as enabled.
*
* This constructor initializes all logging categories to `Enabled` by default.
* The number of categories is determined by `ELogCategory::AutoCount`, which
* represents the total number of available logging categories.
*/
LogManager() noexcept
{
for (int i = 0; i < static_cast<int>(ELogCategory::AutoCount); ++i)
{
logCategoryStates[static_cast<ELogCategory>(i)] = ELogCategoryState::Enabled;
}
}

/**
* @brief Enables a specific logging category.
*
* This function enables the specified logging category, allowing log messages
* from this category to be processed.
*
* @param category The logging category to enable.
*/
void EnableCategory(ELogCategory category)
{
logCategoryStates[category] = ELogCategoryState::Enabled;
}

/**
* @brief Disables a specific logging category.
*
* This function disables the specified logging category, preventing log messages
* from this category from being processed.
*
* @param category The logging category to disable.
*/
void DisableCategory(ELogCategory category)
{
logCategoryStates[category] = ELogCategoryState::Disabled;
}

/**
* @brief Checks if a specific logging category is enabled.
*
* This function checks if the specified logging category is currently enabled.
*
* @param category The logging category to check.
* @return `true` if the category is enabled, `false` otherwise.
*/
bool IsCategoryEnabled(ELogCategory category) const
{
return logCategoryStates.at(category) == ELogCategoryState::Enabled;
}

/**
* @brief Checks if a specific logging category is disabled.
*
* This function checks if the specified logging category is currently disabled.
*
* @param category The logging category to check.
* @return `true` if the category is disabled, `false` otherwise.
*/
bool IsCategoryDisabled(ELogCategory category) const
{
return logCategoryStates.at(category) == ELogCategoryState::Disabled;
}

private:
/**
* @brief Stores the state (enabled/disabled) of each logging category.
*
* This map holds the current state of all logging categories, where the key is
* an `ELogCategory` enum and the value is an `ELogCategoryState` enum.
*/
std::map<ELogCategory, ELogCategoryState> logCategoryStates;
};
Loading