Сборник кусков кода

Различные примеры кода.

[C++] Простая библиотека логирования

Abyx Abyx
Библиотека состоит из пары классов:
класс BasicLog — абстрактный класс, обеспечивает необходимую инфраструктуру для логирования;
класс StreamLog — конкретный класс логирования, пишет текст в переданный ему поток (std::ostream). Использует мьютекс для синхронизации доступа к этому потоку.

Код библиотеки:
(Один файл Log.hpp)
#pragma once
/* Simple Log library
 */

#include <sstream>
#include <mutex>

namespace lib
{
    class BasicLog
    {
    public:
        enum Level { Debug, Info, Warning, Error, Fatal };

        BasicLog(Level level = Info) : m_level(level) {}

        virtual ~BasicLog() {}

        Level getLevel() const { return m_level; }
        void setLevel(Level level) { m_level = level; }
        bool matchLevel(Level level) const { return level >= m_level; }

        struct Stream
        {
            BasicLog& log;
            std::stringstream stream;

            explicit Stream(BasicLog& log) : log(log)
            {
                log.beginLog(stream);
            }

            ~Stream()
            {
                log.endLog(stream);
            }
        };

    protected:
        virtual void beginLog(std::stringstream& tempStream) { (void)tempStream; }
        virtual void endLog(std::stringstream& tempStream) = 0;

    private:
        BasicLog(const BasicLog&); // = delete;
        void operator=(const BasicLog&); // = delete;

        Level m_level;
    };

    class StreamLog : public BasicLog
    {
    public:
        StreamLog(std::ostream& stream, Level level = Info) : BasicLog(level), m_stream(stream) {}

    private:
        StreamLog(const StreamLog&); // = delete;
        void operator=(const StreamLog&); // = delete;

        virtual void endLog(std::stringstream& tempStream) override
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            m_stream << tempStream.rdbuf() << std::endl;
        }

        std::mutex m_mutex;
        std::ostream& m_stream;
    };

#define LIB_LOG_HELPER_(log, level) if (log.matchLevel(level)) lib::BasicLog::Stream(log).stream
}


Пример использования:
#include "Log.hpp"

#include <iostream>

#define MY_LOG_DEBUG(log) LIB_LOG_HELPER_(log, log.Debug) << "DEBUG: "
#define MY_LOG_INFO(log) LIB_LOG_HELPER_(log, log.Info) << "INFO: "
#define MY_LOG_WARN(log) LIB_LOG_HELPER_(log, log.Warning) << "WARNING: "
#define MY_LOG_ERROR(log) LIB_LOG_HELPER_(log, log.Error) << "ERROR: "
#define MY_LOG_FATAL(log) LIB_LOG_HELPER_(log, log.Fatal) << "FATAL: "

int main()
{
    lib::StreamLog log(std::cout);
    MY_LOG_INFO(log) << "some" << " text";
}


Пример своего класса логирования:
#include <Windows.h>

struct DebugLog : lib::BasicLog
{
    virtual void endLog(std::stringstream& tempStream) override
    {
        tempStream << '\n';
        auto&& str = tempStream.str();
        ::OutputDebugStringA(str.c_str()); // в отличие от StreamLog, тут мьютекс не нужен
    }
};

...
    DebugLog dbgLog;
    MY_LOG_INFO(dbgLog) << "text";


Известные проблемы:
Метод endLog может бросить исключение, например bad_alloc.
По этому код между "MY_LOG_*()" и ";" не должен бросать исключения. Если при записи во временный поток оттуда будет брошено исключение bad_alloc, то скорее всего в методе endLog тоже возникнет bad_alloc, и программа завершится.
Впрочем, в любом случае, программы обычно умирают от неожиданного bad_alloc, так что это незначительный недостаток библиотеки.

О производительности:
Для повышения производительности, было бы лучше делать асинхронную запись в лог.
Вместо m_stream << tempStream.rdbuf() << endl; в методе endLog должно быть что-то вроде
m_logQueue.emplace_back(std::move(tempStream)); m_condVar.notify_all();, а сама запись в поток лога должна быть в отдельном фоновом потоке.