Введение
Уважаемыее, у меня родилась публикация — вопрос. С удовольствием выслушаю вашу критику в комментариях.
В проекте пришлось набросать небольшую обертку над существующей системой логирования и помимо ее основного предназначения захотелось ее использовать в стиле работы с потоком вывода.
Опуская не нужные детали обертка выглядит так:
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(), принимающий в качестве аргумента желаемый уровень для строки, которая будет выведена.
Мне больше нравится второе решение, так как не нужно дописывать после каждой строки некоторое магическое слово. А при добавлении уровней логирования, во временном объекте можно будет хранить эту информацию для текущей строки.
Спасибо, что дочитали, надеюсь увидеть ваше мнение в комментариях.
Автор: Hokum