mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 09:50:40 +08:00
916 lines
25 KiB
C++
916 lines
25 KiB
C++
#pragma once
|
||
|
||
#include <filesystem>
|
||
#include <fstream>
|
||
#include <functional>
|
||
#include <iostream>
|
||
#include <mutex>
|
||
#include <streambuf>
|
||
#include <thread>
|
||
#include <type_traits>
|
||
#include <utility>
|
||
|
||
#include "Common/AsstTypes.h"
|
||
#include "Common/AsstVersion.h"
|
||
#include "Locale.hpp"
|
||
#include "Meta.hpp"
|
||
#include "Platform.hpp"
|
||
#include "Ranges.hpp"
|
||
#include "SingletonHolder.hpp"
|
||
#include "Time.hpp"
|
||
#include "WorkingDir.hpp"
|
||
|
||
#if defined(__APPLE__) || defined(__linux__)
|
||
#include <unistd.h>
|
||
#endif
|
||
|
||
namespace asst
|
||
{
|
||
template <typename Stream, typename T>
|
||
concept has_stream_insertion_operator = requires { std::declval<Stream&>() << std::declval<T>(); };
|
||
template <typename T>
|
||
concept enum_could_to_string = requires { asst::enum_to_string(std::declval<T>()); };
|
||
|
||
// is_reference_wrapper
|
||
template <typename T>
|
||
struct is_reference_wrapper : std::false_type
|
||
{
|
||
};
|
||
|
||
template <typename T>
|
||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type
|
||
{
|
||
};
|
||
|
||
template <typename T>
|
||
struct is_reference_wrapper<std::reference_wrapper<T>&> : std::true_type
|
||
{
|
||
};
|
||
|
||
template <typename T>
|
||
struct is_reference_wrapper<std::reference_wrapper<T>&&> : std::true_type
|
||
{
|
||
};
|
||
|
||
template <typename T>
|
||
inline constexpr bool is_reference_wrapper_v = is_reference_wrapper<T>::value;
|
||
|
||
// from_reference_wrapper
|
||
template <typename T>
|
||
struct from_reference_wrapper
|
||
{
|
||
typedef T type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct from_reference_wrapper<std::reference_wrapper<T>>
|
||
{
|
||
typedef typename from_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct from_reference_wrapper<std::reference_wrapper<T>&>
|
||
{
|
||
typedef typename from_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct from_reference_wrapper<std::reference_wrapper<T>&&>
|
||
{
|
||
typedef typename from_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
using from_reference_wrapper_t = typename from_reference_wrapper<T>::type;
|
||
|
||
// to_reference_wrapper
|
||
template <typename T>
|
||
struct to_reference_wrapper
|
||
{
|
||
typedef T type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct to_reference_wrapper<T&>
|
||
{
|
||
typedef std::reference_wrapper<T> type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct to_reference_wrapper<std::reference_wrapper<T>>
|
||
{
|
||
typedef typename to_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct to_reference_wrapper<std::reference_wrapper<T>&>
|
||
{
|
||
typedef typename to_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
struct to_reference_wrapper<std::reference_wrapper<T>&&>
|
||
{
|
||
typedef typename to_reference_wrapper<T&>::type type;
|
||
};
|
||
|
||
template <typename T>
|
||
using to_reference_wrapper_t = typename to_reference_wrapper<T>::type;
|
||
|
||
template <typename T>
|
||
struct remove_cvref
|
||
{
|
||
typedef std::remove_cvref_t<from_reference_wrapper_t<std::remove_cvref_t<T>>> type;
|
||
};
|
||
|
||
template <typename T>
|
||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||
|
||
template <typename T>
|
||
constexpr from_reference_wrapper_t<T> convert_reference_wrapper(T&& x)
|
||
{
|
||
return x;
|
||
}
|
||
|
||
namespace detail
|
||
{
|
||
class scope_slice
|
||
{
|
||
public:
|
||
using id = int;
|
||
|
||
scope_slice() = default;
|
||
|
||
std::string push(id& i)
|
||
{
|
||
i = 0;
|
||
if (auto iter = std::find_if(m_state.rbegin(), m_state.rend(), [](int e) { return e != -1; });
|
||
iter != m_state.rend()) {
|
||
i = *iter + 1;
|
||
}
|
||
|
||
std::string result;
|
||
result.reserve(m_state.size() * 3);
|
||
for (auto e : m_state) {
|
||
result += (e == -1 ? " " : "│");
|
||
}
|
||
|
||
m_state.push_back(i);
|
||
result += "┌";
|
||
|
||
return result;
|
||
}
|
||
|
||
std::string pop(id i)
|
||
{
|
||
std::string result;
|
||
result.reserve(m_state.size() * 3);
|
||
std::string sym = "│";
|
||
std::string emp = " ";
|
||
for (auto& e : m_state) {
|
||
if (i != -1 && e == i) {
|
||
result += "└";
|
||
e = -1;
|
||
sym = "┼";
|
||
emp = "─";
|
||
}
|
||
else {
|
||
result += (e == -1 ? emp : sym);
|
||
}
|
||
}
|
||
|
||
while (!m_state.empty() && m_state.back() == -1) {
|
||
m_state.pop_back();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
std::string next()
|
||
{
|
||
while (!m_state.empty() && m_state.back() == -1) {
|
||
m_state.pop_back();
|
||
}
|
||
|
||
std::string result;
|
||
result.reserve(m_state.size() * 3);
|
||
auto lhs = m_state.end();
|
||
auto rhs = m_state.end();
|
||
auto iter = m_state.begin();
|
||
// | <- rhs
|
||
// ------
|
||
// | <- lhs
|
||
for (; iter != m_state.end(); ++iter) {
|
||
if (*iter != -1) {
|
||
result += "│";
|
||
continue;
|
||
}
|
||
|
||
lhs = iter;
|
||
result += "┌";
|
||
// if (iter != m_state.end())
|
||
++iter;
|
||
|
||
for (; iter != m_state.end(); ++iter) {
|
||
if (*iter == -1) {
|
||
result += "─";
|
||
}
|
||
else {
|
||
break;
|
||
}
|
||
}
|
||
rhs = iter;
|
||
result += "┘";
|
||
|
||
if (iter != m_state.end()) {
|
||
++iter;
|
||
}
|
||
|
||
for (; iter != m_state.end(); ++iter) {
|
||
result += (*iter == -1 ? " " : "│");
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (lhs != m_state.end()) {
|
||
if (rhs == m_state.end()) {
|
||
m_state.erase(lhs, rhs);
|
||
}
|
||
else {
|
||
std::swap(*lhs, *rhs);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private:
|
||
std::vector<id> m_state {};
|
||
};
|
||
} // namespace detail
|
||
|
||
class console_ostream
|
||
{
|
||
std::reference_wrapper<std::ostream> m_ofs;
|
||
|
||
public:
|
||
console_ostream(console_ostream&&) = default;
|
||
console_ostream(const console_ostream&) = default;
|
||
console_ostream& operator=(console_ostream&&) = default;
|
||
console_ostream& operator=(const console_ostream&) = default;
|
||
|
||
console_ostream(std::ostream& stream) :
|
||
m_ofs(stream)
|
||
{
|
||
}
|
||
|
||
console_ostream(std::reference_wrapper<std::ostream> stream) :
|
||
m_ofs(stream)
|
||
{
|
||
}
|
||
|
||
template <typename T>
|
||
requires has_stream_insertion_operator<std::ostream, T>
|
||
console_ostream& operator<<(T&& v)
|
||
{
|
||
#ifdef _WIN32
|
||
if constexpr (std::convertible_to<T, std::string_view>) {
|
||
asst::utils::utf8_scope scope(m_ofs.get());
|
||
m_ofs.get() << std::forward<T>(v);
|
||
}
|
||
else {
|
||
m_ofs.get() << std::forward<T>(v);
|
||
}
|
||
#else
|
||
m_ofs.get() << std::forward<T>(v);
|
||
#endif
|
||
return *this;
|
||
}
|
||
|
||
console_ostream& operator<<(std::ostream& (*pf)(std::ostream&))
|
||
{
|
||
m_ofs.get() << pf;
|
||
return *this;
|
||
}
|
||
};
|
||
|
||
template <typename... Args>
|
||
class ostreams
|
||
{
|
||
std::tuple<Args...> m_ofss;
|
||
|
||
public:
|
||
ostreams(ostreams&&) = default;
|
||
ostreams(const ostreams&) = default;
|
||
ostreams& operator=(ostreams&&) = default;
|
||
ostreams& operator=(const ostreams&) = default;
|
||
|
||
ostreams(Args&&... args) :
|
||
m_ofss(std::forward<Args>(args)...)
|
||
{
|
||
}
|
||
|
||
template <typename T>
|
||
requires has_stream_insertion_operator<std::ostream, T>
|
||
ostreams& operator<<(T&& x)
|
||
{
|
||
streams_put(m_ofss, x, std::index_sequence_for<Args...> {});
|
||
return *this;
|
||
}
|
||
|
||
template <typename Tuple, typename T, size_t... Is>
|
||
static void streams_put(Tuple& t, T&& x, std::index_sequence<Is...>)
|
||
{
|
||
((convert_reference_wrapper(std::get<Is>(t)) << std::forward<T>(x)), ...);
|
||
}
|
||
|
||
ostreams& operator<<(std::ostream& (*pf)(std::ostream&))
|
||
{
|
||
streams_put(m_ofss, pf, std::index_sequence_for<Args...> {});
|
||
return *this;
|
||
}
|
||
|
||
template <typename Tuple, size_t... Is>
|
||
static void streams_put(Tuple& t, std::ostream& (*pf)(std::ostream&), std::index_sequence<Is...>)
|
||
{
|
||
((convert_reference_wrapper(std::get<Is>(t)) << pf), ...);
|
||
}
|
||
};
|
||
|
||
template <typename... Args>
|
||
ostreams(Args&&...) -> ostreams<to_reference_wrapper_t<Args>...>;
|
||
|
||
class Logger : public SingletonHolder<Logger>
|
||
{
|
||
public:
|
||
struct separator
|
||
{
|
||
constexpr separator() = default;
|
||
constexpr separator(const separator&) = default;
|
||
constexpr separator(separator&&) noexcept = default;
|
||
|
||
constexpr explicit separator(std::string_view s) noexcept :
|
||
str(s)
|
||
{
|
||
}
|
||
|
||
constexpr separator& operator=(const separator&) = default;
|
||
constexpr separator& operator=(separator&&) noexcept = default;
|
||
|
||
constexpr separator& operator=(std::string_view s) noexcept
|
||
{
|
||
str = s;
|
||
return *this;
|
||
}
|
||
|
||
static const separator none;
|
||
static const separator space;
|
||
static const separator tab;
|
||
static const separator newline;
|
||
static const separator comma;
|
||
|
||
std::string_view str;
|
||
};
|
||
|
||
struct level
|
||
{
|
||
constexpr level(const level&) = default;
|
||
constexpr level(level&&) noexcept = default;
|
||
|
||
constexpr explicit level(std::string_view s) noexcept :
|
||
str(s)
|
||
{
|
||
}
|
||
|
||
constexpr level& operator=(const level&) = default;
|
||
constexpr level& operator=(level&&) noexcept = default;
|
||
|
||
constexpr level& operator=(std::string_view s) noexcept
|
||
{
|
||
str = s;
|
||
return *this;
|
||
}
|
||
|
||
bool enabled = true;
|
||
|
||
bool is_enabled() const { return enabled; }
|
||
|
||
void set_enabled(bool enable) { enabled = enable; }
|
||
|
||
static level debug;
|
||
static level trace;
|
||
static level info;
|
||
static level warn;
|
||
static level error;
|
||
|
||
std::string_view str;
|
||
};
|
||
|
||
template <typename stream_t>
|
||
class LogStream
|
||
{
|
||
public:
|
||
template <typename T>
|
||
LogStream& operator<<(T&& arg)
|
||
{
|
||
if constexpr (std::same_as<separator, remove_cvref_t<T>>) {
|
||
m_sep = std::forward<T>(arg);
|
||
}
|
||
else {
|
||
// 如果是 level,则不输出 separator
|
||
if constexpr (!std::same_as<Logger::level, remove_cvref_t<T>>) {
|
||
stream_put(m_ofs, m_sep.str);
|
||
}
|
||
stream_put(m_ofs, std::forward<T>(arg));
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
template <typename _stream_t = stream_t>
|
||
LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv) :
|
||
m_trace_lock(mtx),
|
||
m_ofs(ofs)
|
||
{
|
||
*this << lv;
|
||
}
|
||
|
||
template <typename _stream_t = stream_t, typename... Args>
|
||
LogStream(std::mutex& mtx, _stream_t&& ofs, Logger::level lv, Args&&... buff) :
|
||
m_trace_lock(mtx),
|
||
m_ofs(ofs)
|
||
{
|
||
((*this << lv) << ... << std::forward<Args>(buff));
|
||
}
|
||
|
||
template <typename _stream_t = stream_t, typename... Args>
|
||
LogStream(std::unique_lock<std::mutex>&& lock, _stream_t&& ofs, Logger::level lv, Args&&... buff) :
|
||
m_trace_lock(std::move(lock)),
|
||
m_ofs(ofs)
|
||
{
|
||
((*this << lv) << ... << std::forward<Args>(buff));
|
||
}
|
||
|
||
LogStream(LogStream&&) = delete;
|
||
LogStream(const LogStream&) = delete;
|
||
LogStream& operator=(LogStream&&) = delete;
|
||
LogStream& operator=(const LogStream&) = delete;
|
||
|
||
~LogStream() { m_ofs << std::endl; }
|
||
|
||
private:
|
||
template <typename Stream, typename T>
|
||
static Stream& stream_put(Stream& s, T&& v)
|
||
{
|
||
if constexpr (std::same_as<std::filesystem::path, remove_cvref_t<T>>) {
|
||
s << utils::path_to_utf8_string(std::forward<T>(v));
|
||
}
|
||
else if constexpr (std::same_as<Logger::level, remove_cvref_t<T>>) {
|
||
constexpr int buff_len = 128;
|
||
char buff[buff_len] = { 0 };
|
||
#ifdef _WIN32
|
||
#ifdef _MSC_VER
|
||
sprintf_s(
|
||
buff,
|
||
buff_len,
|
||
#else // ! _MSC_VER
|
||
sprintf(
|
||
buff,
|
||
#endif // END _MSC_VER
|
||
"[%s][%s][Px%x][Tx%4.4lx]",
|
||
asst::utils::get_format_time().c_str(),
|
||
v.str.data(),
|
||
m_pid,
|
||
m_tid);
|
||
#else // ! _WIN32
|
||
sprintf(
|
||
buff,
|
||
"[%s][%s][Px%x][Tx%4.4hx]",
|
||
asst::utils::get_format_time().c_str(),
|
||
v.str.data(),
|
||
m_pid,
|
||
m_tid);
|
||
#endif // END _WIN32
|
||
s << buff;
|
||
}
|
||
else if constexpr (std::is_enum_v<T> && enum_could_to_string<T>) {
|
||
s << asst::enum_to_string(std::forward<T>(v));
|
||
}
|
||
else if constexpr (has_stream_insertion_operator<Stream, T>) {
|
||
s << std::forward<T>(v);
|
||
}
|
||
else if constexpr (std::constructible_from<std::string, T>) {
|
||
s << std::string(std::forward<T>(v));
|
||
}
|
||
else if constexpr (ranges::input_range<T>) {
|
||
s << "[";
|
||
std::string_view comma_space {};
|
||
for (const auto& elem : std::forward<T>(v)) {
|
||
s << comma_space;
|
||
stream_put(s, elem);
|
||
comma_space = ", ";
|
||
}
|
||
s << "]";
|
||
}
|
||
else {
|
||
ASST_STATIC_ASSERT_FALSE(
|
||
"unsupported type, one of the following expected\n"
|
||
"\t1. those can be converted to string;\n"
|
||
"\t2. those can be inserted to stream with operator<< directly;\n"
|
||
"\t3. container or nested container containing 1. 2. or 3. and iterable with "
|
||
"range-based for",
|
||
Stream,
|
||
T);
|
||
}
|
||
return s;
|
||
}
|
||
|
||
separator m_sep = separator::space;
|
||
std::unique_lock<std::mutex> m_trace_lock;
|
||
stream_t m_ofs;
|
||
|
||
inline static thread_local const auto m_pid =
|
||
#ifdef _WIN32
|
||
_getpid();
|
||
#else
|
||
::getpid();
|
||
#endif
|
||
|
||
inline static thread_local const auto m_tid =
|
||
#ifdef _WIN32
|
||
::GetCurrentThreadId();
|
||
#else
|
||
static_cast<unsigned short>(std::hash<std::thread::id> {}(std::this_thread::get_id()));
|
||
#endif
|
||
};
|
||
|
||
// template <typename stream_t>
|
||
// LogStream(std::mutex&, stream_t&) -> LogStream<stream_t&>;
|
||
|
||
// template <typename stream_t>
|
||
// LogStream(std::mutex&, stream_t&&) -> LogStream<stream_t>;
|
||
|
||
template <typename stream_t, typename... Args>
|
||
LogStream(std::mutex&, stream_t&, Args&&...) -> LogStream<stream_t&>;
|
||
|
||
template <typename stream_t, typename... Args>
|
||
LogStream(std::mutex&, stream_t&&, Args&&...) -> LogStream<stream_t>;
|
||
|
||
template <typename stream_t, typename... Args>
|
||
LogStream(std::unique_lock<std::mutex>&&, stream_t&&, Args&&...) -> LogStream<stream_t>;
|
||
|
||
class LogStreambuf : public std::filebuf
|
||
{
|
||
public:
|
||
LogStreambuf(std::filebuf* dest_buf) :
|
||
dest(dest_buf)
|
||
{
|
||
}
|
||
|
||
std::streamsize count_bytes() const { return count; }
|
||
|
||
protected:
|
||
int_type overflow(int_type c) override
|
||
{
|
||
if (c != traits_type::eof()) {
|
||
ch = static_cast<char>(c);
|
||
if (ch == '\n') {
|
||
count += NewLineSize;
|
||
}
|
||
else {
|
||
count++;
|
||
}
|
||
if (dest) {
|
||
dest->sputc(ch);
|
||
}
|
||
}
|
||
return ch;
|
||
}
|
||
|
||
int sync() override
|
||
{
|
||
if (dest) {
|
||
return dest->pubsync();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// 处理字符块
|
||
std::streamsize xsputn(const char* s, std::streamsize n) override
|
||
{
|
||
count += n;
|
||
if (dest) {
|
||
return dest->sputn(s, n);
|
||
}
|
||
return n;
|
||
}
|
||
|
||
private:
|
||
#if defined(_WIN32) || defined(_WIN64)
|
||
const static std::size_t NewLineSize = 2; // \r\n;
|
||
#else
|
||
const static std::size_t NewLineSize = 1; // \n;
|
||
#endif
|
||
char ch = 0;
|
||
std::filebuf* dest;
|
||
std::streamsize count = 0;
|
||
};
|
||
|
||
public:
|
||
virtual ~Logger() override { flush(); }
|
||
|
||
// static bool set_directory(const std::filesystem::path& dir)
|
||
// {
|
||
// if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||
// return false;
|
||
// }
|
||
// m_directory = dir;
|
||
|
||
// return true;
|
||
// }
|
||
|
||
template <typename T>
|
||
auto operator<<(T&& arg)
|
||
{
|
||
std::unique_lock lock { m_trace_mutex };
|
||
rotate();
|
||
if constexpr (std::same_as<level, remove_cvref_t<T>>) {
|
||
#ifdef ASST_DEBUG
|
||
return LogStream(std::move(lock), ostreams { console_ostream(std::cout), m_of }, arg);
|
||
#else
|
||
return LogStream(std::move(lock), m_of, arg);
|
||
#endif
|
||
}
|
||
else {
|
||
#ifdef ASST_DEBUG
|
||
return LogStream(std::move(lock), ostreams { console_ostream(std::cout), m_of }, level::trace, arg);
|
||
#else
|
||
return LogStream(std::move(lock), m_of, level::trace, arg);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
#ifdef ASST_DEBUG
|
||
#define LOGGER_FUNC_WITH_LEVEL(lv) \
|
||
template <typename... Args> \
|
||
inline void lv(Args&&... args) \
|
||
{ \
|
||
std::unique_lock lock { m_trace_mutex }; \
|
||
log(std::move(lock), level::lv, m_scopes.next(), std::forward<Args>(args)...); \
|
||
}
|
||
#else
|
||
#define LOGGER_FUNC_WITH_LEVEL(lv) \
|
||
template <typename... Args> \
|
||
inline void lv(Args&&... args) \
|
||
{ \
|
||
log(level::lv, std::forward<Args>(args)...); \
|
||
}
|
||
#endif
|
||
|
||
LOGGER_FUNC_WITH_LEVEL(trace)
|
||
LOGGER_FUNC_WITH_LEVEL(info)
|
||
LOGGER_FUNC_WITH_LEVEL(warn)
|
||
LOGGER_FUNC_WITH_LEVEL(error)
|
||
|
||
template <typename... Args>
|
||
inline void debug([[maybe_unused]] Args&&... args)
|
||
{
|
||
#ifdef ASST_DEBUG
|
||
constexpr bool need_log = true;
|
||
#else
|
||
static const bool need_log = std::filesystem::exists("DEBUG.txt");
|
||
#endif
|
||
if (need_log) {
|
||
std::unique_lock lock { m_trace_mutex };
|
||
log(std::move(lock), level::debug, m_scopes.next(), std::forward<Args>(args)...);
|
||
}
|
||
}
|
||
|
||
#undef LOGGER_FUNC_WITH_LEVEL
|
||
|
||
template <typename... Args>
|
||
inline int push(Args&&... args)
|
||
{
|
||
int id = -1;
|
||
std::unique_lock lock { m_trace_mutex };
|
||
log(std::move(lock), level::trace, m_scopes.push(id), std::forward<Args>(args)...);
|
||
return id;
|
||
}
|
||
|
||
template <typename... Args>
|
||
inline void pop(int id, Args&&... args)
|
||
{
|
||
std::unique_lock lock { m_trace_mutex };
|
||
log(std::move(lock), level::trace, m_scopes.pop(id), std::forward<Args>(args)...);
|
||
}
|
||
|
||
template <typename... Args>
|
||
inline void log(level lv, Args&&... args)
|
||
{
|
||
if (lv.is_enabled()) {
|
||
((*this << lv) << ... << std::forward<Args>(args));
|
||
}
|
||
}
|
||
|
||
template <typename... Args>
|
||
inline void log(std::unique_lock<std::mutex>&& lock, level lv, Args&&... args)
|
||
{
|
||
if (!lv.is_enabled()) {
|
||
return;
|
||
}
|
||
rotate();
|
||
(LogStream(
|
||
std::move(lock),
|
||
#ifdef ASST_DEBUG
|
||
ostreams { console_ostream(std::cout), m_of },
|
||
#else
|
||
m_of,
|
||
#endif
|
||
lv)
|
||
<< ... << std::forward<Args>(args));
|
||
}
|
||
|
||
void flush()
|
||
{
|
||
std::unique_lock<std::mutex> m_trace_lock(m_trace_mutex);
|
||
if (m_ofs.is_open()) {
|
||
m_of.flush();
|
||
m_ofs.close();
|
||
}
|
||
}
|
||
|
||
private:
|
||
friend class SingletonHolder<Logger>;
|
||
|
||
Logger() :
|
||
m_directory(UserDir.get()),
|
||
m_buff(nullptr),
|
||
m_of(&m_buff)
|
||
{
|
||
try {
|
||
std::filesystem::create_directories(m_log_path.parent_path());
|
||
}
|
||
catch (std::filesystem::filesystem_error& e) {
|
||
std::cerr << e.what() << std::endl;
|
||
}
|
||
catch (...) {
|
||
}
|
||
|
||
LoadFileStream();
|
||
log_init_info();
|
||
}
|
||
|
||
void rotate()
|
||
{
|
||
if (!m_of || !m_ofs || !m_ofs.is_open()) {
|
||
LoadFileStream();
|
||
}
|
||
if (m_file_size + m_buff.count_bytes() <= MaxLogSize) {
|
||
return;
|
||
}
|
||
try {
|
||
if (!std::filesystem::exists(m_log_path) || !std::filesystem::is_regular_file(m_log_path)) {
|
||
return;
|
||
}
|
||
m_buff.close();
|
||
m_ofs.close();
|
||
std::filesystem::rename(m_log_path, m_log_bak_path);
|
||
}
|
||
catch (std::filesystem::filesystem_error& e) {
|
||
std::cerr << e.what() << std::endl;
|
||
}
|
||
catch (...) {
|
||
}
|
||
LoadFileStream();
|
||
}
|
||
|
||
void LoadFileStream()
|
||
{
|
||
m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app);
|
||
m_file_size = std::filesystem::file_size(m_log_path);
|
||
m_buff = LogStreambuf(m_ofs.rdbuf());
|
||
m_of.rdbuf(&m_buff);
|
||
}
|
||
|
||
void log_init_info()
|
||
{
|
||
trace("-----------------------------");
|
||
trace("MaaCore Process Start");
|
||
trace("Version", asst::Version);
|
||
trace("Built at", __DATE__, __TIME__);
|
||
trace("User Dir", m_directory);
|
||
trace("-----------------------------");
|
||
}
|
||
|
||
detail::scope_slice m_scopes;
|
||
|
||
std::filesystem::path m_directory;
|
||
|
||
std::filesystem::path m_log_path = m_directory / "debug" / "asst.log";
|
||
std::filesystem::path m_log_bak_path = m_directory / "debug" / "asst.bak.log";
|
||
std::mutex m_trace_mutex;
|
||
std::ofstream m_ofs;
|
||
LogStreambuf m_buff;
|
||
std::ostream m_of;
|
||
std::size_t m_file_size = 0;
|
||
const std::size_t MaxLogSize = 64LL * 1024 * 1024;
|
||
};
|
||
|
||
inline constexpr Logger::separator Logger::separator::none;
|
||
inline constexpr Logger::separator Logger::separator::space(" ");
|
||
inline constexpr Logger::separator Logger::separator::tab("\t");
|
||
inline constexpr Logger::separator Logger::separator::newline("\n");
|
||
inline constexpr Logger::separator Logger::separator::comma(",");
|
||
|
||
inline Logger::level Logger::level::debug("DBG");
|
||
inline Logger::level Logger::level::trace("TRC");
|
||
inline Logger::level Logger::level::info("INF");
|
||
inline Logger::level Logger::level::warn("WRN");
|
||
inline Logger::level Logger::level::error("ERR");
|
||
|
||
class LoggerAux
|
||
{
|
||
public:
|
||
explicit LoggerAux(std::string_view func_name) :
|
||
m_func_name(func_name),
|
||
m_start_time(std::chrono::steady_clock::now())
|
||
{
|
||
#ifdef ASST_DEBUG
|
||
m_id = Logger::get_instance().push
|
||
#else
|
||
Logger::get_instance().trace
|
||
#endif
|
||
(m_func_name, "| enter");
|
||
}
|
||
|
||
~LoggerAux()
|
||
{
|
||
const auto duration = std::chrono::steady_clock::now() - m_start_time;
|
||
#ifdef ASST_DEBUG
|
||
Logger::get_instance().pop(
|
||
m_id,
|
||
#else
|
||
Logger::get_instance().trace(
|
||
#endif
|
||
m_func_name,
|
||
"| leave,",
|
||
std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(),
|
||
"ms");
|
||
}
|
||
|
||
LoggerAux(const LoggerAux&) = default;
|
||
LoggerAux(LoggerAux&&) = default;
|
||
LoggerAux& operator=(const LoggerAux&) = default;
|
||
LoggerAux& operator=(LoggerAux&&) = default;
|
||
|
||
private:
|
||
std::string m_func_name;
|
||
std::chrono::time_point<std::chrono::steady_clock> m_start_time;
|
||
int m_id [[maybe_unused]] = -1;
|
||
};
|
||
|
||
#define _Cat_(a, b) a##b
|
||
#define _Cat(a, b) _Cat_(a, b)
|
||
#define _CatVarNameWithLine(Var) _Cat(Var, __LINE__)
|
||
|
||
#define Log asst::Logger::get_instance()
|
||
#define LogDebug Log << asst::Logger::level::debug
|
||
#define LogTrace Log << asst::Logger::level::trace
|
||
#define LogInfo Log << asst::Logger::level::info
|
||
#define LogWarn Log << asst::Logger::level::warn
|
||
#define LogError Log << asst::Logger::level::error
|
||
|
||
#define LogTraceScope LoggerAux _CatVarNameWithLine(_func_aux_)
|
||
|
||
#ifndef _MSC_VER
|
||
inline constexpr std::string_view summarize_pretty_function(std::string_view pf) // can be consteval?
|
||
{
|
||
// unable to handle something like std::function<void(void)> gen_func()
|
||
const auto paren = pf.find_first_of('(');
|
||
if (paren != std::string_view::npos) {
|
||
pf.remove_suffix(pf.size() - paren);
|
||
}
|
||
const auto space = pf.find_last_of(' ');
|
||
if (space != std::string_view::npos) {
|
||
pf.remove_prefix(space + 1);
|
||
}
|
||
return pf;
|
||
}
|
||
|
||
// TODO: use std::source_location
|
||
#define LogTraceFunction \
|
||
LogTraceScope \
|
||
{ \
|
||
::asst::summarize_pretty_function(__PRETTY_FUNCTION__) \
|
||
}
|
||
#else
|
||
#define LogTraceFunction LogTraceScope(__FUNCTION__)
|
||
#endif
|
||
|
||
#define VAR_RAW(x) "[" << #x << "=" << (x) << "] "
|
||
#define VAR(x) Logger::separator::none << VAR_RAW(x) << Logger::separator::space
|
||
#define VAR_VOIDP_RAW(x) "[" << #x << "=" << reinterpret_cast<void*>(x) << "] "
|
||
#define VAR_VOIDP(x) Logger::separator::none << VAR_VOIDP_RAW(x) << Logger::separator::space
|
||
|
||
#define LogTraceFunctionWithArgs // how to do this?, like LogTraceScope(__FUNCTION__,
|
||
// __FUNCTION_ALL_ARGS__)
|
||
} // namespace asst
|