Skip to content
Merged
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
7 changes: 6 additions & 1 deletion include/MaaUtils/Logger.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <future>

#include "MaaUtils/LoggerUtils.h"
#include "MaaUtils/ScopeLeave.hpp"

Expand Down Expand Up @@ -60,6 +62,7 @@ class MAA_UTILS_API Logger
void start_logging(std::filesystem::path dir);
void set_stdout_level(level lv);
void flush();
void cleanup(bool force = false);

private:
template <typename... args_t>
Expand All @@ -75,7 +78,6 @@ class MAA_UTILS_API Logger
Logger() = default;

void reinit();
void cleanup();
bool rotate();
void open(bool append = true);
void close();
Expand All @@ -97,6 +99,9 @@ class MAA_UTILS_API Logger
std::mutex trace_mutex_;

size_t log_count_ = 0;
std::mutex cleanup_mutex_;
std::chrono::steady_clock::time_point last_cleanup_ {};
std::future<void> cleanup_task_;
};

class LogScopeEnterHelper
Expand Down
2 changes: 1 addition & 1 deletion source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ file(GLOB_RECURSE maa_utils_header ${MAAUTILS_DIR}/include/MaaUtils/*)

add_library(MaaUtils SHARED ${maa_utils_src} ${maa_utils_header})
target_include_directories(MaaUtils PRIVATE ${MAAUTILS_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(MaaUtils PUBLIC Boost::regex PRIVATE Boost::system ${OpenCV_LIBS})
target_link_libraries(MaaUtils PUBLIC Boost::regex PRIVATE Boost::system ${OpenCV_LIBS} ZLIB::ZLIB)

if(WIN32)
target_link_libraries(MaaUtils PRIVATE d3d12 dxgi Cfgmgr32)
Expand Down
230 changes: 230 additions & 0 deletions source/Logger/LogMaintenance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include "LogMaintenance.h"

#include <algorithm>
#include <array>
#include <fstream>
#include <memory>
#include <system_error>
#include <vector>

#include <zlib.h>

#include "MaaUtils/Platform.h"
#include "MaaUtils/ScopeLeave.hpp"
#include "MaaUtils/Time.hpp"

#ifdef _WIN32
#include "MaaUtils/SafeWindows.hpp"
#endif

MAA_LOG_NS_BEGIN

namespace
{

enum class ArtifactKind
{
Vision,
Log,
Error,
};

struct LogArtifact
{
std::filesystem::path path;
std::filesystem::file_time_type modified;
uintmax_t size = 0;
ArtifactKind kind = ArtifactKind::Log;
};

#ifdef _WIN32
struct MaintenanceTask
{
std::packaged_task<void()> run;
HMODULE library = nullptr;
};

void CALLBACK run_maintenance(PTP_CALLBACK_INSTANCE instance, void* context)
{
std::unique_ptr<MaintenanceTask> task(static_cast<MaintenanceTask*>(context));
FreeLibraryWhenCallbackReturns(instance, task->library);
task->run();
}
#endif

bool compress_log(LogArtifact& artifact)
{
auto archive = artifact.path;
archive += ".gz";
auto temporary = archive;
temporary += "." + format_now_for_filename() + ".tmp";

std::error_code ec;
if (std::filesystem::exists(archive, ec) || ec) {
return false;
}
std::ifstream input(artifact.path, std::ios::binary);
if (!input) {
return false;
}
#ifdef _WIN32
gzFile output = gzopen_w(temporary.c_str(), "wb6x");
#else
gzFile output = gzopen(temporary.c_str(), "wb6x");
#endif
if (!output) {
return false;
}
OnScopeLeave([&] {
if (output) {
gzclose(output);
}
std::filesystem::remove(temporary, ec);
});

std::array<char, 64 * 1024> buffer;
while (input) {
input.read(buffer.data(), buffer.size());
auto size = static_cast<unsigned int>(input.gcount());
if (size && gzwrite(output, buffer.data(), size) != static_cast<int>(size)) {
return false;
}
}
if (!input.eof()) {
return false;
}
input.close();
int result = gzclose(output);
output = nullptr;
if (result != Z_OK) {
return false;
}

// Do not replace a backup which changed while it was being read.
if (std::filesystem::file_size(artifact.path, ec) != artifact.size || ec
|| std::filesystem::last_write_time(artifact.path, ec) != artifact.modified || ec) {
return false;
}
auto compressed_size = std::filesystem::file_size(temporary, ec);
if (ec) {
return false;
}
// Retention is based on the original log's age, not the compression time.
std::filesystem::last_write_time(temporary, artifact.modified, ec);
if (ec) {
return false;
}
std::filesystem::rename(temporary, archive, ec);
if (ec) {
return false;
}
if (!std::filesystem::remove(artifact.path, ec) || ec) {
return false;
}
artifact.path = std::move(archive);
artifact.size = compressed_size;
return true;
}

} // namespace

void maintain_log_files(const std::filesystem::path& dir)
{
constexpr auto kMaxAge = std::chrono::hours(24 * 7);
constexpr auto kVisionMaxAge = std::chrono::hours(24 * 3);
constexpr uintmax_t kMaxBytes = 1024ULL * 1024 * 1024;
const auto now = std::filesystem::file_time_type::clock::now();
std::vector<LogArtifact> artifacts;

std::error_code ec;
auto iter = std::filesystem::recursive_directory_iterator(dir, std::filesystem::directory_options::skip_permission_denied, ec);
const auto end = std::filesystem::recursive_directory_iterator();
for (; !ec && iter != end; iter.increment(ec)) {
const auto& entry = *iter;
if (!std::filesystem::is_regular_file(entry.symlink_status(ec)) || ec) {
ec.clear();
continue;
}
auto last_write = entry.last_write_time(ec);
if (ec) {
ec.clear();
continue;
}
auto relative = entry.path().lexically_relative(dir);
const auto name = path_to_utf8_string(relative.filename());
const auto ext = relative.extension();
const bool backup = relative.parent_path().empty() && name.starts_with("maafw.bak.");
ArtifactKind kind;
if (backup && (name.ends_with(".log") || name.ends_with(".log.gz"))) {
kind = ArtifactKind::Log;
}
else if ((ext == ".png" || ext == ".jpg") && *relative.begin() == "vision") {
kind = ArtifactKind::Vision;
}
else if ((ext == ".png" || ext == ".jpg") && *relative.begin() == "on_error") {
kind = ArtifactKind::Error;
}
else {
if (backup && name.ends_with(".tmp") && now - last_write > kMaxAge) {
std::filesystem::remove(entry.path(), ec);
}
ec.clear();
continue;
}
auto max_age = kind == ArtifactKind::Vision ? kVisionMaxAge : kMaxAge;
if (now - last_write > max_age && std::filesystem::remove(entry.path(), ec)) {
continue;
}
ec.clear();
auto size = entry.file_size(ec);
if (!ec) {
artifacts.push_back({ entry.path(), last_write, size, kind });
}
ec.clear();
}

uintmax_t total_size = 0;
for (auto& artifact : artifacts) {
if (artifact.kind == ArtifactKind::Log && artifact.path.extension() == ".log") {
compress_log(artifact);
}
total_size += artifact.size;
}
// Discard vision drawings first; retain failure screenshots for as long as possible.
std::ranges::sort(artifacts, [](const LogArtifact& lhs, const LogArtifact& rhs) {
return lhs.kind != rhs.kind ? lhs.kind < rhs.kind : lhs.modified < rhs.modified;
});
for (const auto& artifact : artifacts) {
if (total_size <= kMaxBytes) {
break;
}
if (std::filesystem::remove(artifact.path, ec)) {
total_size -= artifact.size;
}
}
}

std::future<void> start_log_maintenance(const std::filesystem::path& dir)
{
#ifdef _WIN32
// DLL teardown must not wait for workers that Windows terminates at process exit.
// Keep MaaUtils loaded until the callback returns during normal library unloading.
auto task = std::make_unique<MaintenanceTask>();
task->run = std::packaged_task<void()>([dir] { maintain_log_files(dir); });
auto future = task->run.get_future();
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<LPCWSTR>(&run_maintenance), &task->library)) {
throw std::system_error(GetLastError(), std::system_category(), "Hold MaaUtils for log maintenance");
}
if (!TrySubmitThreadpoolCallback(run_maintenance, task.get(), nullptr)) {
auto error = GetLastError();
FreeLibrary(task->library);
throw std::system_error(error, std::system_category(), "Start log maintenance");
}
task.release();
return future;
#else
return std::async(std::launch::async, maintain_log_files, dir);
#endif
}

MAA_LOG_NS_END
13 changes: 13 additions & 0 deletions source/Logger/LogMaintenance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <filesystem>
#include <future>

#include "MaaUtils/Conf.h"

MAA_LOG_NS_BEGIN

void maintain_log_files(const std::filesystem::path& dir);
std::future<void> start_log_maintenance(const std::filesystem::path& dir);

MAA_LOG_NS_END
Loading