Pull to refresh

Варианты operator<< для логгера

Reading time3 min
Views8.2K

Уважаемые хабровчане, у меня родилась публикация — вопрос. С удовольствием выслушаю вашу критику в комментариях.


В проекте пришлось набросать небольшую обертку над существующей системой логирования и помимо ее основного предназначения захотелось ее использовать в стиле работы с потоком вывода.


Опуская ненужные детали, обертка выглядит так:


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(), принимающий в качестве аргумента желаемый уровень для строки, которая будет выведена.


Мне больше нравится второе решение, так как не нужно дописывать после каждой строки некоторое магическое слово. А при добавлении уровней логирования, во временном объекте можно будет хранить эту информацию для текущей строки.


Спасибо, что дочитали, надеюсь увидеть ваше мнение в комментариях.

Only registered users can participate in poll. Log in, please.
А какой вариант вам нравится больше?
5.17% Вариант 1.6
37.07% Вариант 2.43
22.41% Ни один не нравится.26
35.34% Я просто проходил мимо.41
116 users voted. 23 users abstained.
Tags:
Hubs:
+7
Comments58

Articles

Change theme settings