Files
MaaAssistantArknights/src/MaaCore/Utils/Logger.hpp

916 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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