mardi 5 février 2019

Dynamic parameter type dispatch based on function overrides to callbacks with variadic parameters

I'm trying to think of a better way to supply an optional LogData style initial parameter in registered callbacks with my logging class. I would love to be able to override one of 2 different kinds of signatures, either log(Severity, std::string message), or log(Severity, CUSTOMDATATYPE, std::string message)

In my program, I can call the log method with variadic parameters. I want this call to correctly dispatch to my callbacks depending on the first parameter supplied. For example, if I write the following:

log(Severity::Debug, NetworkDataObject{}, "Now let's print 32: ", 32, " there, did it!");
log(Severity::Debug, "Here we just have some message and additional parameters", 'c', 6, " all these values get concatenated by the to_string tuple helper.");

If I define and register the following classes as listeners I'm hoping they work as described.

class CustomNetworkReceiver {
    //this gets called by the first log, but not the second
    void log(Severity severity, NetworkDataObject myObject, std::string message){
    }
    //this gets called by the second log, but not the first
    void log(Severity severity, std::string message){
    }
};

class CoutReceiver {
    //This gets called by anything, so both logs hit it.
    void log(Severity severity, std::string message){
        std::cout << to_string(severity) << ": " << message << std::endl;
    }
};

Here is what I currently have, making use of std::function. This is log.h

#ifndef _MV_LOG_H_
#define _MV_LOG_H_

#include <string>
#include <iostream>
#include <vector>
#include <array>
#include <mutex>
#include <functional>
#include <tuple>
#include "require.hpp"
#include "standard_any.hpp"

namespace MV {
    struct LogData {
        MV::any value;
    };

    struct LogReciever {
        virtual void log(Severity, const LogData &, const std::string &) = 0;
    };

    struct CoutLogReciever : public LogReciever {
        virtual void log(Severity severity, const LogData &, const std::string & message) override {
            std::cout << message << std::endl;
        }
    };

    struct CerrLogReciever : public LogReciever {
        virtual void log(Severity severity, const LogData &, const std::string & message) override {
            std::cerr << message << std::endl;
        }
    };

    struct Logger {
        Logger(Severity defaultLevel = Severity::Debug, bool useDefaultListeners = true) : level(defaultLevel) {
            if(useDefaultListeners) {
                addDefaultListeners();
            }
        }

        void filter(Severity level){
            level = level;
        }

        template <typename... Args>
        inline void log(Severity a_level, Args&&... a_args) {
            if (a_level != Severity::Silence && a_level >= level) {
                std::lock_guard<std::mutex> lock(m);
                auto output = MV::to_string(std::make_tuple(std::forward<Args>(a_args)...));
                auto logIndex = static_cast<size_t>(a_level);
                for(auto&& listener : listeners[logIndex]){
                    listener(a_level, {}, output);
                }
            }
        }

        template <typename... Args>
        inline void log(Severity a_level, const LogData &a_data, Args&&... a_args) {
            if (a_level != Severity::Silence && a_level >= level) {
                std::lock_guard<std::mutex> lock(m);
                auto output = MV::to_string(std::make_tuple(std::forward<Args>(a_args)...));
                auto logIndex = static_cast<size_t>(a_level);
                for(auto&& listener : listeners[logIndex]){
                    listener(a_level, a_data, output);
                }
            }
        }

        template <typename... Args>
        inline void debug(Args&&... a_args) {
            log(Severity::Debug, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void info(Args&&... a_args) {
            log(Severity::Info, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void warning(Args&&... a_args) {
            log(Severity::Warning, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void error(Args&&... a_args) {
            log(Severity::Error, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void debug(const LogData &a_data, Args&&... a_args) {
            log(Severity::Debug, a_data, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void info(const LogData &a_data, Args&&... a_args) {
            log(Severity::Info, a_data, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void warning(const LogData &a_data, Args&&... a_args) {
            log(Severity::Warning, a_data, std::forward<Args>(a_args)...);
        }

        template <typename... Args>
        inline void error(const LogData &a_data, Args&&... a_args) {
            log(Severity::Error, a_data, std::forward<Args>(a_args)...);
        }

        //This override provides an easy to declare non-owning interface wrapper for the std::function interface.
        //Mostly this is to avoid accidental copies and to allow for a descriptively named "log" function to be used.
        //Caller owns the lifespan of the receiver, be aware not to dangle! :)
        void listen(Severity level, LogReciever* receiver) {
            listen(level, [receiver](Severity level, const LogData& data, const std::string &message){receiver->log(level, data, message);});
        }
        void listen(LogReciever* receiver) {
            listen([receiver](Severity level, const LogData& data, const std::string &message){receiver->log(level, data, message);});
        }

        void listen(Severity a_level, std::function<void (Severity, const LogData &, const std::string&)> a_receiver) {
            int levelIndex = static_cast<int>(a_level);
            MV::require<MV::RangeException>(levelIndex >= 0 && levelIndex < listeners.size(), "Failed to listen to logger on Severity Level [", a_level, "]");
            std::lock_guard<std::mutex> lock(m);
            listeners[static_cast<size_t>(a_level)].push_back(a_receiver);
        }
        void listen(std::function<void (Severity, const LogData &, const std::string&)> a_receiver) {
            for(size_t i = 0;i < static_cast<size_t>(Severity::Count);++i){
                listen(static_cast<Severity>(i), a_receiver);
            }
        }

        void clearListeners(){
            std::lock_guard<std::mutex> lock(m);
            for(auto&& listenerList : listeners){
                listenerList.clear();
            }
        }

        void addDefaultListeners(){
            listen(Severity::Debug, &defaultReceiver);
            listen(Severity::Info, &defaultReceiver);
            listen(Severity::Warning, &defaultReceiver);
            listen(Severity::Error, &defaultErrorReceiver);
        }

    private:
        CoutLogReciever defaultReceiver;
        CerrLogReciever defaultErrorReceiver;
        std::array<std::vector<std::function<void (Severity, const LogData &, const std::string&)>>, static_cast<int>(Severity::Count)> listeners;
        Severity level;
        std::mutex m;
    };

    //Global convenience accessors
    extern Logger logger;

    template <typename... Args>
    inline void log(Severity a_level, Args&&... a_args) {
        logger.log(a_level, std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void debug(Args&&... a_args) {
        logger.debug(std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void info(Args&&... a_args) {
        logger.info(std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void warning(Args&&... a_args) {
        logger.warning(std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void error(Args&&... a_args) {
        logger.error(std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void log(Severity a_level, const LogData &a_data, Args&&... a_args) {
        logger.log(a_level, a_data, std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void debug(const LogData &a_data, Args&&... a_args) {
        logger.debug(a_data, std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void info(const LogData &a_data, Args&&... a_args) {
        logger.info(a_data, std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void warning(const LogData &a_data, Args&&... a_args) {
        logger.warning(a_data, std::forward<Args>(a_args)...);
    }

    template <typename... Args>
    inline void error(const LogData &a_data, Args&&... a_args) {
        logger.error(a_data, std::forward<Args>(a_args)...);
    }
}

#endif

And this is require.hpp

#ifndef __MV_REQUIRE_H__
#define __MV_REQUIRE_H__

#undef require

#include <string>
#include <tuple>
#include <sstream>
#include <stdint.h>
#include <iomanip>

namespace MV {
    enum class Severity {Debug, Info, Warning, Error, Silence, Count = static_cast<int>(Silence)};

    inline std::string to_string(Severity severity){
        static std::array<std::string, 5> lookup = {"debug", "info", "warning", "error", "silence"};
        return lookup[static_cast<int>(severity)];
    }

    inline std::string to_string(bool a_type) {
        return a_type ? std::string("1") : std::string("0");
    }

    inline std::string to_string(int a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(unsigned int a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(long a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(unsigned long a_type){
        return std::to_string(a_type);
    }
#ifndef __GNUC__
    inline std::string to_string(int64_t a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(uint64_t a_type){
        return std::to_string(a_type);
    }
#endif
    inline std::string to_string(long double a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(double a_type){
        return std::to_string(a_type);
    }
    inline std::string to_string(float a_type){
        return std::to_string(a_type);
    }
    inline std::string& to_string(std::string &a_string){
        return a_string;
    }
    inline const std::string& to_string(const std::string &a_string){
        return a_string;
    }
    inline const char* const to_string(const char * const a_string){
        return a_string;
    }
    inline char* to_string(char *a_string){
        return a_string;
    }
    inline std::string to_string(long double toCast, unsigned precision) {
        std::ostringstream result;
        result << std::setprecision(precision) << toCast;
        return result.str();
    }
    inline std::string to_string(double toCast, unsigned precision) {
        std::ostringstream result;
        result << std::setprecision(precision) << toCast;
        return result.str();
    }
    inline std::string to_string(float toCast, unsigned precision) {
        std::ostringstream result;
        result << std::setprecision(precision) << toCast;
        return result.str();
    }

    template<typename T>
    std::string to_string(const T &a_type) {
        std::stringstream stream;
        stream << a_type;
        return stream.str();
    }

    template<class Tuple, std::size_t N>
    struct TupleStringHelper {
        static void to_string_combiner(const Tuple& t, std::stringstream &a_stream){
            TupleStringHelper<Tuple, N - 1>::to_string_combiner(t, a_stream);
            a_stream << MV::to_string(std::get<N - 1>(t));
        }
    };

    template<class Tuple>
    struct TupleStringHelper<Tuple, 1> {
        static void to_string_combiner(const Tuple& t, std::stringstream &a_stream){
            a_stream << MV::to_string(std::get<0>(t));
        }
    };

    template<class... Args>
    std::string to_string(const std::tuple<Args...>& t){
        std::stringstream stream;
        TupleStringHelper<decltype(t), sizeof...(Args)>::to_string_combiner(t, stream);
        return stream.str();
    }

    class Exception : public virtual std::runtime_error {
    public:
        explicit Exception(const std::string& a_message): std::runtime_error(a_message){}
        explicit Exception(const char *a_message): std::runtime_error(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("General Exception: ");
            return combinedWhat.c_str();
        }

        void append(const char *a_extra) {
            combinedWhat+="\n[";
            combinedWhat+=a_extra;
            combinedWhat+="]";
        }
        void append(const std::string &a_extra) {
            combinedWhat+="\n[";
            combinedWhat+=a_extra;
            combinedWhat+="]";
        }
    protected:
        void prefixWhat(const char * a_prefix) const{
            if(combinedWhat.empty()){
                combinedWhat = a_prefix;
                combinedWhat += runtime_error::what();
            }
        }

        mutable std::string combinedWhat;
    };

    class RangeException : public Exception {
    public:
        explicit RangeException(const std::string& a_message): std::runtime_error(a_message), Exception(a_message) {}
        explicit RangeException(const char *a_message): std::runtime_error(a_message), Exception(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("Range Exception: ");
            return combinedWhat.c_str();
        }
    };

    class ResourceException : public Exception {
    public:
        explicit ResourceException(const std::string& a_message): std::runtime_error(a_message), Exception(a_message) {}
        explicit ResourceException(const char *a_message): std::runtime_error(a_message), Exception(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("Resource Exception: ");
            return combinedWhat.c_str();
        }
    };

    class DeviceException : public Exception {
    public:
        explicit DeviceException(const std::string& a_message) : std::runtime_error(a_message), Exception(a_message) {}
        explicit DeviceException(const char *a_message) : std::runtime_error(a_message), Exception(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("Device Exception: ");
            return combinedWhat.c_str();
        }
    };

    class PointerException : public Exception {
    public:
        explicit PointerException(const std::string& a_message): std::runtime_error(a_message), Exception(a_message) {}
        explicit PointerException(const char *a_message): std::runtime_error(a_message), Exception(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("Pointer Exception: ");
            return combinedWhat.c_str();
        }
    };

    class LogicException : public Exception {
    public:
        explicit LogicException(const std::string& a_message) : std::runtime_error(a_message), Exception(a_message) {}
        explicit LogicException(const char *a_message) : std::runtime_error(a_message), Exception(a_message) {}

        virtual const char * what() const noexcept override {
            prefixWhat("Logic Exception: ");
            return combinedWhat.c_str();
        }
    };

    template <typename ExceptionType, typename ConditionType, typename... Args>
    inline void require(ConditionType&& a_condition, Args&&... a_args){
        if(!a_condition){
            ExceptionType exception(MV::to_string(std::make_tuple(std::forward<Args>(a_args)...)));
            std::cerr << "ASSERTION FAILED! " << exception.what() << std::endl;
            throw exception;
        }
    }
}

#endif

Aucun commentaire:

Enregistrer un commentaire