Skip to content

Instantly share code, notes, and snippets.

@Scavanger
Created February 26, 2026 13:44
Show Gist options
  • Select an option

  • Save Scavanger/f28de491d3e4d6bf8cf4c0866ceaf576 to your computer and use it in GitHub Desktop.

Select an option

Save Scavanger/f28de491d3e4d6bf8cf4c0866ceaf576 to your computer and use it in GitHub Desktop.
Logger - A thread-safe singleton logging library for C++20
#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