Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/MaaUtils/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -57,6 +62,11 @@ class MAA_UTILS_API Logger
return stream(level::trace, std::forward<args_t>(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();
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)()
Expand Down
44 changes: 44 additions & 0 deletions include/MaaUtils/LoggerUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Comment thread
weypro marked this conversation as resolved.
}
}

template <typename T>
requires has_output_operator<T>
PerfLogStream& operator<<(T&& value)
{
buffer_ << std::forward<T>(value);
return *this;
}

private:
std::mutex& mutex_;
std::ofstream& stream_;
std::ostringstream buffer_;
};

MAA_LOG_NS_END
102 changes: 92 additions & 10 deletions source/Logger/Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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);
Comment thread
weypro marked this conversation as resolved.
#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