aboutsummaryrefslogtreecommitdiff
path: root/libs/uibase/src/log.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/uibase/src/log.cpp')
-rw-r--r--libs/uibase/src/log.cpp411
1 files changed, 411 insertions, 0 deletions
diff --git a/libs/uibase/src/log.cpp b/libs/uibase/src/log.cpp
new file mode 100644
index 0000000..a44c9e3
--- /dev/null
+++ b/libs/uibase/src/log.cpp
@@ -0,0 +1,411 @@
+#include <uibase/log.h>
+#include "pch.h"
+#include <uibase/utility.h>
+#include <iostream>
+
+#include <algorithm>
+#include <locale>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4365)
+#endif
+
+#ifdef _WIN32
+#define SPDLOG_WCHAR_FILENAMES 1
+#endif
+
+#include <spdlog/logger.h>
+#include <spdlog/sinks/base_sink.h>
+#include <spdlog/sinks/basic_file_sink.h>
+#include <spdlog/sinks/daily_file_sink.h>
+#include <spdlog/sinks/dist_sink.h>
+#include <spdlog/sinks/rotating_file_sink.h>
+#include <spdlog/sinks/stdout_color_sinks.h>
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+namespace MOBase::log
+{
+
+namespace fs = std::filesystem;
+static std::unique_ptr<Logger> 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<std::mutex>
+{
+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);
+
+ if (formatted.size() >= 2) {
+ // remove \r\n
+ e.formattedMessage.assign(formatted.begin(), formatted.end() - 2);
+ } else {
+ e.formattedMessage = std::string(formatted);
+ }
+
+ (*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<Callback*> 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<spdlog::sinks::daily_file_sink_mt>(
+ f.file.native(), f.dailyHour, f.dailyMinute);
+ }
+
+ case File::Rotating: {
+ return std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
+ f.file.native(), f.maxSize, f.maxFiles);
+ }
+
+ case File::Single: {
+ return std::make_shared<spdlog::sinks::basic_file_sink_mt>(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<spdlog::sinks::dist_sink<std::mutex>*>(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<CallbackSink*>(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<std::mutex>);
+
+#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<sink_type*>(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<spdlog::sinks::sink> 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<spdlog::sinks::dist_sink<std::mutex>*>(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<spdlog::pattern_formatter>(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<Logger>(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<std::size_t>(p - start));
+ lg.log(toSpdlog(lv), "{}", sv);
+
+ if (!*p) {
+ break;
+ }
+
+ ++p;
+ start = p;
+ }
+ } catch (...) {
+ // eat it
+ }
+}
+
+} // namespace MOBase::log::details