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