概要
本記事では、C++でスレッドセーフなログモジュールを実装する方法を紹介します。STLのqueueにロックを追加し、複数のスレッドからのログ出力を安全に処理できるようにします。ログの書き込みは専用スレッドが担当し、非同期でファイルに書き込む仕組みとなっています。
スレッドセーフなキューの実装
複数のスレッドから同時にアクセスされるキューを実現するため、std::mutexとstd::condition_variableを使用して排他制御を行います。
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class ThreadSafeQueue {
public:
void enqueue(const T& item) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push(item);
lock.unlock();
condition_.notify_one();
}
T dequeue() {
std::unique_lock<std::mutex> lock(mutex_);
while (queue_.empty()) {
condition_.wait(lock);
}
T item = queue_.front();
queue_.pop();
return item;
}
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable condition_;
};
ログレベルの定義
ログの重要度を区別するために、2つのログレベルを定義します。
enum class LogLevel {
INFORMATION,
CRITICAL
};
ログクラスの実装
シングルトンパターンを使用して、アプリケーション全体で1つのログインスタンスだけが使用されるようにします。
class Logger {
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger() = default;
static Logger& instance();
void set_level(LogLevel level);
void write_log(const std::string& message);
private:
LogLevel current_level_;
ThreadSafeQueue<std::string> log_queue_;
Logger();
};
ログ書き込みスレッドの起動
コンストラクタ内で永続的に動作するスレッドを生成し、キューからログを取得してファイルに書き込みます。
Logger::Logger() {
std::thread writer([this]() {
while (true) {
auto now = std::chrono::system_clock::now();
std::time_t current_time = std::chrono::system_clock::to_time_t(now);
std::tm* local_time = std::localtime(¤t_time);
std::stringstream date_stream, time_stream;
date_stream << std::put_time(local_time, "%Y-%m-%d");
time_stream << std::put_time(local_time, "[%H:%M:%S]");
std::string filename = date_stream.str() + ".log";
FILE* file = fopen(filename.c_str(), "a+");
if (!file) {
continue;
}
std::string log_entry = time_stream.str() + log_queue_.dequeue() + "\n";
fputs(log_entry.c_str(), file);
fclose(file);
}
});
writer.detach();
}
マクロによるログ出力インターフェース
使いやすさのために、マクロを使用してファイル名と行番号を自動挿入するインターフェースを提供します。
#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG(level, message, ...) \
do { \
auto& logger = Logger::instance(); \
logger.set_level(level); \
char buffer[1024] = {0}; \
snprintf(buffer, sizeof(buffer), \
(std::string("[") + #level + "][" + FILENAME + ":" + \
std::to_string(__LINE__) + "] " + message).c_str(), ##__VA_ARGS__); \
logger.write_log(buffer); \
} while (0)
#define INFO_LOG(message, ...) LOG(LogLevel::INFORMATION, message, ##__VA_ARGS__)
#define ERROR_LOG(message, ...) LOG(LogLevel::CRITICAL, message, ##__VA_ARGS__)
使用例
以下はログモジュールの使用例です:
#include "logger.h"
int main() {
while (true) {
std::string input;
std::getline(std::cin, input);
INFO_LOG("%s", input.c_str());
}
}