What is it?
It's a fairly simple logger which utilises a thread.
How it works
Note: this a pretty terrible explanation, perhaps it would be easier to look at the code.
The logger class is a singleton, which contains the function log(). Upon logs()'s first use a static logger instance is created. The function then returns a logstream object (constructed with a reference to the logger), which is a derivative of std::ostringstream. This object is used to format the message. Upon its destruction it sends the formatted std::string back to the logger using the push() function, which locks a mutex and then appends the std::string to a private std::queue belonging to the logger.
When the logger is constructed it creates a thread running the print_routine() function, which is a loop that locks a mutex and prints all the contents of the std::queue, and then sleeps for a set interval. Upon destruction it tells the routine to finish by setting the bool print to false and joins the thread.
Code
log_enum.h
#ifndef ANDROMEDA_LOG_ENUM_H #define ANDROMEDA_LOG_ENUM_H namespace andromeda { enum class log_level { info, warning, severe, fatal }; } #endif
logger.h
#ifndef ANDROMEDA_LOGGER_H #define ANDROMEDA_LOGGER_H #include <sstream> #include <mutex> #include <queue> #include <chrono> #include <thread> #include "log_enum.h" namespace andromeda { class logger; } #include "logstream.h" namespace andromeda { class logger { std::queue<std::string> m_q; std::mutex m_q_mu; std::mutex m_stdout_mu; std::mutex m_stderr_mu; std::thread m_print_thread; bool m_print = true; static void print_routine(logger *instance, std::chrono::duration<double, std::milli> interval); logger(); ~logger(); public: logger(logger const&) = delete; void operator=(logger const&) = delete; static logstream log(log_level level = log_level::info) { static logger m_handler; return logstream(m_handler, level); } void push(std::string fmt_msg); }; } #endif
logger.cpp
#include "logger.h" #include <iostream> namespace andromeda { logger::logger() { m_print_thread = std::thread(print_routine, this, std::chrono::milliseconds(16)); } logger::~logger() { m_print = false; m_print_thread.join(); } void logger::push(std::string fmt_msg) { std::lock_guard<std::mutex> lock(m_q_mu); m_q.push(fmt_msg); } void logger::print_routine(logger *instance, std::chrono::duration<double, std::milli> interval) { while(instance->m_print || !instance->m_q.empty()) { auto t1 = std::chrono::steady_clock::now(); { std::lock_guard<std::mutex> lock(instance->m_q_mu); while(!instance->m_q.empty()) { std::cout << instance->m_q.front() << std::endl; instance->m_q.pop(); } } auto t2 = std::chrono::steady_clock::now(); std::chrono::duration<double, std::milli> time_took = t2 - t1; //sleep if(time_took < interval && instance->m_print) { std::this_thread::sleep_for(interval - time_took); } } } }
logstream.h
#ifndef ANDROMEDA_LOGSTREAM_H #define ANDROMEDA_LOGSTREAM_H #include <sstream> #include "log_enum.h" namespace andromeda { class logger; class logstream : public std::ostringstream { logger& m_logger; log_level m_level; std::string get_level_string(); std::string get_time_string(); public: logstream(logger& log, log_level); ~logstream(); }; } #endif
logstream.cpp
#include "logstream.h" #include <ctime> #include "logger.h" namespace andromeda { logstream::logstream(logger& log, log_level level) : m_logger(log), m_level(level) {} logstream::~logstream() { //note: not using time yet because it adds 0.015 ms //m_logger.push(get_time_string() + get_level_string() + str()); m_logger.push(get_level_string() + str()); } std::string logstream::get_level_string() { std::string temp; switch(m_level) { case log_level::info: temp = "[INFO]"; break; case log_level::warning: temp = "[WARNING]"; break; case log_level::severe: temp = "[SEVERE]"; break; case log_level::fatal: temp = "[FATAL]"; break; } return temp; //copy ellision should be guaranteed with a C++17 compiler } std::string logstream::get_time_string() { std::time_t t = std::time(nullptr); #ifdef _WIN32 std::tm time; localtime_s(&time, &t); #else std::tm time = *std::localtime(&t); #endif char t_str[20]; std::strftime(t_str, sizeof(t_str), "%T", &time); return ("[" + std::string(t_str) + "]"); } }
main.cpp
#include "logger/logger.h" #include <iostream> int main(int argc, char **argv) { { using namespace andromeda; auto t1 = std::chrono::steady_clock::now(); logger::log() << "Hello World"; auto t2 = std::chrono::steady_clock::now(); /* auto t3 = std::chrono::steady_clock::now(); std::cout << "Hello World" << std::endl; auto t4 = std::chrono::steady_clock::now(); */ std::chrono::duration<double, std::milli> d1 = t2 - t1; //std::chrono::duration<double, std::milli> d2 = t4 - t3; logger::log() << "logger took " << d1.count() << "ms"; //std::cout << "cout took " << d2.count() << "ms" << std::endl; //This line is here to make test whether everything is printed before program exit logger::log(log_level::fatal) << "end of program test: " << 33; } return 0; }
Benchmark
I ran a benchmark of this logger vs std::cout without using the time.
run 1: logger = 0.02925ms and cout = 0.007725ms -> log/cout = 3.77 run 2: logger = 0.028469ms and cout = 0.008442ms -> log/cout = 3.37 run 3: logger = 0.027484ms and cout = 0.016155ms -> log/cout = 1.7 run 4: logger = 0.028764ms and cout = 0.007859ms -> log/cout = 3.66 run 5: logger = 0.027457ms and cout = 0.008173ms -> log.cout = 3.36
On average the logger was 3.172 times slower than std::cout. Is tha bad?
What I'm aiming for
I'm aiming for it to be reasonably fast, thread-safe and cross-platform.
What I think could be improved
I think the get_time_string() could be improved. At the moment it worsens performance by about 50%. Another things is the detail. I think it might be a good idea to perhaps include the source and thread id. One last minor thing is the log_level. I don't have much experience so I don't know how many different levels are required for bigger projects.
Any feedback is appreciated.
logger
vscout
, and then exiting the program, then you're creating the static logger (astd::ostringstream
) every time...\$\endgroup\$std::ostringstream
above, my bad).\$\endgroup\$