Действительно простой логгер для C++

от автора

Однажды, как и автору похожего топика, захотелось приделать простенькое журналирование к многопоточной консольной утилите. Причём подумалось, что быстрее будет запилить свой велосипед на пару экранов кода, чем прикручивать уже изобретённый. Правда, потом эти 2 экрана немного расползлись, так как захотелось еще раскрасить вывод в зависимости от «уровня» записи и для win32-версии ещё и перекодировать текст в кодовую страницу консоли (в русской винде это CP866), чтобы русские буквы читались с экрана без перенаправления в файл.

Что получилось:

  • для журналирования используются стандартные потоки std::clog и std::cerr;
  • не требует изменения кода, достаточно прилинковать объектник;
  • разные треды могут вести запись в журнал одновременно, без «сливания» записей.

Недостатки:

  • отсутствуют гибкость и настраиваемость;
  • невозможно надёжное журналирование на этапе инициализации глобальных статических переменных (то есть до входа в main);
  • недостаточная «платформонезависимость», поддержка многопоточности реализована с помощью библиотеки Boost.Thread.

Что касается недостатков, то для их преодоления уже существует множество разнообразных библиотек журналирования: два варианта Boost.Log — один, второй, Panthios, log4cxx, glog, и т.д. По поводу второго пункта — речь идёт о неопредённом порядке инициализации статических переменных в разных единицах трансляции. Обычно это ограничение обходится синглтоном, однако в нашем случае просто не будем ничего писать в журнал из конструкторов статических переменных. Впрочем, даже если и писать, ничего страшного не произойдёт — просто будет задействован стандартный поток std::cerr.

Теперь, вкратце о реализации.

Чтобы можно было заменить буфер, в который пишут потоки std::clog и std::cerr, наш логгер должен наследоваться от std::streambuf и как минимум переопределять виртуальные методы overflow и xsputn. Буферизация будет построчная, с опустошением буфера при записи в него символа конца строки ‘\n’. С помощью boost::thread_specific_ptr для каждого треда будет организован свой строковый буфер.

using boost::thread_specific_ptr;  class logger : public std::streambuf { public:     typedef char                                char_type;     typedef std::char_traits<char_type>         traits_type;     typedef traits_type::int_type               int_type;      struct line_buffer;      explicit logger (sys::raw_handle console, bool prepend_time = true);     ~logger ();  protected:     virtual int_type overflow (int_type c);     virtual std::streamsize xsputn (const char_type* buf, std::streamsize size);  private:     line_buffer* buffer ();     void write_line (const char* data, size_t size);     void write_line (const std::string& line) { write_line (line.data(), line.size()); }      sys::raw_handle     m_con;     thread_specific_ptr<line_buffer>                         m_buffer;     bool                m_console_output;     bool                m_prepend_time; }; 

Чтобы заменить буфер у стандартных потоков, объявим статический инициализатор:

class logger_init { public:     logger_init ();     ~logger_init ();  private:     logger*     	m_clog;     logger*	        m_cerr;     std::streambuf*     m_clog_native;     std::streambuf*     m_cerr_native;     sys::file_handle    m_file; } std_stream_logger;  logger_init::logger_init ()     : m_clog (0)     , m_cerr (0)     , m_clog_native (0)     , m_cerr_native (0) {     sys::raw_handle con = sys::io::err();     if (!sys::handle::valid (con) || !sys::file_handle::valid (con))         return;     m_clog = new logger (con);     m_cerr = new logger (con);     m_clog_native = std::clog.rdbuf (m_clog);     m_cerr_native = std::cerr.rdbuf (m_cerr); }  logger_init::~logger_init () {     if (m_clog)     {         std::cerr.rdbuf (m_cerr_native);         std::clog.rdbuf (m_clog_native);         delete m_cerr;         delete m_clog;     } } 

В пространстве имён sys определены обёртки над низкоуровневыми системными вызовами.

«Магия» thread-local storage реализована в методе logger::buffer(), который возвращает указатель на буфер, локальный для каждого потока.

logger::line_buffer* logger:: buffer () {     line_buffer* bufptr = m_buffer.get();     if (!bufptr)         m_buffer.reset (bufptr = new line_buffer (this));     return bufptr; } 

Метод overflow добавляет символ в буфер и опустошает его, если был записан символ конца строки.

logger::int_type logger:: overflow (int_type c) {     // требование стандарта -- при попытке записи eof(), надо вернуть not_eof()     if (traits_type::eq_int_type (c, traits_type::eof()))         return traits_type::not_eof (c);      char_type chr = traits_type::to_char_type (c);     if (traits_type::eq (chr, '\n'))         buffer()->flush();     else         buffer()->append (&chr, 1);     return (c); } 

Метод xsputn ищет в добавляемой последовательности символы конца строки и соответственно обновляет буфер.

std::streamsize logger:: xsputn (const char_type* buf, std::streamsize sz) {     line_buffer* bufptr = buffer();     for (std::streamsize size = sz; size > 0; )     {         const char* nl = traits_type::find (buf, size, '\n');         if (!nl)         {             // символ конца строки не встретился - добавляем строку целиком             bufptr->append (buf, size);             break;         }         if (nl != buf)         {             // добавляем в буфер часть строки до символа '\n'             bufptr->append (buf, nl-buf);         }         bufptr->flush(); // опустошаем буфер         ++nl;         size -= nl - buf;         buf = nl;     }     return sz; } 

Наконец, реализация строкового буфера.

struct logger::line_buffer {     static const size_t s_limit = 1000; // максимальная длина строки      explicit line_buffer (logger* owner) : m_owner (owner) { }      void append (const char* buf, size_t size);     void flush ();  private:     void append_time ();     void append_crlf ();      logger*         m_owner;     std::string     m_text; };  void logger::line_buffer:: append (const char* buf, size_t size) {     if (m_owner->m_prepend_time && m_text.empty())         append_time();     while (size + m_text.size() > s_limit)     {         assert (m_text.size() < s_limit);         size_t chunk = std::min (s_limit - m_text.size(), size);         m_text.append (buf, chunk);         flush();         size -= chunk;         buf += chunk;         if (size && m_owner->m_prepend_time)             append_time();     }     if (size)         m_text.append (buf, size); }  void logger::line_buffer:: flush () {     append_crlf();     m_owner->write_line (m_text);     m_text.clear(); }  inline void logger::line_buffer:: append_crlf () { #ifdef _WIN32     m_text.append ("\r\n", 2); #else     m_text.push_back ('\n'); #endif }  inline void logger:: write_line (const char* data, size_t size) {     sys::write_file (m_con, data, size); } 

Перед каждой строкой добавляется время с миллисекундами и идентификатор треда.

#ifdef _WIN32  void logger::line_buffer:: append_time () {     char cvtbuf[32];     SYSTEMTIME time;     GetLocalTime (&time);     int rc = _snprintf (cvtbuf, sizeof(cvtbuf), "%02d:%02d:%02d.%03d [%04lu] ",                         time.wHour, time.wMinute, time.wSecond, time.wMilliseconds,                         GetCurrentThreadId());     if (rc < 0 || rc > int (sizeof(cvtbuf))) 	rc = sizeof(cvtbuf);     m_text.append (cvtbuf, rc); }  #else  void logger::line_buffer:: append_time () {     char cvtbuf[32];     struct timeval sys_time;     int rc = ::gettimeofday (&sys_time, NULL);     if (rc != -1)     {         struct tm time;         localtime_r (&sys_time.tv_sec, &time);         rc = ::snprintf (cvtbuf, sizeof(cvtbuf), "%02d:%02d:%02d.%03d [%08x] ", time.tm_hour,                          time.tm_min, time.tm_sec, int(sys_time.tv_usec/1000),                          (unsigned) pthread_self());         if (rc > int (sizeof(cvtbuf)))             rc = sizeof(cvtbuf);     }     if (rc != -1)         m_text.append (cvtbuf, rc); }  #endif // _WIN32 

Рабочая версия кода несколько сложнее, поскольку добавлена расцветка для консоли, перенаправление в файл при инициализации и перекодировка в кодовую страницу консоли для win32. Замечу ещё раз, что для низкоуровнего ввода/вывода используются «обертки» над системными вызовами, объявленные в пространстве имён sys. Их разбор уже выходит за рамки этой заметки, просто приведу ссылку для ознакомления.

ссылка на оригинал статьи http://habrahabr.ru/post/174757/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *