4
\$\begingroup\$

So I had previously posted this question and asked for some feedback regarding my (now) header-only logging system and figured it would be a good time to go back to the changes I made it and see if it can be further improved before starting a larger project.

#pragma once

/*
Header only logging system.
Should be thread-safe and type-safe, as well as easier to use.
Hopefully no major errors to deal with.
*/

// Standard Headers.
#include <ostream>
#include <variant>
#include <memory>
#include <utility>
#include <mutex>
#include <array>
#include <string_view>

// Everything will take place in this namespace, so we can use the Logger:: prefix for functions.
namespace Logger {
    // Various logging severity levels.
    enum class Level {
        Info, Debug, Warning, Error, Fatal
    };

    class Log {
    public:
        // Takes standard streams cout, cerr, etc.
        explicit Log(std::ostream& p_stream) : m_log(&p_stream) {}

        // Create logger using std::make_unique<std::ofstream>(...) so ownership is passed.
        explicit Log(std::unique_ptr<std::ostream> p_stream) : m_log(std::move(p_stream)) {}

        template <typename T>
        inline void info(T&& p_message);

        template <typename T>
        inline void debug(T&& p_message);

        template <typename T>
        inline void warning(T&& p_message);

        template <typename T>
        inline void error(T&& p_message);

        template <typename T>
        inline void fatal(T&& p_message);

    private:
        template <typename T>
        void log(T&& p_msg) const {
            auto const t_lock = std::lock_guard(*m_lock);
            std::visit([&](auto&& p_ptr) {
                (*p_ptr) << p_msg;
            }, m_log);
        };

        std::ostream& stream() const {
            return std::visit([](auto&& ptr) -> std::ostream& {
                return *ptr;
            }, m_log);
        }

        template <typename T>
        inline void add(Logger::Level p_level, T&& p_message);

        std::variant<std::unique_ptr<std::ostream>, std::ostream*> m_log;
        std::unique_ptr<std::mutex> m_lock = std::make_unique<std::mutex>();
        std::array<std::string_view, 5> m_levels = { "Info", "Debug", "Warning", "Error", "Fatal" };
    };

    template <typename T>
    void Log::add(Level p_level, T&& p_message) {
        auto const f_lock = std::lock_guard(*m_lock);
        stream() << m_levels[static_cast<size_t>(p_level)] << ": " << p_message << '\n';
    }

    template <typename T>
    inline void Log::info(T&& p_message) {
        add(Level::Info, p_message);
    }

    template <typename T>
    inline void Log::debug(T&& p_message) {
        add(Level::Debug, p_message);
    }

    template <typename T>
    inline void Log::warning(T&& p_message) {
        add(Level::Warning, p_message);
    }

    template <typename T>
    inline void Log::error(T&& p_message) {
        add(Level::Error, p_message);
    }

    template <typename T>
    inline void Log::fatal(T&& p_message) {
        add(Level::Fatal, p_message);
    }
}

Is this thread-safe? Is it type-safe? How can I improve on it? What have I done badly? What have I done correctly so I know to keep that for the future?

Thank you!

\$\endgroup\$
  • \$\begingroup\$ Are the severity levels configurable? Could you configure a minimum level? Is Debug allowed in production environment? \$\endgroup\$ – dfhwze Jul 17 at 18:52
  • \$\begingroup\$ @dfhwze No, no, and I don't understand the last point. Can you clarify a bit I would add that in and set up a minimum level? \$\endgroup\$ – Rietty Jul 18 at 15:15
  • \$\begingroup\$ Logging at Debug level could indicate that you only want these logs in test environments, not in production. A minimum level would indicate that all calls to the logger below this level would be silently ignored. This way, you can put a very fine trace log level when introspecting some issues, and reset the log level afterwards. \$\endgroup\$ – dfhwze Jul 18 at 15:36
  • \$\begingroup\$ How would I specify those switches during build? I.e. How would the code know it's being built for a particular environment? \$\endgroup\$ – Rietty Jul 18 at 16:21
  • \$\begingroup\$ I would expect compiler flags or configuration files. \$\endgroup\$ – dfhwze Jul 18 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.