Уважаемые хабровчане, у меня родилась публикация — вопрос. С удовольствием выслушаю вашу критику в комментариях.
В проекте пришлось набросать небольшую обертку над существующей системой логирования и помимо ее основного предназначения захотелось ее использовать в стиле работы с потоком вывода.
Опуская ненужные детали, обертка выглядит так:
class LoggerWrap: public RequiredInterface { public: void write(const std::string& str) const; //запись в существующую систему логирования /* ... прочие методы и поля класса реализующие RequiredInterface ... */ };
А хочется еще использовать как-нибудь так:
LoggerWrap log; log << "значение 1: " << std::setw(10) << someValue << "; значение 2:" << std::setw(15) << anotherValue;
В процессе размышлений мне пришло в голову несколько решений которые я и хочу обсудить.
Вариант 1
Перегрузить operator<<, данные полученные им направлять в локальный для потока ostringstream. В конце писать в лог специальный объект — признак конца строки.
Решение может выглядеть примерно так:
class LoggerWrap { public: void write(const std::string& str) const { //просто для примера std::cout << "[Log]: " << str << std::endl; } /* ... прочие методы и поля класса ... */ struct Flush {}; //признака конца строки template<typename T> LoggerWrap& operator<< (const T& data) { buf << data; return *this; } LoggerWrap& operator<< (const Flush&) { write(buf.str()); buf.str(""); buf.flags(defFmtFlags); return *this; } private: thread_local static std::ostringstream buf; const thread_local static std::ios::fmtflags defFmtFlags; }; thread_local std::ostringstream LoggerWrap::buf; const thread_local std::ios::fmtflags LoggerWrap::defFmtFlags(buf.flags());
Использование:
LoggerWrap logger; logger << "#" << 1 << ": " << 1.2 << ", text again" << LoggerWrap::Flush(); logger << "#" << 2 << ": " << std::scientific << 2.3 << ", text again" << LoggerWrap::Flush(); logger << "string #" << 3 << ": " << 10.5 << LoggerWrap::Flush();
в консоли будет напечатано:
[Log]: #1: 1.2, text again [Log]: #2: 2.300000e+00, text again [Log]: #3: 10.5
Для меня, недостатком этого варианта является необходимость писать LoggerWrap::Flush(). Его можно забыть написать и потом долго пытаться понять, а что за чертовщина происходит в логе.
Вариант 2
Как определить, что строка для логирования завершена, без явного указания на это? Я решил опереться на время жизни временного объекта. Когда функция возвращает объект, то он живет пока на него есть ссылка. Таким образом можно создать временный объект со своим operator<<, возвращающим ссылку на этот объект, а его деструктор будет вызывать метод LoggerWrap::write.
Получается следующее:
class LoggerWrap { public: void write(const std::string& str) const { //просто для примера std::cout << "[Log]: " << str << std::endl; } /* ... прочие методы и поля класса ... */ class TmpLog { friend class LoggerWrap; public: ~TmpLog() { if (flush) { logger.write(buf.str()); buf.str(""); } } template<typename T> TmpLog& operator<< (const T& data) { buf << data; return *this; } TmpLog(const TmpLog&) = delete; private: TmpLog(const LoggerWrap& logger, std::ostringstream& buf) : logger(logger), buf(buf) { } TmpLog(TmpLog&& that): logger(that.logger), buf(that.buf), flush(that.flush) { that.flush = false; } const LoggerWrap& logger; std::ostringstream& buf; bool flush = true; }; template<typename T> TmpLog operator<< (const T& data) { buf.flags(defFmtFlags); TmpLog tmlLog(*this, buf); return std::move(tmlLog << data); } private: thread_local static std::ostringstream buf; const thread_local static std::ios::fmtflags defFmtFlags; }; thread_local std::ostringstream LoggerWrap::buf; const thread_local std::ios::fmtflags LoggerWrap::defFmtFlags(buf.flags());
и использование:
LoggerWrap logger; logger << "#" << 1 << ": " << 1.2 << ", text again"; logger << "#" << 2 << ": " << std::scientific << 2.3 << ", text again"; logger << "#" << 3 << ": " << 10.5;
вывод в консоли будет аналогичен первому решению.
Итоги
Здесь не задаются уровни логирования (Warning, Error, Info и т.п.) так как наша система логирования их не различает, но добавить это в обертку не сложно. Например определить LoggerWrap::operator(), принимающий в качестве аргумента желаемый уровень для строки, которая будет выведена.
Мне больше нравится второе решение, так как не нужно дописывать после каждой строки некоторое магическое слово. А при добавлении уровней логирования, во временном объекте можно будет хранить эту информацию для текущей строки.
Спасибо, что дочитали, надеюсь увидеть ваше мнение в комментариях.
