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
83 changes: 83 additions & 0 deletions include/logit_cpp/logit/Logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,89 @@ namespace logit {
}
}

/// \brief Clears logger-owned records for a specific logger.
/// \param logger_index Index of logger.
/// \param options Data categories to clear.
/// \return Cleanup result for the selected logger.
LogClearResult clear_logger(int logger_index, const LogClearOptions& options = LogClearOptions()) {
LogClearResult result;
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "logger system is shut down";
return result;
}

auto strategy = get_strategy_snapshot(logger_index);
if (!strategy) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "logger index not found";
return result;
}

std::lock_guard<std::mutex> exec_lock(strategy->exec_mx);
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "logger system is shut down";
return result;
}
return strategy->logger->clear_logs(options);
}

/// \brief Clears logger-owned records for all registered loggers.
/// \param options Data categories to clear.
/// \return Aggregated cleanup result. Unsupported loggers are skipped.
LogClearResult clear_all_loggers(const LogClearOptions& options = LogClearOptions()) {
LogClearResult result;
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "logger system is shut down";
return result;
}

const auto snapshot = get_all_strategy_snapshots();
std::size_t supported = 0;
std::size_t failed = 0;
std::size_t unsupported = 0;
for (const auto& strategy : snapshot) {
if (!strategy) continue;
std::lock_guard<std::mutex> exec_lock(strategy->exec_mx);
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "logger system is shut down";
return result;
}
const LogClearResult one = strategy->logger->clear_logs(options);
if (one.status == LogClearStatus::Cleared && one.ok) {
++supported;
result.cleared_records += one.cleared_records;
} else if (one.status == LogClearStatus::Unsupported) {
++unsupported;
} else {
++failed;
if (result.message.empty()) {
result.message = one.message;
}
}
}

result.ok = supported > 0 && failed == 0;
if (result.ok) {
result.status = LogClearStatus::Cleared;
result.message = unsupported > 0 ? "cleared supported loggers; skipped unsupported loggers" : "cleared";
} else {
result.status = unsupported > 0 && failed == 0 ? LogClearStatus::Unsupported : LogClearStatus::Failed;
if (result.message.empty()) {
result.message = unsupported > 0 ? "no logger supports clearing" : "no loggers to clear";
}
}
return result;
}

/// \brief Returns the number of registered logger strategies.
std::size_t logger_count() const {
LoggerReadLock lock(m_loggers_mx);
Expand Down
24 changes: 24 additions & 0 deletions include/logit_cpp/logit/log_macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2812,6 +2812,30 @@ static_assert(LOGIT_LEVEL_FATAL == static_cast<int>(logit::LogLevel::LOG_LVL_FAT
#define LOGIT_READ_LOG_FILES(logger_index, paths) \
logit::Logger::get_instance().read_log_files(logger_index, paths)

/// \brief Clears logger-owned records for a specific logger.
/// \param logger_index Index of logger.
/// \return Cleanup result for the selected logger.
#define LOGIT_CLEAR_LOGGER(logger_index) \
logit::Logger::get_instance().clear_logger(logger_index)

/// \brief Clears logger-owned records for a specific logger with options.
/// \param logger_index Index of logger.
/// \param options LogClearOptions value.
/// \return Cleanup result for the selected logger.
#define LOGIT_CLEAR_LOGGER_EX(logger_index, options) \
logit::Logger::get_instance().clear_logger(logger_index, options)

/// \brief Clears logger-owned records for all registered loggers.
/// \return Aggregated cleanup result.
#define LOGIT_CLEAR_ALL_LOGGERS() \
logit::Logger::get_instance().clear_all_loggers()

/// \brief Clears logger-owned records for all registered loggers with options.
/// \param options LogClearOptions value.
/// \return Aggregated cleanup result.
#define LOGIT_CLEAR_ALL_LOGGERS_EX(options) \
logit::Logger::get_instance().clear_all_loggers(options)

/// \brief Enables or disables a logger.
/// \param logger_index Index of logger.
/// \param enabled True to enable, false to disable.
Expand Down
53 changes: 53 additions & 0 deletions include/logit_cpp/logit/loggers/FileLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,51 @@ namespace logit {
return results;
}

/// \brief Clears managed log files and reopens the current log file.
LogClearResult clear_logs(const LogClearOptions& options = LogClearOptions()) override {
(void)options;
wait();

std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
LogClearResult result;
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "FileLogger is shut down";
return result;
}

try {
std::lock_guard<std::mutex> lock(m_mutex);
const std::vector<LogFileInfo> files = list_log_files();
if (m_file.is_open()) {
m_file.flush();
m_file.close();
}
for (size_t i = 0; i < files.size(); ++i) {
if (remove_file_path(files[i].path)) {
++result.cleared_records;
}
}
m_current_file_size = 0;
m_last_log_ts.store(0, std::memory_order_release);
m_last_log_mono_ts.store(0, std::memory_order_release);
open_log_file(get_current_utc_date_ts());
result.ok = true;
result.status = LogClearStatus::Cleared;
result.message = "cleared";
} catch (const std::exception& e) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = std::string("FileLogger clear error: ") + e.what();
} catch (...) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "FileLogger clear error";
}
return result;
}

/// \brief Waits for all asynchronous tasks to complete.
void wait() override {
std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
Expand Down Expand Up @@ -660,6 +705,14 @@ namespace logit {
return !in.bad();
}

bool remove_file_path(const std::string& file_path) const {
# if defined(_WIN32)
return std::remove(utf8_to_ansi(file_path).c_str()) == 0;
# else
return std::remove(file_path.c_str()) == 0;
# endif
}

void sort_log_files_newest_first(std::vector<LogFileInfo>& files) const {
std::sort(files.begin(), files.end(), [this](const LogFileInfo& lhs, const LogFileInfo& rhs) {
if (lhs.day_start_ms != rhs.day_start_ms) {
Expand Down
38 changes: 38 additions & 0 deletions include/logit_cpp/logit/loggers/ILogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,37 @@
/// \ingroup LogBackends Logging Backends
/// \{

#include <cstddef>
#include <string>
#include <vector>

namespace logit {

/// \struct LogClearOptions
/// \brief Selects which data categories a logger cleanup should remove.
struct LogClearOptions {
bool include_persistent_records = true; ///< Clear primary stored log records.
bool include_payloads = true; ///< Clear associated payload/blob data.
bool include_sessions = false; ///< Clear session metadata when supported.
};

/// \enum LogClearStatus
/// \brief Backend cleanup status.
enum class LogClearStatus {
Cleared,
Unsupported,
Failed
};

/// \struct LogClearResult
/// \brief Result returned by logger cleanup operations.
struct LogClearResult {
bool ok = false; ///< True when the backend completed cleanup.
LogClearStatus status = LogClearStatus::Failed; ///< Detailed cleanup status.
std::size_t cleared_records = 0; ///< Number of primary records/files cleared when available.
std::string message; ///< Optional diagnostic or unsupported reason.
};

/// \brief Optional buffered snapshot access returns empty results by default.
/// Custom loggers may override these methods when they keep recent history.

Expand Down Expand Up @@ -108,6 +134,18 @@ namespace logit {
return results;
}

/// \brief Clears logger-owned buffered or persisted records when supported.
/// \param options Data categories to clear.
/// \return Cleanup result. Default reports unsupported.
virtual LogClearResult clear_logs(const LogClearOptions& options = LogClearOptions()) {
(void)options;
LogClearResult result;
result.ok = false;
result.status = LogClearStatus::Unsupported;
result.message = "unsupported";
return result;
}

/// \brief Waits for all asynchronous logging operations to complete.
///
/// This pure virtual function must be implemented by derived logger classes.
Expand Down
51 changes: 51 additions & 0 deletions include/logit_cpp/logit/loggers/MdbxLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,57 @@ namespace logit {
return static_cast<LogLevel>(m_log_level.load(std::memory_order_acquire));
}

LogClearResult clear_logs(const LogClearOptions& options = LogClearOptions()) override {
wait();

LogClearResult result;
try {
{
std::lock_guard<std::mutex> db_lock(m_db_mutex);
auto txn = m_connection->transaction(mdbxc::TransactionMode::WRITABLE);
if (options.include_persistent_records) {
result.cleared_records = m_records->count(txn);
m_records->clear(txn);
}
if (options.include_payloads) {
m_payloads->clear(txn);
}
if (options.include_sessions) {
m_sessions->clear(txn);
Session session;
session.app_name = m_config.app_name;
session.start_time_ms = LOGIT_CURRENT_TIMESTAMP_MS();
session.end_time_ms = 0;
session.process_id = detail::current_process_id();
session.schema_version = 1;
m_session_id = make_unique_id();
m_sessions->insert_or_assign(m_session_id, session, txn);
}
txn.commit();
}

{
std::lock_guard<std::mutex> lock(m_mutex);
m_next_sequence_by_timestamp.clear();
}
m_last_log_ts.store(0, std::memory_order_release);
m_last_log_mono_ts.store(0, std::memory_order_release);

result.ok = true;
result.status = LogClearStatus::Cleared;
result.message = "cleared";
} catch (const std::exception& e) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = std::string("MdbxLogger clear error: ") + e.what();
} catch (...) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "MdbxLogger clear error";
}
return result;
}

uint64_t dropped_count() const {
return m_dropped.load(std::memory_order_acquire);
}
Expand Down
16 changes: 16 additions & 0 deletions include/logit_cpp/logit/loggers/MemoryLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@ namespace logit {
/// \brief Memory logger is synchronous, so no flush step is needed.
void wait() override {}

/// \brief Clears buffered entries.
LogClearResult clear_logs(const LogClearOptions& options = LogClearOptions()) override {
(void)options;
std::lock_guard<std::mutex> lock(m_mutex);
LogClearResult result;
result.ok = true;
result.status = LogClearStatus::Cleared;
result.cleared_records = m_entries.size();
result.message = "cleared";
m_entries.clear();
m_total_bytes = 0;
m_last_log_ts.store(0, std::memory_order_relaxed);
m_last_log_mono_ts.store(0, std::memory_order_relaxed);
return result;
}

/// \brief Reads records in `[from_ms, to_ms)` ordered by timestamp.
std::vector<LogRecordView> read_range(
int64_t from_ms,
Expand Down
51 changes: 51 additions & 0 deletions include/logit_cpp/logit/loggers/UniqueFileLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,49 @@ namespace logit {
}
}

/// \brief Clears managed unique log files.
LogClearResult clear_logs(const LogClearOptions& options = LogClearOptions()) override {
(void)options;
wait();

std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
LogClearResult result;
if (m_shutdown.load(std::memory_order_acquire)) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "UniqueFileLogger is shut down";
return result;
}

try {
std::lock_guard<std::mutex> lock(m_mutex);
const std::vector<LogFileInfo> files = list_log_files();
for (size_t i = 0; i < files.size(); ++i) {
if (remove_file_path(files[i].path)) {
++result.cleared_records;
}
}
{
std::lock_guard<std::mutex> info_lock(m_thread_log_info_mutex);
m_thread_log_info.clear();
}
m_last_log_ts.store(0, std::memory_order_release);
m_last_log_mono_ts.store(0, std::memory_order_release);
result.ok = true;
result.status = LogClearStatus::Cleared;
result.message = "cleared";
} catch (const std::exception& e) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = std::string("UniqueFileLogger clear error: ") + e.what();
} catch (...) {
result.ok = false;
result.status = LogClearStatus::Failed;
result.message = "UniqueFileLogger clear error";
}
return result;
}

/// \brief Stops logger-owned asynchronous resources after draining pending writes.
void shutdown() override {
{
Expand Down Expand Up @@ -578,6 +621,14 @@ namespace logit {
return !in.bad();
}

bool remove_file_path(const std::string& file_path) const {
# if defined(_WIN32)
return std::remove(utf8_to_ansi(file_path).c_str()) == 0;
# else
return std::remove(file_path.c_str()) == 0;
# endif
}

bool is_direct_child_path(const std::string& file_path, const std::string& directory_path) const {
# if __cplusplus >= 201703L
# if defined(_WIN32)
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ else()
log_filters_tags_test.cpp
logger_shutdown_race_test.cpp
logger_snapshot_read_path_test.cpp
logger_clear_api_test.cpp
memory_logger_backend_test.cpp
memory_logger_concurrency_test.cpp
memory_logger_integration_test.cpp
Expand Down
Loading
Loading