112 lines
2.8 KiB
C++
112 lines
2.8 KiB
C++
#include "log.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <format>
|
|
#include <exception>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <streambuf>
|
|
#include <thread>
|
|
|
|
#include "util/preferences.hpp"
|
|
#include "util/time.hpp"
|
|
|
|
using namespace game::util;
|
|
|
|
namespace game
|
|
{
|
|
class StderrToLoggerBuf final : public std::streambuf
|
|
{
|
|
Logger* logger{};
|
|
std::string buffer;
|
|
std::mutex mutex;
|
|
|
|
public:
|
|
explicit StderrToLoggerBuf(Logger* target) : logger(target) {}
|
|
|
|
protected:
|
|
int overflow(int ch) override
|
|
{
|
|
if (ch == traits_type::eof()) return traits_type::not_eof(ch);
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
if (ch == '\n')
|
|
{
|
|
if (logger && !buffer.empty())
|
|
{
|
|
logger->error(buffer);
|
|
buffer.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer.push_back(static_cast<char>(ch));
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
int sync() override
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
if (logger && !buffer.empty())
|
|
{
|
|
logger->error(buffer);
|
|
buffer.clear();
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
std::streambuf* old_stderr_buf = nullptr;
|
|
|
|
void Logger::write_raw(const std::string& message)
|
|
{
|
|
std::fwrite(message.c_str(), 1, message.size(), stdout);
|
|
std::fwrite("\n", 1, 1, stdout);
|
|
std::fflush(stdout);
|
|
if (file.is_open()) file << message << '\n' << std::flush;
|
|
}
|
|
|
|
void Logger::write(const Level level, const std::string& message)
|
|
{
|
|
std::string formatted = std::format("{} {} {}", LEVEL_STRINGS[level], time::get("(%d-%B-%Y %I:%M:%S)"), message);
|
|
write_raw(formatted);
|
|
}
|
|
|
|
void Logger::info(const std::string& message) { write(LEVEL_INFO, message); }
|
|
void Logger::warning(const std::string& message) { write(LEVEL_WARNING, message); }
|
|
void Logger::error(const std::string& message) { write(LEVEL_ERROR, message); }
|
|
void Logger::fatal(const std::string& message) { write(LEVEL_FATAL, message); }
|
|
void Logger::open(const std::filesystem::path& path) { file.open(path, std::ios::out | std::ios::app); }
|
|
|
|
std::filesystem::path Logger::path() { return preferences::path() / "log.txt"; }
|
|
|
|
Logger::Logger()
|
|
{
|
|
open(path());
|
|
static StderrToLoggerBuf stderr_buf(this);
|
|
old_stderr_buf = std::cerr.rdbuf(&stderr_buf);
|
|
std::cerr.setf(std::ios::unitbuf);
|
|
std::set_terminate(
|
|
[]
|
|
{
|
|
try
|
|
{
|
|
if (auto eptr = std::current_exception()) std::rethrow_exception(eptr);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger.fatal(std::string("Unhandled exception: ") + ex.what());
|
|
}
|
|
catch (...)
|
|
{
|
|
logger.fatal("Unhandled exception: <unknown>");
|
|
}
|
|
std::abort();
|
|
});
|
|
}
|
|
}
|
|
|
|
game::Logger logger;
|