mirror of
https://github.com/ModOrganizer2/modorganizer-uibase.git
synced 2026-07-27 14:03:49 -07:00
433 lines
8.7 KiB
C++
433 lines
8.7 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "utility.h"
|
|
#include <iostream>
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4365)
|
|
namespace spdlog { using wstring_view_t = fmt::basic_string_view<wchar_t>; }
|
|
#define SPDLOG_WCHAR_FILENAMES 1
|
|
#include <spdlog/logger.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <spdlog/sinks/base_sink.h>
|
|
#include <spdlog/sinks/dist_sink.h>
|
|
#include <spdlog/sinks/daily_file_sink.h>
|
|
#include <spdlog/sinks/rotating_file_sink.h>
|
|
#include <spdlog/sinks/basic_file_sink.h>
|
|
#pragma warning(pop)
|
|
|
|
|
|
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:
|
|
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 = fmt::to_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 = fmt::to_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::createLogger(const std::string& name)
|
|
{
|
|
m_sinks.reset(new spdlog::sinks::dist_sink<std::mutex>);
|
|
|
|
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, cs->WHITE);
|
|
cs->set_color(spdlog::level::debug, cs->WHITE);
|
|
}
|
|
|
|
addSink(m_console);
|
|
}
|
|
|
|
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
|
|
|
|
|
|
namespace MOBase::log::details
|
|
{
|
|
|
|
std::string converter<std::wstring>::convert(const std::wstring& s)
|
|
{
|
|
return QString::fromStdWString(s).toStdString();
|
|
}
|
|
|
|
std::string converter<QString>::convert(const QString& s)
|
|
{
|
|
return s.toStdString();
|
|
}
|
|
|
|
std::string converter<QSize>::convert(const QSize& s)
|
|
{
|
|
return fmt::format("QSize({}, {})", s.width(), s.height());
|
|
}
|
|
|
|
std::string converter<QRect>::convert(const QRect& r)
|
|
{
|
|
return fmt::format(
|
|
"QRect({},{}-{},{})", r.left(), r.top(), r.right(), r.bottom());
|
|
}
|
|
|
|
std::string converter<QColor>::convert(const QColor& c)
|
|
{
|
|
return fmt::format(
|
|
"QColor({}, {}, {}, {})",
|
|
c.red(), c.green(), c.blue(), c.alpha());
|
|
}
|
|
|
|
std::string converter<QByteArray>::convert(const QByteArray& v)
|
|
{
|
|
return fmt::format("QByteArray({} bytes)", v.size());
|
|
}
|
|
|
|
std::string converter<QVariant>::convert(const QVariant& v)
|
|
{
|
|
return fmt::format(
|
|
"QVariant(type={}, value='{}')",
|
|
v.typeName(), (v.type() == QVariant::ByteArray ?
|
|
"(binary)" : v.toString().toStdString()));
|
|
}
|
|
|
|
|
|
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
|