My goal was to have a logger that does the blocking file I/O in a separate thread.
A few notes:
- I made it a singleton rather than having a global "logger" variable, or a bunch of loggers that are passed around. It is used by saying Logger::Log("Hello, this is a log").
- Since the class is a singleton, it feels pointless to put the implementation variables in the class. I just kept the class declaration clean and put the variables in the CPP file. This is unconventional I think, but please let me know your thoughts on this.
- This is slightly stripped down, the main code has some stuff that adds timestamps, etc. but I wanted to focus on the thread synchronization because that is new to me.
- I strongly prefer the C-style "printf" string formatting, so that is why I used it.
Header:
class Logger {
static Logger* instance;
Logger(const char *path);
Logger(const Logger& o);
void operator = (const Logger& o);
void push(const std::string &str);
public:
static void Init(const char* path); //not thread safe, don't be an idiot
static void Log(const char* s, ...); //is thread safe
};
CPP:
Logger * Logger::instance = 0;
mutex q_mutex;
queue <string> log_queue;
condition_variable log_data_ready_cv;
FILE* log_file;
void log_monitor() {
for (;;) {
unique_lock lock(q_mutex);
log_data_ready_cv.wait(lock, [] {return !log_queue.empty(); });
string s = log_queue.front();
log_queue.pop();
lock.unlock();
fputs(s.c_str(), log_file);
fputc('\n', log_file);
fflush(log_file);
}
}
Logger::Logger(const char* path) {
log_file = fopen(path, "a");
}
void Logger::Init(const char* path) {
if (instance) return;
instance = new Logger(path);
thread(&log_monitor).detach();
}
void Logger::Log(const char* s, ...) {
if (!instance) return;
const size_t MaxLen = 2000;
char line[MaxLen];
va_list args;
va_start(args, s);
int len = vsnprintf(line, MaxLen, s, args);
va_end(args);
if (len < 0) return; //encoding error
if (len >= MaxLen) return; //not enough space
instance->push(line);
}
void Logger::push(const string &str, int len) {
q_mutex.lock();
log_queue.push(str);
q_mutex.unlock();
log_data_ready_cv.notify_one();
}
printf
? I can understand not liking streaming style - cause it's awful. But in comparison tofmt::format
?fmt::format
is safer, faster, more general, and much more convenient than C-style printf. \$\endgroup\$