Created
February 26, 2026 13:44
-
-
Save Scavanger/f28de491d3e4d6bf8cf4c0866ceaf576 to your computer and use it in GitHub Desktop.
Logger - A thread-safe singleton logging library for C++20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| /// <summary> | |
| /// Logger - A thread-safe singleton logging library for C++20 | |
| /// | |
| /// Features: | |
| /// - Thread-safe logging to file and console | |
| /// - Support for multiple log levels (Info, Warn, Error, Debug) | |
| /// - Type-safe formatted output using C++20 std::format | |
| /// - Automatic timestamping of log entries | |
| /// - Optional console output for GUI applications | |
| /// - Configurable log levels and file names | |
| /// | |
| /// Example usage: | |
| /// <code> | |
| /// // Basic usage with Logger instance | |
| /// Logger::getInstance().info("Application started"); | |
| /// Logger::getInstance().warn("Low memory: {} MB", availableMemory); | |
| /// Logger::getInstance().error("Failed to open file: {}", filename); | |
| /// | |
| /// // Using namespace-level convenience functions | |
| /// Log::Info("Processing user {}", userId); | |
| /// Log::Warn("Connection timeout after {} seconds", timeout); | |
| /// Log::Error("Database error: {}", errorMsg); | |
| /// | |
| /// // Configuration | |
| /// Log::SetLogFilename("myapp.log"); // Set custom log file | |
| /// Log::EnableConsoleLogging(); // Enable console output | |
| /// Log::SetLogLevel(LogLevel::Warn); // Only log warnings and errors | |
| /// </code> | |
| /// </summary> | |
| #define WIN32_LEAN_AND_MEAN | |
| #include <windows.h> | |
| #include <iostream> | |
| #include <fstream> | |
| #include <format> | |
| #include <chrono> | |
| #include <mutex> | |
| #include <string> | |
| #include <string_view> | |
| namespace Log | |
| { | |
| /// <summary> | |
| /// Enumeration representing the severity levels of log messages. | |
| /// </summary> | |
| enum class LogLevel : UINT | |
| { | |
| Info, ///< Informational messages | |
| Warn, ///< Warning messages | |
| Error, ///< Error messages | |
| Debug ///< Debug messages | |
| }; | |
| /// <summary> | |
| /// Thread-safe singleton logger class that writes log messages to file and optionally to console. | |
| /// Supports formatted output using C++20 std::format. | |
| /// </summary> | |
| class Logger | |
| { | |
| public: | |
| /// <summary> | |
| /// Gets the singleton instance of the Logger. | |
| /// </summary> | |
| /// <returns>Reference to the Logger instance.</returns> | |
| static Logger& getInstance() | |
| { | |
| static Logger instance; | |
| return instance; | |
| } | |
| /// <summary> | |
| /// Disables all logging operations globally. | |
| /// </summary> | |
| static void disableLogging() | |
| { | |
| Logger::enabled = false; | |
| } | |
| /// <summary> | |
| /// Sets the filename for the log file before the Logger instance is created. | |
| /// Must be called before first getInstance() call to take effect. | |
| /// </summary> | |
| /// <param name="filename">The name of the log file.</param> | |
| static void setLogFilename(const std::string& filename) | |
| { | |
| Logger::logFilename = filename; | |
| } | |
| /// <summary> | |
| /// Sets the minimum log level for messages to be logged. | |
| /// Messages with lower priority will be filtered out. | |
| /// </summary> | |
| /// <param name="level">The minimum log level.</param> | |
| void setLogLevel(const LogLevel level) | |
| { | |
| Logger::getInstance().logLevel = level; | |
| } | |
| /// <summary> | |
| /// Enables logging output to the console in addition to the log file. | |
| /// </summary> | |
| void enableConsoleLogging(void) | |
| { | |
| logToConsole = true; | |
| } | |
| /// <summary> | |
| /// Allocates an external console window for console output. | |
| /// Useful for Windows GUI applications that don't have a console. | |
| /// </summary> | |
| /// <returns>True if console allocation succeeded, false otherwise.</returns> | |
| bool enableExtenalConsole(void) | |
| { | |
| if (AllocConsole()) | |
| { | |
| logToConsole = true; | |
| freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Delete copy and move constructors and assignment operators | |
| Logger(const Logger&) = delete; | |
| Logger& operator=(const Logger&) = delete; | |
| Logger(Logger&&) = delete; | |
| Logger& operator=(Logger&&) = delete; | |
| /// <summary> | |
| /// Logs a message with the specified log level. | |
| /// </summary> | |
| /// <param name="level">The severity level of the message.</param> | |
| /// <param name="message">The message to log.</param> | |
| void log(LogLevel level, std::string_view message) | |
| { | |
| if (!Logger::enabled || level > logLevel) | |
| { | |
| return; | |
| } | |
| std::lock_guard<std::mutex> lock(m_mutex); | |
| auto now = std::chrono::system_clock::now(); | |
| auto timestamp = std::format("{:%Y-%m-%d %H:%M:%S}", now); | |
| std::string levelStr = getLevelString(level); | |
| std::string logEntry = std::format("[{}] [{}] {}\n", | |
| timestamp, | |
| levelStr, | |
| message); | |
| if (m_logFile.is_open()) | |
| { | |
| m_logFile << logEntry; | |
| m_logFile.flush(); | |
| } | |
| if (logToConsole) | |
| { | |
| std::cout << logEntry; | |
| } | |
| } | |
| /// <summary> | |
| /// Logs a formatted message with the specified log level. | |
| /// Uses C++20 std::format for type-safe formatting. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="level">The severity level of the message.</param> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| void log(LogLevel level, std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| std::string formatted = std::format(fmt, std::forward<Args>(args)...); | |
| log(level, formatted); | |
| } | |
| /// <summary> | |
| /// Logs a formatted informational message. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| void info(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| log(LogLevel::Info, fmt, std::forward<Args>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs a warning message. | |
| /// </summary> | |
| /// <param name="message">The warning message to log.</param> | |
| void warn(std::string_view message) | |
| { | |
| log(LogLevel::Warn, message); | |
| } | |
| /// <summary> | |
| /// Logs a formatted warning message. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| void warn(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| log(LogLevel::Warn, fmt, std::forward<Args>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs an error message. | |
| /// </summary> | |
| /// <param name="message">The error message to log.</param> | |
| void error(std::string_view message) | |
| { | |
| log(LogLevel::Error, message); | |
| } | |
| /// <summary> | |
| /// Logs a formatted debug message. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| void debug(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| log(LogLevel::Debug, fmt, std::forward<Args>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs an debug message. | |
| /// </summary> | |
| /// <param name="message">The error message to log.</param> | |
| void debug(std::string_view message) | |
| { | |
| log(LogLevel::Debug, message); | |
| } | |
| /// <summary> | |
| /// Logs a formatted error message. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| void error(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| log(LogLevel::Error, fmt, std::forward<Args>(args)...); | |
| } | |
| /// <summary> | |
| /// Destructor. Closes the log file if open. | |
| /// </summary> | |
| ~Logger() | |
| { | |
| if (m_logFile.is_open()) | |
| { | |
| m_logFile.close(); | |
| } | |
| } | |
| private: | |
| /// <summary> | |
| /// Private constructor. Initializes the logger and opens the log file. | |
| /// If no filename is set, generates a timestamped filename. | |
| /// </summary> | |
| Logger() : logToConsole(false), logLevel(LogLevel::Info) | |
| { | |
| if (!Logger::enabled) | |
| { | |
| return; | |
| } | |
| std::string filename = Logger::logFilename; | |
| if (filename.empty()) | |
| { | |
| auto now = std::chrono::system_clock::now(); | |
| filename = std::format("log_{:%Y%m%d_%H%M%S}.txt", now); | |
| } | |
| m_logFile.open(filename, std::ios::out | std::ios::app); | |
| if (!m_logFile.is_open()) | |
| { | |
| std::cerr << "Error! Unable to open log file\n"; | |
| } | |
| } | |
| /// <summary> | |
| /// Converts a LogLevel enum value to its string representation. | |
| /// </summary> | |
| /// <param name="level">The log level to convert.</param> | |
| /// <returns>String representation of the log level.</returns> | |
| std::string getLevelString(LogLevel level) const | |
| { | |
| switch (level) | |
| { | |
| case LogLevel::Info: return "INFO "; | |
| case LogLevel::Warn: return "WARN "; | |
| case LogLevel::Error: return "ERROR"; | |
| case LogLevel::Debug: return "DEBUG"; | |
| default: return "UNKNOWN"; | |
| } | |
| } | |
| std::ofstream m_logFile; ///< Output file stream for the log file | |
| std::mutex m_mutex; ///< Mutex for thread-safe logging | |
| bool logToConsole; ///< Flag indicating whether to log to console | |
| LogLevel logLevel; ///< Minimum log level for messages to be logged | |
| inline static bool enabled = true; ///< Global flag to enable/disable logging | |
| inline static std::string logFilename = ""; ///< Global log filename | |
| }; | |
| // Inline wrapper functions for convenient logging without needing to access the Logger instance directly | |
| /// <summary> | |
| /// Logs an informational message using the global Logger instance. | |
| /// </summary> | |
| /// <param name="message">The message to log.</param> | |
| static inline void Info(std::string_view message) | |
| { | |
| Logger::getInstance().log(LogLevel::Info, message); | |
| } | |
| /// <summary> | |
| /// Logs a warning message using the global Logger instance. | |
| /// </summary> | |
| /// <param name="message">The warning message to log.</param> | |
| static inline void Warn(std::string_view message) | |
| { | |
| Logger::getInstance().log(LogLevel::Warn, message); | |
| } | |
| /// <summary> | |
| /// Logs an error message using the global Logger instance. | |
| /// </summary> | |
| /// <param name="message">The error message to log.</param> | |
| static inline void Error(std::string_view message) | |
| { | |
| Logger::getInstance().log(LogLevel::Error, message); | |
| } | |
| /// <summary> | |
| /// Logs an debug message using the global Logger instance. | |
| /// </summary> | |
| /// <param name="message">The error message to log.</param> | |
| static inline void Error(std::string_view message) | |
| { | |
| Logger::getInstance().log(LogLevel::Debug, message); | |
| } | |
| /// <summary> | |
| /// Logs a formatted informational message using the global Logger instance. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| static inline void Info(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| Logger::getInstance().log(LogLevel::Info, fmt, std::forward<decltype(args)>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs a formatted warning message using the global Logger instance. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| static inline void Warn(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| Logger::getInstance().log(LogLevel::Warn, fmt, std::forward<decltype(args)>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs a formatted error message using the global Logger instance. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| static inline void Error(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| Logger::getInstance().log(LogLevel::Error, fmt, std::forward<decltype(args)>(args)...); | |
| } | |
| /// <summary> | |
| /// Logs a formatted debug message using the global Logger instance. | |
| /// </summary> | |
| /// <typeparam name="...Args">Types of the formatting arguments.</typeparam> | |
| /// <param name="fmt">Format string compatible with std::format.</param> | |
| /// <param name="...args">Arguments to be formatted.</param> | |
| template<typename... Args> | |
| static inline void Debug(std::format_string<Args...> fmt, Args&&... args) | |
| { | |
| Logger::getInstance().log(LogLevel::Debug, fmt, std::forward<decltype(args)>(args)...); | |
| } | |
| /// <summary> | |
| /// Enables console output for logging in addition to file output. | |
| /// </summary> | |
| static inline void EnableConsoleLogging() | |
| { | |
| Logger::getInstance().enableConsoleLogging(); | |
| } | |
| /// <summary> | |
| /// Allocates an external console window for console output. | |
| /// Useful for Windows GUI applications. | |
| /// </summary> | |
| /// <returns>True if console allocation succeeded, false otherwise.</returns> | |
| static inline bool EnableExtenalConsole() | |
| { | |
| return Logger::getInstance().enableExtenalConsole(); | |
| } | |
| /// <summary> | |
| /// Disables all logging operations globally. | |
| /// </summary> | |
| static inline void DisableLogging() | |
| { | |
| Logger::disableLogging(); | |
| } | |
| /// <summary> | |
| /// Sets the filename for the log file. | |
| /// Must be called before the Logger instance is created. | |
| /// </summary> | |
| /// <param name="filename">The name of the log file.</param> | |
| static inline void SetLogFilename(const std::string& filename) | |
| { | |
| Logger::setLogFilename(filename); | |
| } | |
| /// <summary> | |
| /// Sets the minimum log level for messages to be logged. | |
| /// </summary> | |
| /// <param name="level">The minimum log level.</param> | |
| static inline void SetLogLevel(const LogLevel level) | |
| { | |
| Logger::getInstance().setLogLevel(level); | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment