diff --git a/include/MaaUtils/Logger.h b/include/MaaUtils/Logger.h index 9efe6ff..f6d39ad 100644 --- a/include/MaaUtils/Logger.h +++ b/include/MaaUtils/Logger.h @@ -11,6 +11,11 @@ class MAA_UTILS_API Logger static constexpr std::string_view kLogFilename = "maafw.log"; static constexpr std::string_view kLogbakFilename = "maafw.bak.{}.log"; + // perf trace 通道(独立文件,独立锁,无 Logger 默认前缀,纯 CSV 行) + static constexpr std::string_view kPerfTraceSubdir = "perf"; + static constexpr std::string_view kPerfTraceFilenameFormat = "maafw_perf_trace_{}.csv"; + static constexpr std::string_view kPerfTraceCsvHeader = "ts_us,tid,scope,name,extra,elapsed_us"; + public: static Logger& get_instance(); @@ -57,6 +62,11 @@ class MAA_UTILS_API Logger return stream(level::trace, std::forward(args)...); } + // perf trace 通道入口。返回的 PerfLogStream 不带任何 Logger 默认前缀, + // 调用方负责把整行 CSV 文本拼好。析构时一次性原子追加一行到 perf 文件。 + // perf 文件随 start_logging() 打开;若日志目录未设置,当前写入会静默丢弃。 + PerfLogStream perf_stream(); + void start_logging(std::filesystem::path dir); void set_stdout_level(level lv); void flush(); @@ -82,6 +92,11 @@ class MAA_UTILS_API Logger void log_proc_info(); void count_and_check_flush(); + // perf 通道相关(实现见 Logger.cpp) + void perf_reset_locked(); + void perf_try_open_locked(); + void perf_close_locked(); + LogStream internal_dbg(); private: @@ -97,6 +112,12 @@ class MAA_UTILS_API Logger std::mutex trace_mutex_; size_t log_count_ = 0; + + // perf 通道:与主日志完全分离 + std::mutex perf_init_mutex_; // 保护 perf 初始化状态及 log_dir_ 切换 + std::mutex perf_mutex_; // 保护对 perf_ofs_ 的实际写入 + std::ofstream perf_ofs_; + bool perf_initialized_ = false; }; class LogScopeEnterHelper @@ -164,6 +185,10 @@ MAA_LOG_NS_END #define LogDebug MAA_LOG_NS::Logger::get_instance().debug(LOG_ARGS) #define LogTrace MAA_LOG_NS::Logger::get_instance().trace(LOG_ARGS) +// perf 通道入口。无任何前缀,调用方拼好整行 CSV 文本即可。 +// 此宏在所有构建中都存在,供 MaaFramework 的 perf trace 功能按需调用。 +#define LogPerf MAA_LOG_NS::Logger::get_instance().perf_stream() + #define LogFunc \ MAA_LOG_NS::LogScopeLeaveHelper ScopeHelperVarName(LOG_ARGS); \ MAA_LOG_NS::LogScopeEnterHelper(LOG_ARGS)() diff --git a/include/MaaUtils/LoggerUtils.h b/include/MaaUtils/LoggerUtils.h index bfe6452..5a8daef 100644 --- a/include/MaaUtils/LoggerUtils.h +++ b/include/MaaUtils/LoggerUtils.h @@ -157,4 +157,48 @@ class MAA_UTILS_API LogStream std::stringstream buffer_; }; +// perf trace 通道专用流。 +// +// 跟 LogStream 的关键差别: +// 1. 不写任何默认前缀 (无 timestamp/level/pid/tid 头部),调用方完全控制行内容 +// 2. 不写 stdout +// 3. 不做 json 特殊化处理,纯粹 ostream 风格拼接 +// 4. 使用独立的 perf 文件 + 独立的 mutex,跟主日志互不干扰 +// +// 析构时把 buffer 一次性原子追加一行到 perf 文件 (含尾部换行)。 +// 若 perf 文件未打开 (例如 log_dir 尚未配置),析构静默丢弃,不抛错。 +class MAA_UTILS_API PerfLogStream +{ +public: + PerfLogStream(std::mutex& m, std::ofstream& s) + : mutex_(m) + , stream_(s) + { + } + + PerfLogStream(const PerfLogStream&) = delete; + PerfLogStream(PerfLogStream&&) = delete; + + ~PerfLogStream() + { + std::unique_lock lock(mutex_); + if (stream_.is_open()) { + stream_ << buffer_.str() << '\n'; + } + } + + template + requires has_output_operator + PerfLogStream& operator<<(T&& value) + { + buffer_ << std::forward(value); + return *this; + } + +private: + std::mutex& mutex_; + std::ofstream& stream_; + std::ostringstream buffer_; +}; + MAA_LOG_NS_END diff --git a/source/Logger/Logger.cpp b/source/Logger/Logger.cpp index 61c16d7..8ba3d83 100644 --- a/source/Logger/Logger.cpp +++ b/source/Logger/Logger.cpp @@ -84,13 +84,21 @@ Logger& Logger::get_instance() void Logger::start_logging(std::filesystem::path dir) { - log_dir_ = std::move(dir); - if (log_dir_.empty()) { - log_path_.clear(); - } - else { - log_path_ = log_dir_ / kLogFilename; + { + std::unique_lock init_lock(perf_init_mutex_); + + log_dir_ = std::move(dir); + if (log_dir_.empty()) { + log_path_.clear(); + } + else { + log_path_ = log_dir_ / kLogFilename; + } + + perf_reset_locked(); + perf_try_open_locked(); } + reinit(); } @@ -132,7 +140,7 @@ static void remove_old_files(const std::filesystem::path& dir) } const auto ext = path_to_utf8_string(entry.path().extension()); - if (ext != ".log" && ext != ".jpg" && ext != ".png") { + if (ext != ".log" && ext != ".jpg" && ext != ".png" && ext != ".csv") { continue; } @@ -215,9 +223,15 @@ void Logger::close() internal_dbg() << "Close log"; internal_dbg() << kSplitLine; - std::unique_lock trace_lock(trace_mutex_); - if (ofs_.is_open()) { - ofs_.close(); + { + std::unique_lock trace_lock(trace_mutex_); + if (ofs_.is_open()) { + ofs_.close(); + } + } + { + std::unique_lock init_lock(perf_init_mutex_); + perf_close_locked(); } } @@ -282,4 +296,72 @@ LogStream Logger::internal_dbg() return debug("Logger"); } +PerfLogStream Logger::perf_stream() +{ + std::unique_lock init_lock(perf_init_mutex_); + // 注意: 此处返回的 PerfLogStream 析构时会拿 perf_mutex_ 写。 + // 若 perf_ofs_ 没打开(log_dir 空,或打开失败),PerfLogStream 析构静默丢弃。 + return PerfLogStream(perf_mutex_, perf_ofs_); +} + +void Logger::perf_reset_locked() +{ + // 切换 log_dir 时重置 perf 通道,随后由 start_logging() 按新目录重开 + perf_close_locked(); + perf_initialized_ = false; +} + +void Logger::perf_try_open_locked() +{ + if (perf_initialized_) { + return; + } + if (log_dir_.empty()) { + return; + } + + std::error_code ec; + const std::filesystem::path perf_dir = log_dir_ / kPerfTraceSubdir; + std::filesystem::create_directories(perf_dir, ec); + if (ec) { + return; + } + + const auto filename = std::format(kPerfTraceFilenameFormat, format_now_for_filename()); + const std::filesystem::path perf_path = perf_dir / filename; + + { + std::unique_lock perf_lock(perf_mutex_); + if (perf_ofs_.is_open()) { + perf_ofs_.close(); + } +#ifdef _WIN32 + std::string str_path = perf_path.string(); + FILE* fp = fopen(str_path.c_str(), "w"); + if (!fp) { + return; + } + SetHandleInformation((HANDLE)_get_osfhandle(_fileno(fp)), HANDLE_FLAG_INHERIT, 0); + perf_ofs_ = std::ofstream(fp); +#else + perf_ofs_ = std::ofstream(perf_path, std::ios::out | std::ios::trunc); +#endif + if (!perf_ofs_.is_open()) { + return; + } + perf_ofs_ << kPerfTraceCsvHeader << '\n'; + } + + perf_initialized_ = true; +} + +void Logger::perf_close_locked() +{ + std::unique_lock perf_lock(perf_mutex_); + if (perf_ofs_.is_open()) { + perf_ofs_.flush(); + perf_ofs_.close(); + } +} + MAA_LOG_NS_END