#include #include "pch.h" #include #include #include #include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4365) #endif #ifdef _WIN32 #define SPDLOG_WCHAR_FILENAMES 1 #endif #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma warning(pop) #endif namespace MOBase::log { namespace fs = std::filesystem; static std::unique_ptr g_default; spdlog::level::level_enum toSpdlog(Levels lv) { switch (lv) { case Debug: return spdlog::level::debug; case Warning: return spdlog::level::warn; case Error: return spdlog::level::err; case Info: // fall-through default: return spdlog::level::info; } } Levels fromSpdlog(spdlog::level::level_enum lv) { switch (lv) { case spdlog::level::trace: case spdlog::level::debug: return Debug; case spdlog::level::warn: return Warning; case spdlog::level::critical: // fall-through case spdlog::level::err: return Error; case spdlog::level::info: // fall-through case spdlog::level::off: case spdlog::level::n_levels: // to please MSVC default: return Info; } } class CallbackSink : public spdlog::sinks::base_sink { public: CallbackSink(Callback* f) : m_f(f) {} void setCallback(Callback* f) { m_f = f; } protected: void sink_it_(const spdlog::details::log_msg& m) override { thread_local bool active = false; if (active) { // trying to log from a log callback, ignoring return; } if (!m_f) { // disabled return; } try { auto g = Guard([&] { active = false; }); active = true; Entry e; e.time = m.time; e.level = fromSpdlog(m.level); e.message = std::string(m.payload); spdlog::memory_buf_t formatted; base_sink::formatter_->format(m, formatted); // Strip trailing newline(s). spdlog appends \r\n on Windows but only // \n on Linux; unconditionally removing 2 chars truncates the real message. { auto end = formatted.end(); while (end != formatted.begin() && (*(end - 1) == '\n' || *(end - 1) == '\r')) { --end; } e.formattedMessage.assign(formatted.begin(), end); } (*m_f)(std::move(e)); } catch (std::exception& e) { fprintf(stderr, "uncaugh exception in logging callback, %s\n", e.what()); } catch (...) { fprintf(stderr, "uncaught exception in logging callback\n"); } } void flush_() override { // no-op } private: std::atomic m_f; }; File::File() : type(None), maxSize(0), maxFiles(0), dailyHour(0), dailyMinute(0) {} File File::daily(fs::path file, int hour, int minute) { File fl; fl.type = Daily; fl.file = std::move(file); fl.dailyHour = hour; fl.dailyMinute = minute; return fl; } File File::rotating(fs::path file, std::size_t maxSize, std::size_t maxFiles) { File fl; fl.type = Rotating; fl.file = std::move(file); fl.maxSize = maxSize; fl.maxFiles = maxFiles; return fl; } File File::single(std::filesystem::path file) { File fl; fl.type = Single; fl.file = std::move(file); return fl; } spdlog::sink_ptr createFileSink(const File& f) { try { switch (f.type) { case File::Daily: { return std::make_shared( f.file.native(), f.dailyHour, f.dailyMinute); } case File::Rotating: { return std::make_shared( f.file.native(), f.maxSize, f.maxFiles); } case File::Single: { return std::make_shared(f.file.native(), true); } case File::None: // fall-through default: return {}; } } catch (spdlog::spdlog_ex& e) { std::cerr << "failed to create file log, " << e.what() << "\n"; return {}; } } Logger::Logger(LoggerConfiguration conf_moved) : m_conf(std::move(conf_moved)) { createLogger(m_conf.name); const auto timeType = m_conf.utc ? spdlog::pattern_time_type::utc : spdlog::pattern_time_type::local; m_logger->set_level(toSpdlog(m_conf.maxLevel)); m_logger->set_pattern(m_conf.pattern, timeType); m_logger->flush_on(spdlog::level::trace); } // anchor Logger::~Logger() = default; Levels Logger::level() const { return fromSpdlog(m_logger->level()); } void Logger::setLevel(Levels lv) { m_logger->set_level(toSpdlog(lv)); } void Logger::setPattern(const std::string& s) { m_logger->set_pattern(s); } void Logger::setFile(const File& f) { if (m_file) { auto* ds = static_cast*>(m_sinks.get()); ds->remove_sink(m_file); m_file = {}; } if (f.type != File::None) { try { m_file = createFileSink(f); if (m_file) { addSink(m_file); } } catch (spdlog::spdlog_ex& e) { error("{}", e.what()); } } } void Logger::setCallback(Callback* f) { if (m_callback) { static_cast(m_callback.get())->setCallback(f); } else { m_callback.reset(new CallbackSink(f)); addSink(m_callback); } } void Logger::addToBlacklist(const std::string& filter, const std::string& replacement) { if (filter.length() <= 0 || replacement.length() <= 0) { // nothing to do return; } bool present = false; for (BlacklistEntry& e : m_conf.blacklist) { if (iequals(e.filter, filter)) { e.replacement = replacement; present = true; break; } } if (!present) { m_conf.blacklist.push_back(BlacklistEntry(filter, replacement)); } } void Logger::removeFromBlacklist(const std::string& filter) { if (filter.length() <= 0) { // nothing to do return; } for (auto it = m_conf.blacklist.begin(); it != m_conf.blacklist.end();) { if (iequals(it->filter, filter)) { it = m_conf.blacklist.erase(it); } else { ++it; } } } void Logger::resetBlacklist() { m_conf.blacklist.clear(); } void Logger::createLogger(const std::string& name) { m_sinks.reset(new spdlog::sinks::dist_sink); #ifdef _WIN32 DWORD console_mode; if (::GetConsoleMode(::GetStdHandle(STD_ERROR_HANDLE), &console_mode) != 0) { using sink_type = spdlog::sinks::wincolor_stderr_sink_mt; m_console.reset(new sink_type); if (auto* cs = dynamic_cast(m_console.get())) { cs->set_color(spdlog::level::info, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); cs->set_color(spdlog::level::debug, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); } addSink(m_console); } #else // On Linux, use ANSI color stderr sink m_console.reset(new spdlog::sinks::ansicolor_stderr_sink_mt); addSink(m_console); #endif m_logger.reset(new spdlog::logger(name, m_sinks)); } void Logger::addSink(std::shared_ptr sink) { // this is called for both the file and callback sinks // // in createLogger(), the dist_sink that was just created will be given the // pattern that was set in Logger::Logger(), and will pass it to its children; // the log level is irrelevant in child sinks because dist_sink checks it // itself // // the problem then is that dist_sink doesn't have children yet, they're added // in setFile() and setCallback(), which can be called by the user much later // (or not at all) // // however, when a sink is added to dist_sink, it does _not_ set the pattern // on it, it merely adds it to the list // // this sets the formatter on the sink manually before adding it to dist_sink auto* ds = static_cast*>(m_sinks.get()); const auto timeType = m_conf.utc ? spdlog::pattern_time_type::utc : spdlog::pattern_time_type::local; sink->set_formatter( std::make_unique(m_conf.pattern, timeType)); ds->add_sink(sink); } QString levelToString(Levels level) { const auto spdlogLevel = toSpdlog(level); const auto sv = spdlog::level::to_string_view(spdlogLevel); const std::string s(sv.begin(), sv.end()); return QString::fromStdString(s); } void createDefault(LoggerConfiguration conf) { g_default = std::make_unique(conf); } Logger& getDefault() { Q_ASSERT(g_default); return *g_default; } } // namespace MOBase::log namespace MOBase::log::details { void doLogImpl(spdlog::logger& lg, Levels lv, const std::string& s) noexcept { try { const char* start = s.c_str(); const char* p = start; for (;;) { while (*p && *p != '\n') { ++p; } std::string_view sv(start, static_cast(p - start)); lg.log(toSpdlog(lv), "{}", sv); if (!*p) { break; } ++p; start = p; } } catch (...) { // eat it } } } // namespace MOBase::log::details