- для журналирования используются стандартные потоки 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/
Добавить комментарий