I'm designing a logger library in C , and I've been stuck with my implementation of a formatter class.
My goal is to be able to create a message "formatter", which will take a string (something like "[A] [B]", or "[B]") and decide which policies to attach to my formatter.
Here is a simplified version of my policy implementation:
#include <iostream>
#include <string>
#include <memory>
template <typename PolicyA, typename PolicyB>
class Formatter : private PolicyA, private PolicyB {
public:
void Format(std::string& param_message) const {
PolicyA::A(param_message);
PolicyB::B(param_message);
}
};
class Default {
protected:
virtual void A(std::string& param_message) const = 0;
};
class ADefault : public virtual Default {
protected:
void A(std::string& param_message) const {}
};
class AExplicit : public virtual Default {
protected:
void A(std::string& param_message) const { param_message = "[A] " param_message; }
};
class BDefault : public virtual Default {
protected:
void B(std::string& param_message) const {}
};
class BExplicit : public virtual Default {
protected:
void B(std::string& param_message) const { param_message = "[B] " param_message; }
};
int main() {
std::string message_1 = "message_1";
std::string message_2 = "message_2";
Formatter<ADefault, BDefault> default_message;
Formatter<AExplicit, BExplicit> explicit_message;
default_message.Format(message_1);
explicit_message.Format(message_2);
std::cout << message_1 << std::endl;
std::cout << message_2 << std::endl;
}
The policy works as I intended, but how would I go about creating a sort of Builder function? One that could return a Fromatter from a set of parameters. I already have an implementation of the string interpreter, which returns an enum buffer of formats:
enum class Format : uint16_t {
NONE = 0x000,
A = 0x001,
B = 0x002,
};
Format ConfigFormatter(std::string& param_configuration) {
Format formatter_configuration = Format::NONE;
if (param_configuration.find("[A]") != std::string::npos) {
formatter_configuration = Format::A;
}
if (param_configuration.find("[B]") != std::string::npos) {
formatter_configuration = Format::B;
}
return formatter_configuration;
}
Is it possible to store the created Formatter as a member variable of another class? And should the Builder class be a variadic class with specializations for every type? I've looked for on variadic factories, but could not find anything that could help me.
CodePudding user response:
Maybe this could help:
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
enum class LogLevel { Debug, Info, Warn, Error, Fatal, Off };
template <typename... Fmts>
struct Logger {
template <LogLevel Level>
inline void Log(std::string_view msg) {
if constexpr (Level == LogLevel::Off) {
return;
}
std::stringstream out{};
((Fmts::template Format<Level>(out)), ...);
std::clog << out.str() << msg << std::endl;
}
};
template <typename... Fmts>
struct LoggerBuilder {
template <typename Fmt>
inline auto With() {
return LoggerBuilder<Fmts..., Fmt>{};
}
inline auto Build() {
return Logger<Fmts...>{};
}
};
struct DefaultFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
}
};
struct ExplicitFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
using enum LogLevel;
if constexpr (Level == Debug) {
buf << "[DEBUG] ";
} else if constexpr (Level == Info) {
buf << "[INFO] ";
} else if constexpr (Level == Warn) {
buf << "[WARN] ";
} else if constexpr (Level == Error) {
buf << "[ERROR] ";
} else if constexpr (Level == Fatal) {
buf << "[FATAL] ";
}
}
};
struct LongTimeFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
buf << "[00:00:00.00 00/00/0000] ";
}
};
int main() {
auto logger = LoggerBuilder{}
.With<LongTimeFormatter>()
.With<ExplicitFormatter>()
.Build();
logger.Log<LogLevel::Debug>("My Debug Message");
logger.Log<LogLevel::Info>("My Info Message");
logger.Log<LogLevel::Warn>("My Warn Message");
logger.Log<LogLevel::Error>("My Error Message");
logger.Log<LogLevel::Fatal>("My Fatal Message");
logger.Log<LogLevel::Off>("Not Printed");
return 0;
}
It uses templates rather than RTTI, so you get almost a zero-cost abstraction.