File logger.h
Go to the documentation of this file
#pragma once
#include <cstdint>
#include <iostream>
#include <source_location>
#include <sstream>
#include <string>
#include <string_view> // Add this
using namespace std::literals; // required for ""sv
namespace logger {
enum class LogLevel : uint8_t { LOG, ERROR, DEBUG };
constexpr std::string_view levelString(const LogLevel& level) {
switch (level) {
case LogLevel::LOG:
return "LOG"sv;
case LogLevel::ERROR:
return "ERROR"sv;
case LogLevel::DEBUG:
return "DEBUG"sv;
}
return "LOG"sv;
}
struct LogMessage {
const LogLevel level = LogLevel::LOG;
const std::string_view message; // View of the original message
const std::string_view file_name; // View of the const char*
// const std::string_view function_name; // View of the const char*
const std::string tags;
const unsigned int line_number;
};
inline const std::string format(const LogMessage& msg) {
std::stringstream str;
str << "[" << levelString(msg.level) << "] " << msg.message;
if (!msg.tags.empty()) {
str << " " << msg.tags;
}
str << " (" << msg.file_name << ":" << msg.line_number << ")";
// if (msg.file_name.length() && bool(msg.line_number) && msg.function_name.length()) {
// str << " (" << msg.file_name << ":" << msg.line_number << " :: " << msg.function_name << ")";
// }
return str.str();
}
class Backend { // abstract base class for backend
protected:
virtual ~Backend() = default;
virtual bool render(const std::string_view& str) = 0;
};
class ConsoleBackend: public Backend {
public:
bool render(const std::string_view& str) override {
std::cout << str << std::endl;
return true;
}
};
struct LogSource {
std::string_view msg;
std::source_location loc;
template <typename StringType>
constexpr LogSource(const StringType& m, const std::source_location& l = std::source_location::current()):
msg(m), loc(l) {}
};
template <class B>
requires std::derived_from<B, Backend>
class Logger {
B backend;
template <typename... Ts>
void
doLogging(const LogLevel& level, const LogSource& src, Ts&&... flags) {
std::stringstream tags;
((tags << "[" << flags << "] "), ...);
LogMessage log{
.level = level,
.message = src.msg,
.file_name = src.loc.file_name(),
// .function_name = src.loc.function_name(),
.tags = tags.str(),
.line_number = src.loc.line(),
};
std::string logStr = format(log);
backend.render(logStr);
}
public:
template <typename... Ts>
void LOG(LogSource& src, Ts&&... flags) {
doLogging(LogLevel::LOG, src, std::forward<Ts>(flags)...);
};
template <typename... Ts>
void ERROR(LogSource& src, Ts&&... flags) {
doLogging(LogLevel::ERROR, src, std::forward<Ts>(flags)...);
};
template <typename... Ts>
void DEBUG(LogSource& src, Ts&&... flags) {
doLogging(LogLevel::DEBUG, src, std::forward<Ts>(flags)...);
};
};
inline static Logger<ConsoleBackend> defaultLogger;
template <typename... Ts>
void LOG(LogSource src, Ts&&... flags) {
defaultLogger.LOG(src, std::forward<Ts>(flags)...);
};
template <typename... Ts>
void ERROR(LogSource src, Ts&&... flags) {
defaultLogger.ERROR(src, std::forward<Ts>(flags)...);
};
template <typename... Ts>
void DEBUG(LogSource src, Ts&&... flags) {
defaultLogger.DEBUG(src, std::forward<Ts>(flags)...);
};
// What if these were classes, whose initializers did the logging? A lot more would be definitively known at
// compile time...
}; // namespace logger