#pragma once #include #include #include "common.hpp" #include "utils/string.hpp" LEMONBUDDY_NS enum class loglevel { NONE = 0, ERROR, WARNING, INFO, TRACE, }; /** * Convert given loglevel name to its enum type counterpart */ auto parse_loglevel_name = [](string name) { if (string_util::compare(name, "error")) return loglevel::ERROR; else if (string_util::compare(name, "warning")) return loglevel::WARNING; else if (string_util::compare(name, "info")) return loglevel::INFO; else if (string_util::compare(name, "trace")) return loglevel::TRACE; else return loglevel::NONE; }; class logger { public: /** * Construct logger */ explicit logger(loglevel level) : m_level(level) { if (isatty(STDOUT_FILENO)) { // clang-format off m_prefixes[loglevel::TRACE] = "\r\033[0;90m- "; m_prefixes[loglevel::INFO] = "\r\033[1;32m* \033[0m"; m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarning: \033[0m"; m_prefixes[loglevel::ERROR] = "\r\033[1;31merror: \033[0m"; m_suffixes[loglevel::TRACE] = "\033[0m"; m_suffixes[loglevel::INFO] = "\033[0m"; m_suffixes[loglevel::WARNING] = "\033[0m"; m_suffixes[loglevel::ERROR] = "\033[0m"; // clang-format on } } /** * Construct logger */ explicit logger(string level_name) : logger(parse_loglevel_name(level_name)) {} /** * Set output verbosity */ void verbosity(loglevel level) { #ifndef DEBUG if (level == loglevel::TRACE) throw application_error("not a debug build: trace disabled..."); #endif m_level = level; } /** * Set output verbosity by loglevel name */ void verbosity(string level) { verbosity(parse_loglevel_name(level)); } /** * Output a trace message */ template void trace(string message, Args... args) const { #ifdef DEBUG output(loglevel::TRACE, message, args...); #endif } /** * Output an info message */ template void info(string message, Args... args) const { output(loglevel::INFO, message, args...); } /** * Output a warning message */ template void warn(string message, Args... args) const { output(loglevel::WARNING, message, args...); } /** * Output an error message */ template void err(string message, Args... args) const { output(loglevel::ERROR, message, args...); } /** * Configure injection module */ template static di::injector configure(loglevel level = loglevel::NONE) { auto instance = factory::generic_singleton(level); return di::make_injector(di::bind<>().to(instance)); } protected: template decltype(auto) convert(T&& arg) const { return forward(arg); }; /** * Convert string to const char* */ const char* convert(string arg) const { return arg.c_str(); }; /** * Write the log message to the output channel * if the defined verbosity level allows it */ template void output(loglevel level, string format, Args... values) const { if (level > m_level) return; auto prefix = m_prefixes.find(level)->second; auto suffix = m_suffixes.find(level)->second; // silence the compiler #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" dprintf(m_fd, (prefix + format + suffix + "\n").c_str(), convert(values)...); #pragma clang diagnostic pop } private: /** * Logger verbosity level */ loglevel m_level = loglevel::TRACE; /** * File descriptor used when writing the log messages */ int m_fd = STDOUT_FILENO; /** * Loglevel specific prefixes */ // clang-format off map m_prefixes { {loglevel::TRACE, "lemonbuddy|trace "}, {loglevel::INFO, "lemonbuddy|info "}, {loglevel::WARNING, "lemonbuddy|warn "}, {loglevel::ERROR, "lemonbuddy|error "}, }; /** * Loglevel specific suffixes */ map m_suffixes { {loglevel::TRACE, ""}, {loglevel::INFO, ""}, {loglevel::WARNING, ""}, {loglevel::ERROR, ""}, }; // clang-format on }; LEMONBUDDY_NS_END