#pragma once #include #include #include #include #include #include #include #include "AsstRanges.hpp" #include "AsstUtils.hpp" #include "SingletonHolder.hpp" #include "Version.h" namespace asst { class Logger : public SingletonHolder { 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 inline void debug([[maybe_unused]] Args&&... args) { #ifdef ASST_DEBUG std::string_view level = "DEB"; log(level, std::forward(args)...); #endif } template inline void trace(Args&&... args) { std::string_view level = "TRC"; log(level, std::forward(args)...); } template inline void info(Args&&... args) { std::string_view level = "INF"; log(level, std::forward(args)...); } template inline void warn(Args&&... args) { std::string_view level = "WRN"; log(level, std::forward(args)...); } template inline void error(Args&&... args) { std::string_view level = "ERR"; log(level, std::forward(args)...); } template inline void log_with_custom_level(std::string_view level, Args&&... args) { log(level, std::forward(args)...); } void flush() { std::unique_lock trace_lock(m_trace_mutex); if (m_ofs.is_open()) { m_ofs.close(); } } protected: friend class SingletonHolder; Logger() { check_filesize_and_remove(); log_init_info(); } private: void check_filesize_and_remove() const { constexpr uintmax_t MaxLogSize = 4ULL * 1024 * 1024; try { if (std::filesystem::exists(m_log_path)) { const uintmax_t log_size = std::filesystem::file_size(m_log_path); if (log_size >= MaxLogSize) { std::filesystem::rename(m_log_path, m_log_bak_path); } } } catch (...) { } } void log_init_info() { trace("-----------------------------"); trace("MeoAssistant Process Start"); trace("Version", asst::Version); trace("Built at", __DATE__, __TIME__); trace("Working Path", m_directory); trace("-----------------------------"); } template void log(std::string_view level, Args&&... args) { std::unique_lock trace_lock(m_trace_mutex); 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%lx]", asst::utils::get_format_time().c_str(), level.data(), _getpid(), ::GetCurrentThreadId()); #else // ! _WIN32 sprintf(buff, "[%s][%s][Px%x][Tx%lx]", asst::utils::get_format_time().c_str(), level.data(), getpid(), (unsigned long)(std::hash {}(std::this_thread::get_id()))); #endif // END _WIN32 if (!m_ofs || !m_ofs.is_open()) { m_ofs = std::ofstream(m_log_path, std::ios::out | std::ios::app); } #ifdef ASST_DEBUG stream_put_line(m_ofs, buff, args...); #else stream_put_line(m_ofs, buff, std::forward(args)...); #endif #ifdef ASST_DEBUG stream_put_line(std::cout, buff, std::forward(args)...); #endif } template static constexpr bool has_stream_insertion_operator = false; template static constexpr bool has_stream_insertion_operator< Stream, T, std::void_t() << std::declval())>> = true; template static Stream& stream_put(Stream& s, std::filesystem::path&& v) { return stream_put(s, asst::utils::path_to_utf8_string(v)); } template static Stream& stream_put(Stream& s, T&& v) { if constexpr (std::is_constructible_v) { if constexpr (ToAnsi) s << utils::utf8_to_ansi(std::forward(v)); else s << std::string(std::forward(v)); return s; } else if constexpr (has_stream_insertion_operator) { s << std::forward(v); return s; } else if constexpr (ranges::input_range) { s << "["; std::string_view comma {}; for (const auto& elem : std::forward(v)) { s << comma; stream_put(s, elem); comma = ","; } s << "]"; return 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); } } template struct stream_put_line_impl; template struct stream_put_line_impl { static constexpr Stream& apply(Stream& s) { s << std::endl; return s; } }; template struct stream_put_line_impl { static constexpr Stream& apply(Stream& s, Only&& only) { stream_put(s, std::forward(only)); s << std::endl; return s; } }; template struct stream_put_line_impl { static constexpr Stream& apply(Stream& s, First&& f, Rest&&... rs) { stream_put(s, std::forward(f)); s << " "; stream_put_line_impl::apply(s, std::forward(rs)...); return s; } }; template Stream& stream_put_line(Stream& s, Args&&... args) { return stream_put_line_impl::apply(s, std::forward(args)...); } inline static std::filesystem::path m_directory; std::filesystem::path m_log_path = m_directory / "asst.log"; std::filesystem::path m_log_bak_path = m_directory / "asst.bak.log"; std::mutex m_trace_mutex; std::ofstream m_ofs; }; class LoggerAux { public: explicit LoggerAux(std::string func_name) : m_func_name(std::move(func_name)), m_start_time(std::chrono::steady_clock::now()) { Logger::get_instance().trace(m_func_name, "| enter"); } ~LoggerAux() { const auto duration = std::chrono::steady_clock::now() - m_start_time; Logger::get_instance().trace(m_func_name, "| leave,", std::chrono::duration_cast(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 m_start_time; }; #define _Cat_(a, b) a##b #define _Cat(a, b) _Cat_(a, b) #define _CatVarNameWithLine(Var) _Cat(Var, __LINE__) #define Log Logger::get_instance() #define LogTraceScope LoggerAux _CatVarNameWithLine(_func_aux_) #define LogTraceFunction LogTraceScope(__FUNCTION__) #define LogTraceFunctionWithArgs // how to do this?, like LogTraceScope(__FUNCTION__, __FUNCTION_ALL_ARGS__) } // namespace asst