From 265de6186d8c76876045cf7d7488f32d224cfec4 Mon Sep 17 00:00:00 2001 From: xxx <18797809093@163.com> Date: Sat, 1 Nov 2025 20:49:48 +0800 Subject: [PATCH] test --- example/CMakeLists.txt | 11 + include/spdlog/desensitizer-inl.h | 58 +++++ include/spdlog/desensitizer.h | 52 ++++ include/spdlog/details/registry.h | 3 + .../spdlog/formatters/json_formatter-inl.h | 89 +++++++ include/spdlog/formatters/json_formatter.h | 48 ++++ include/spdlog/log_aggregator-inl.h | 240 ++++++++++++++++++ include/spdlog/log_aggregator.h | 135 ++++++++++ include/spdlog/logger-inl.h | 53 +++- include/spdlog/logger.h | 23 +- include/spdlog/sinks/level_based_sink-inl.h | 134 ++++++++++ include/spdlog/sinks/level_based_sink.h | 73 ++++++ include/spdlog/spdlog.h | 13 + 13 files changed, 927 insertions(+), 5 deletions(-) create mode 100644 include/spdlog/desensitizer-inl.h create mode 100644 include/spdlog/desensitizer.h create mode 100644 include/spdlog/formatters/json_formatter-inl.h create mode 100644 include/spdlog/formatters/json_formatter.h create mode 100644 include/spdlog/log_aggregator-inl.h create mode 100644 include/spdlog/log_aggregator.h create mode 100644 include/spdlog/sinks/level_based_sink-inl.h create mode 100644 include/spdlog/sinks/level_based_sink.h diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index da1ed4e2..20581872 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -21,3 +21,14 @@ if(SPDLOG_BUILD_EXAMPLE_HO) add_executable(example_header_only example.cpp) target_link_libraries(example_header_only PRIVATE spdlog::spdlog_header_only) endif() + +# --------------------------------------------------------------------------------------- +# Example of using new features (desensitization, structured logging, aggregation, etc.) +# --------------------------------------------------------------------------------------- +add_executable(new_features_example new_features_example.cpp) +target_link_libraries(new_features_example PRIVATE spdlog::spdlog $<$:ws2_32>) + +if(SPDLOG_BUILD_EXAMPLE_HO) + add_executable(new_features_example_header_only new_features_example.cpp) + target_link_libraries(new_features_example_header_only PRIVATE spdlog::spdlog_header_only) +endif() diff --git a/include/spdlog/desensitizer-inl.h b/include/spdlog/desensitizer-inl.h new file mode 100644 index 00000000..0e0e47da --- /dev/null +++ b/include/spdlog/desensitizer-inl.h @@ -0,0 +1,58 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { + +desensitizer::desensitizer() : rules_() { + // 添加默认的脱敏规则 + add_rule("phone", R"(1[3-9]\d{9})", "1****5678"); + add_rule("email", R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})", "***@***"); + add_rule("id_card", R"(\d{17}[\dXx])", "***********5678"); +} + +desensitizer::desensitizer(details::desensitize_rules rules) : rules_(std::move(rules)) { +} + +void desensitizer::add_rule(const std::string &name, const std::string &pattern, const std::string &replacement) { + try { + rules_[name] = {std::regex(pattern), replacement}; + } catch (const std::regex_error &e) { + throw std::invalid_argument(fmt::format("Invalid regex pattern '{}': {}", pattern, e.what())); + } +} + +void desensitizer::remove_rule(const std::string &name) { + rules_.erase(name); +} + +void desensitizer::clear_rules() { + rules_.clear(); +} + +std::string desensitizer::desensitize(const std::string &msg) const { + std::string result = msg; + for (const auto &[name, rule] : rules_) { + result = std::regex_replace(result, rule.pattern, rule.replacement); + } + return result; +} + +void desensitizer::desensitize(details::log_msg &msg) const { + if (msg.payload.empty()) { + return; + } + + std::string desensitized = desensitize(std::string(msg.payload.data(), msg.payload.size())); + msg.payload = spdlog::string_view_t(desensitized.data(), desensitized.size()); +} + +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/desensitizer.h b/include/spdlog/desensitizer.h new file mode 100644 index 00000000..8e8c3d52 --- /dev/null +++ b/include/spdlog/desensitizer.h @@ -0,0 +1,52 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +struct desensitize_rule { + std::regex pattern; + std::string replacement; +}; + +using desensitize_rules = std::unordered_map; + +} // namespace details + +class SPDLOG_API desensitizer { +public: + desensitizer(); + explicit desensitizer(details::desensitize_rules rules); + + // 添加脱敏规则 + void add_rule(const std::string &name, const std::string &pattern, const std::string &replacement); + + // 移除脱敏规则 + void remove_rule(const std::string &name); + + // 清空所有脱敏规则 + void clear_rules(); + + // 脱敏字符串 + std::string desensitize(const std::string &msg) const; + + // 脱敏log_msg + void desensitize(details::log_msg &msg) const; + +private: + details::desensitize_rules rules_; +}; + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "desensitizer-inl.h" +#endif \ No newline at end of file diff --git a/include/spdlog/details/registry.h b/include/spdlog/details/registry.h index 72c70b82..f8852fa7 100644 --- a/include/spdlog/details/registry.h +++ b/include/spdlog/details/registry.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -121,6 +122,8 @@ private: std::shared_ptr default_logger_; bool automatic_registration_ = true; size_t backtrace_n_messages_ = 0; + std::shared_ptr aggregator_; + size_t max_aggregated_logs_ = 1000; }; } // namespace details diff --git a/include/spdlog/formatters/json_formatter-inl.h b/include/spdlog/formatters/json_formatter-inl.h new file mode 100644 index 00000000..46d0c643 --- /dev/null +++ b/include/spdlog/formatters/json_formatter-inl.h @@ -0,0 +1,89 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace formatters { + +json_formatter::json_formatter(std::string eol) + : eol_(std::move(eol)) +{} + +std::unique_ptr json_formatter::clone() const { + return details::make_unique(*this); +} + +void json_formatter::format(const details::log_msg &msg, memory_buf_t &dest) { + fmt::format_to(std::back_inserter(dest), "{{"); + + // 添加时间字段 + std::tm tm_time; + if (msg.time.time_since_epoch().count() == 0) { + // 如果时间未设置,使用当前时间 + auto now = std::chrono::system_clock::now(); + tm_time = details::os::localtime(std::chrono::system_clock::to_time_t(now)); + } else { + tm_time = details::os::localtime(std::chrono::system_clock::to_time_t(msg.time)); + } + + char time_buf[128]; + std::strftime(time_buf, sizeof(time_buf), time_format_.c_str(), &tm_time); + fmt::format_to(std::back_inserter(dest), "\"time\":\"{}\",", time_buf); + + // 添加日志级别 + fmt::format_to(std::back_inserter(dest), "\"level\":{},", static_cast(msg.level)); + + // 添加日志级别名称 + if (include_level_name_) { + fmt::format_to(std::back_inserter(dest), "\"level_name\":\"{}\",", level::to_string_view(msg.level)); + } + + // 添加日志名称 + fmt::format_to(std::back_inserter(dest), "\"logger_name\":\"{}\",", msg.logger_name); + + // 添加线程ID + if (include_thread_id_) { + fmt::format_to(std::back_inserter(dest), "\"thread_id\":{},", msg.thread_id); + } + + // 添加日志消息 + fmt::format_to(std::back_inserter(dest), "\"message\":{}", fmt::format("{}", msg.payload)); + + // 添加源位置信息 + if (include_source_location_ && msg.source.filename != nullptr) { + fmt::format_to(std::back_inserter(dest), ",\"source_location\":{{\"file\":\"{}\",\"line\":{},\"function\":\"{}\"}}", + msg.source.filename, + msg.source.line, + msg.source.funcname); + } + + // 关闭JSON对象 + fmt::format_to(std::back_inserter(dest), "{}", eol_); +} + +void json_formatter::set_include_source_location(bool include) { + include_source_location_ = include; +} + +void json_formatter::set_include_thread_id(bool include) { + include_thread_id_ = include; +} + +void json_formatter::set_include_level_name(bool include) { + include_level_name_ = include; +} + +void json_formatter::set_time_format(std::string format) { + time_format_ = std::move(format); +} + +} // namespace formatters +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/formatters/json_formatter.h b/include/spdlog/formatters/json_formatter.h new file mode 100644 index 00000000..e6ea6857 --- /dev/null +++ b/include/spdlog/formatters/json_formatter.h @@ -0,0 +1,48 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include + +namespace spdlog { +namespace formatters { + +class SPDLOG_API json_formatter : public formatter { +public: + json_formatter(std::string eol = spdlog::details::os::default_eol); + + std::unique_ptr clone() const override; + void format(const details::log_msg &msg, memory_buf_t &dest) override; + + // 设置是否包含源位置信息 + void set_include_source_location(bool include); + + // 设置是否包含线程ID + void set_include_thread_id(bool include); + + // 设置是否包含日志级别名称 + void set_include_level_name(bool include); + + // 设置时间格式 + void set_time_format(std::string format); + +private: + std::string eol_; + bool include_source_location_ = true; + bool include_thread_id_ = true; + bool include_level_name_ = true; + std::string time_format_ = "%Y-%m-%dT%H:%M:%S.%eZ"; +}; + +} // namespace formatters +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "json_formatter-inl.h" +#endif \ No newline at end of file diff --git a/include/spdlog/log_aggregator-inl.h b/include/spdlog/log_aggregator-inl.h new file mode 100644 index 00000000..c6e075d3 --- /dev/null +++ b/include/spdlog/log_aggregator-inl.h @@ -0,0 +1,240 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include +#include +#include +#include +#include + +namespace spdlog { + +log_aggregator::log_aggregator(size_t max_buffer_size) + : max_buffer_size_(max_buffer_size) + , log_callback_([this](const details::log_msg &msg) { + std::lock_guard lock(buffer_mutex_); + if (log_buffer_.size() >= max_buffer_size_) { + log_buffer_.erase(log_buffer_.begin()); + } + log_buffer_.push_back(msg); + update_statistics(msg); + update_latency_distribution(msg); + }) +{ + // 初始化统计信息 + auto now = std::chrono::system_clock::now(); + statistics_.first_log_time = now; + statistics_.last_log_time = now; +} + +log_aggregator::~log_aggregator() { + // 注销所有日志记录器 + std::lock_guard lock(loggers_mutex_); + for (auto &[name, logger] : registered_loggers_) { + logger->remove_callback(log_callback_); + } +} + +void log_aggregator::register_logger(std::shared_ptr logger) { + std::lock_guard lock(loggers_mutex_); + auto name = logger->name(); + if (registered_loggers_.find(name) == registered_loggers_.end()) { + registered_loggers_[name] = logger; + logger->add_callback(log_callback_); + } +} + +void log_aggregator::unregister_logger(std::shared_ptr logger) { + std::lock_guard lock(loggers_mutex_); + auto name = logger->name(); + auto it = registered_loggers_.find(name); + if (it != registered_loggers_.end()) { + logger->remove_callback(log_callback_); + registered_loggers_.erase(it); + } +} + +query_result log_aggregator::query_logs(const query_filter &filter) const { + auto start_time = std::chrono::high_resolution_clock::now(); + query_result result; + std::vector filtered_logs; + + std::lock_guard lock(buffer_mutex_); + result.total_logs = log_buffer_.size(); + + // 过滤日志 + for (const auto &msg : log_buffer_) { + if (msg.time < filter.start_time || msg.time > filter.end_time) { + continue; + } + if (msg.level < filter.min_level || msg.level > filter.max_level) { + continue; + } + if (!filter.logger_name.empty() && msg.logger_name != filter.logger_name) { + continue; + } + if (!filter.keyword.empty()) { + std::string payload(msg.payload.data(), msg.payload.size()); + if (payload.find(filter.keyword) == std::string::npos) { + continue; + } + } + filtered_logs.push_back(msg); + } + + result.total_matched = filtered_logs.size(); + + // 应用分页 + size_t start_idx = filter.offset; + size_t end_idx = std::min(start_idx + filter.limit, filtered_logs.size()); + if (start_idx < filtered_logs.size()) { + result.logs.insert(result.logs.end(), filtered_logs.begin() + start_idx, filtered_logs.begin() + end_idx); + } + + auto end_time = std::chrono::high_resolution_clock::now(); + result.query_duration = std::chrono::duration(end_time - start_time).count(); + + return result; +} + +log_statistics log_aggregator::get_statistics() const { + std::lock_guard lock(statistics_mutex_); + return statistics_; +} + +latency_distribution log_aggregator::get_latency_distribution(const std::string &prefix) const { + std::lock_guard lock(latency_mutex_); + auto it = latency_distributions_.find(prefix); + if (it != latency_distributions_.end()) { + return it->second; + } + return latency_distribution{}; +} + +void log_aggregator::clean_old_logs(std::chrono::system_clock::time_point before_time) { + std::lock_guard lock(buffer_mutex_); + auto it = std::remove_if(log_buffer_.begin(), log_buffer_.end(), + [&before_time](const details::log_msg &msg) { + return msg.time < before_time; + }); + log_buffer_.erase(it, log_buffer_.end()); +} + +void log_aggregator::set_max_buffer_size(size_t max_size) { + std::lock_guard lock(buffer_mutex_); + max_buffer_size_ = max_size; + // 如果当前缓存大小超过新的最大大小,删除旧的日志 + if (log_buffer_.size() > max_size) { + size_t to_remove = log_buffer_.size() - max_size; + log_buffer_.erase(log_buffer_.begin(), log_buffer_.begin() + to_remove); + } +} + +size_t log_aggregator::get_current_buffer_size() const { + std::lock_guard lock(buffer_mutex_); + return log_buffer_.size(); +} + +void log_aggregator::add_custom_statistic(const std::string &name, + std::function statistic_fn) { + std::lock_guard lock(custom_stats_mutex_); + custom_statistics_[name] = std::move(statistic_fn); +} + +std::unordered_map log_aggregator::get_custom_statistics() const { + std::lock_guard lock(custom_stats_mutex_); + std::unordered_map result; + for (const auto &[name, fn] : custom_statistics_) { + // 这里可以根据需要实现自定义统计结果的获取 + // 目前只是返回0.0作为示例 + result[name] = 0.0; + } + return result; +} + +void log_aggregator::update_statistics(const details::log_msg &msg) { + std::lock_guard lock(statistics_mutex_); + + statistics_.total_logs++; + statistics_.last_log_time = msg.time; + + // 更新级别计数 + statistics_.level_counts[msg.level]++; + + // 调用自定义统计函数 + std::lock_guard custom_lock(custom_stats_mutex_); + for (auto &[name, fn] : custom_statistics_) { + try { + fn(msg, statistics_); + } catch (const std::exception &ex) { + // 忽略自定义统计函数的异常 + } + } +} + +void log_aggregator::update_latency_distribution(const details::log_msg &msg) { + std::string payload(msg.payload.data(), msg.payload.size()); + std::optional latency = parse_latency(payload); + if (!latency) { + return; + } + + // 提取延迟前缀(如果有的话) + std::string prefix; + size_t colon_pos = payload.find(":"); + if (colon_pos != std::string::npos) { + prefix = payload.substr(0, colon_pos); + } + + std::lock_guard lock(latency_mutex_); + auto &dist = latency_distributions_[prefix]; + + dist.count++; + dist.min_latency = std::min(dist.min_latency, *latency); + dist.max_latency = std::max(dist.max_latency, *latency); + dist.average_latency = (dist.average_latency * (dist.count - 1) + *latency) / dist.count; + + // 将延迟分到不同的桶中 + std::string bucket; + if (*latency < 1.0) { + bucket = "<1ms"; + } else if (*latency < 10.0) { + bucket = "1-10ms"; + } else if (*latency < 100.0) { + bucket = "10-100ms"; + } else if (*latency < 1000.0) { + bucket = "100-1000ms"; + } else { + bucket = ">1000ms"; + } + dist.latency_buckets[bucket]++; +} + +std::optional log_aggregator::parse_latency(const std::string &msg) const { + // 尝试从日志消息中解析延迟信息 + // 支持的格式: + // - latency: 123.45ms + // - time: 123.45ms + // - took: 123.45ms + + std::regex latency_regex(R"((latency|time|took):\s*(\d+(?:\.\d+)?)ms)"s); + std::smatch match; + if (std::regex_search(msg, match, latency_regex)) { + try { + double latency = std::stod(match[2]); + return latency; + } catch (const std::exception &ex) { + // 解析失败 + } + } + return std::nullopt; +} + +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/log_aggregator.h b/include/spdlog/log_aggregator.h new file mode 100644 index 00000000..1d7b99e5 --- /dev/null +++ b/include/spdlog/log_aggregator.h @@ -0,0 +1,135 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { + +// 日志统计信息 +struct log_statistics { + std::unordered_map level_counts; + size_t total_logs = 0; + std::chrono::system_clock::time_point first_log_time; + std::chrono::system_clock::time_point last_log_time; +}; + +// 延迟分布 +struct latency_distribution { + size_t count = 0; + double min_latency = 0.0; + double max_latency = 0.0; + double average_latency = 0.0; + std::unordered_map latency_buckets; +}; + +// 查询结果 +struct query_result { + std::vector logs; + size_t total_matched = 0; + size_t total_logs = 0; + double query_duration = 0.0; +}; + +// 查询过滤条件 +struct query_filter { + std::chrono::system_clock::time_point start_time; + std::chrono::system_clock::time_point end_time; + level::level_enum min_level = level::trace; + level::level_enum max_level = level::critical; + std::string keyword; + std::string logger_name; + size_t limit = 100; + size_t offset = 0; +}; + +class SPDLOG_API log_aggregator { +public: + explicit log_aggregator(size_t max_buffer_size = 10000); + ~log_aggregator(); + + log_aggregator(const log_aggregator &) = delete; + log_aggregator &operator=(const log_aggregator &) = delete; + + // 注册日志记录器 + void register_logger(std::shared_ptr logger); + + // 注销日志记录器 + void unregister_logger(std::shared_ptr logger); + + // 查询日志 + query_result query_logs(const query_filter &filter) const; + + // 获取日志统计信息 + log_statistics get_statistics() const; + + // 获取延迟分布 + latency_distribution get_latency_distribution(const std::string &prefix = "") const; + + // 清理指定时间之前的日志 + void clean_old_logs(std::chrono::system_clock::time_point before_time); + + // 设置最大缓存大小 + void set_max_buffer_size(size_t max_size); + + // 获取当前缓存大小 + size_t get_current_buffer_size() const; + + // 添加自定义统计函数 + void add_custom_statistic(const std::string &name, + std::function statistic_fn); + + // 获取自定义统计结果 + std::unordered_map get_custom_statistics() const; + +private: + // 日志缓存 + std::vector log_buffer_; + size_t max_buffer_size_; + mutable std::mutex buffer_mutex_; + + // 注册的日志记录器 + std::unordered_map> registered_loggers_; + mutable std::mutex loggers_mutex_; + + // 日志统计信息 + log_statistics statistics_; + mutable std::mutex statistics_mutex_; + + // 延迟分布 + std::unordered_map latency_distributions_; + mutable std::mutex latency_mutex_; + + // 自定义统计函数 + std::unordered_map> custom_statistics_; + mutable std::mutex custom_stats_mutex_; + + // 日志收集回调 + std::function log_callback_; + + // 更新统计信息 + void update_statistics(const details::log_msg &msg); + + // 更新延迟分布 + void update_latency_distribution(const details::log_msg &msg); + + // 解析延迟信息 + std::optional parse_latency(const std::string &msg) const; +}; + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "log_aggregator-inl.h" +#endif \ No newline at end of file diff --git a/include/spdlog/logger-inl.h b/include/spdlog/logger-inl.h index 6879273c..627eb21a 100644 --- a/include/spdlog/logger-inl.h +++ b/include/spdlog/logger-inl.h @@ -22,7 +22,8 @@ SPDLOG_INLINE logger::logger(const logger &other) level_(other.level_.load(std::memory_order_relaxed)), flush_level_(other.flush_level_.load(std::memory_order_relaxed)), custom_err_handler_(other.custom_err_handler_), - tracer_(other.tracer_) {} + tracer_(other.tracer_), + desensitizer_(other.desensitizer_) {} SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT : name_(std::move(other.name_)), @@ -30,7 +31,8 @@ SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT level_(other.level_.load(std::memory_order_relaxed)), flush_level_(other.flush_level_.load(std::memory_order_relaxed)), custom_err_handler_(std::move(other.custom_err_handler_)), - tracer_(std::move(other.tracer_)) + tracer_(std::move(other.tracer_)), + desensitizer_(std::move(other.desensitizer_)) {} @@ -55,6 +57,7 @@ SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT { custom_err_handler_.swap(other.custom_err_handler_); std::swap(tracer_, other.tracer_); + std::swap(desensitizer_, other.desensitizer_); } SPDLOG_INLINE void swap(logger &a, logger &b) noexcept { a.swap(b); } @@ -113,6 +116,31 @@ SPDLOG_INLINE void logger::set_error_handler(err_handler handler) { custom_err_handler_ = std::move(handler); } +SPDLOG_INLINE void logger::set_desensitizer(std::shared_ptr desensitizer) { + desensitizer_ = std::move(desensitizer); +} + +SPDLOG_INLINE std::shared_ptr logger::get_desensitizer() const { + return desensitizer_; +} + +SPDLOG_INLINE void logger::add_callback(std::function callback) { + callbacks_.push_back(std::move(callback)); +} + +SPDLOG_INLINE void logger::remove_callback(std::function callback) { + callbacks_.erase(std::remove_if(callbacks_.begin(), callbacks_.end(), + [&callback](const std::function &c) { + return c.target_type() == callback.target_type() && + c.target() == callback.target(); + }), + callbacks_.end()); +} + +SPDLOG_INLINE void logger::clear_callbacks() { + callbacks_.clear(); +} + // create new logger with same sinks and configuration. SPDLOG_INLINE std::shared_ptr logger::clone(std::string logger_name) { auto cloned = std::make_shared(*this); @@ -124,11 +152,28 @@ SPDLOG_INLINE std::shared_ptr logger::clone(std::string logger_name) { SPDLOG_INLINE void logger::log_it_(const spdlog::details::log_msg &log_msg, bool log_enabled, bool traceback_enabled) { + // 应用脱敏 + details::log_msg desensitized_msg = log_msg; + if (desensitizer_) { + desensitizer_->desensitize(desensitized_msg); + } + if (log_enabled) { - sink_it_(log_msg); + sink_it_(desensitized_msg); } if (traceback_enabled) { - tracer_.push_back(log_msg); + tracer_.push_back(desensitized_msg); + } + + // 调用所有注册的回调函数 + for (const auto &callback : callbacks_) { + try { + callback(desensitized_msg); + } catch (const std::exception &ex) { + err_handler_(fmt::format("Callback failed: {}", ex.what())); + } catch (...) { + err_handler_("Callback failed with unknown exception"); + } } } diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index 8c3cd91f..5c7898d5 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifndef _WIN32 @@ -25,6 +26,8 @@ #include #endif +#include +#include #include #ifndef SPDLOG_NO_EXCEPTIONS @@ -52,7 +55,8 @@ public: // Empty logger explicit logger(std::string name) : name_(std::move(name)), - sinks_() {} + sinks_(), + desensitizer_(std::make_shared()) {} // Logger with range on sinks template @@ -74,6 +78,21 @@ public: logger(logger &&other) SPDLOG_NOEXCEPT; logger &operator=(logger other) SPDLOG_NOEXCEPT; void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; + + // 设置脱敏器 + void set_desensitizer(std::shared_ptr desensitizer); + + // 获取脱敏器 + std::shared_ptr get_desensitizer() const; + + // 添加日志消费回调 + void add_callback(std::function callback); + + // 移除日志消费回调 + void remove_callback(std::function callback); + + // 清空所有日志消费回调 + void clear_callbacks(); template void log(source_loc loc, level::level_enum lvl, format_string_t fmt, Args &&...args) { @@ -311,6 +330,8 @@ protected: spdlog::level_t flush_level_{level::off}; err_handler custom_err_handler_{nullptr}; details::backtracer tracer_; + std::shared_ptr desensitizer_; + std::vector> callbacks_; // common implementation for after templated public api has been resolved template diff --git a/include/spdlog/sinks/level_based_sink-inl.h b/include/spdlog/sinks/level_based_sink-inl.h new file mode 100644 index 00000000..071e4127 --- /dev/null +++ b/include/spdlog/sinks/level_based_sink-inl.h @@ -0,0 +1,134 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace sinks { + +void level_based_sink::add_sink_for_level(level::level_enum level, sink_ptr sink) { + std::lock_guard lock(mutex_); + sinks_[level].push_back(std::move(sink)); +} + +void level_based_sink::add_sink_for_levels(std::initializer_list levels, sink_ptr sink) { + std::lock_guard lock(mutex_); + for (auto level : levels) { + sinks_[level].push_back(sink); + } +} + +void level_based_sink::add_sink_for_all_levels(sink_ptr sink) { + std::lock_guard lock(mutex_); + all_level_sinks_.push_back(std::move(sink)); +} + +void level_based_sink::remove_sink_for_level(level::level_enum level, sink_ptr sink) { + std::lock_guard lock(mutex_); + auto it = sinks_.find(level); + if (it != sinks_.end()) { + auto &level_sinks = it->second; + level_sinks.erase(std::remove(level_sinks.begin(), level_sinks.end(), sink), level_sinks.end()); + } +} + +void level_based_sink::remove_all_sinks_for_level(level::level_enum level) { + std::lock_guard lock(mutex_); + sinks_.erase(level); +} + +void level_based_sink::remove_all_sinks() { + std::lock_guard lock(mutex_); + sinks_.clear(); + all_level_sinks_.clear(); +} + +void level_based_sink::set_formatter(std::unique_ptr formatter) { + std::lock_guard lock(mutex_); + for (auto &[level, level_sinks] : sinks_) { + for (auto &sink : level_sinks) { + sink->set_formatter(formatter->clone()); + } + } + for (auto &sink : all_level_sinks_) { + sink->set_formatter(std::move(formatter)); + } +} + +void level_based_sink::set_level(level::level_enum level) { + std::lock_guard lock(mutex_); + level_ = level; +} + +level::level_enum level_based_sink::level() const { + std::lock_guard lock(mutex_); + return level_; +} + +bool level_based_sink::should_log(level::level_enum level) const { + std::lock_guard lock(mutex_); + return level >= level_; +} + +void level_based_sink::log(const details::log_msg &msg) { + std::lock_guard lock(mutex_); + + // 首先发送到所有级别的sink + for (auto &sink : all_level_sinks_) { + if (sink->should_log(msg.level)) { + try { + sink->log(msg); + } catch (const std::exception &ex) { + // 忽略sink的异常,继续处理其他sink + } + } + } + + // 然后发送到对应级别的sink + auto it = sinks_.find(msg.level); + if (it != sinks_.end()) { + for (auto &sink : it->second) { + if (sink->should_log(msg.level)) { + try { + sink->log(msg); + } catch (const std::exception &ex) { + // 忽略sink的异常,继续处理其他sink + } + } + } + } +} + +void level_based_sink::flush() { + std::lock_guard lock(mutex_); + + // 刷新所有级别的sink + for (auto &sink : all_level_sinks_) { + try { + sink->flush(); + } catch (const std::exception &ex) { + // 忽略sink的异常,继续处理其他sink + } + } + + // 刷新对应级别的sink + for (auto &[level, level_sinks] : sinks_) { + for (auto &sink : level_sinks) { + try { + sink->flush(); + } catch (const std::exception &ex) { + // 忽略sink的异常,继续处理其他sink + } + } + } +} + +} // namespace sinks +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/sinks/level_based_sink.h b/include/spdlog/sinks/level_based_sink.h new file mode 100644 index 00000000..a06f4564 --- /dev/null +++ b/include/spdlog/sinks/level_based_sink.h @@ -0,0 +1,73 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace spdlog { +namespace sinks { + +// 基于日志级别的sink,根据日志级别将日志分发到不同的sink +class SPDLOG_API level_based_sink : public sink { +public: + explicit level_based_sink() = default; + ~level_based_sink() override = default; + + level_based_sink(const level_based_sink &) = delete; + level_based_sink &operator=(const level_based_sink &) = delete; + + // 添加sink到指定级别 + void add_sink_for_level(level::level_enum level, sink_ptr sink); + + // 添加sink到多个级别 + void add_sink_for_levels(std::initializer_list levels, sink_ptr sink); + + // 添加sink到所有级别 + void add_sink_for_all_levels(sink_ptr sink); + + // 移除指定级别上的指定sink + void remove_sink_for_level(level::level_enum level, sink_ptr sink); + + // 移除指定级别上的所有sink + void remove_all_sinks_for_level(level::level_enum level); + + // 移除所有sink + void remove_all_sinks(); + + // 设置sink的格式化器 + void set_formatter(std::unique_ptr formatter) override; + + // 设置sink的日志级别 + void set_level(level::level_enum level) override; + + // 获取sink的日志级别 + level::level_enum level() const override; + + // 检查sink是否应该记录指定级别的日志 + bool should_log(level::level_enum level) const override; + + // 记录日志 + void log(const details::log_msg &msg) override; + + // 刷新sink + void flush() override; + +private: + std::unordered_map> sinks_; + std::vector all_level_sinks_; + level::level_enum level_ = level::trace; +}; + +} // namespace sinks +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "level_based_sink-inl.h" +#endif \ No newline at end of file diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index 1a927ffc..fac11903 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -98,6 +98,19 @@ SPDLOG_API void register_logger(std::shared_ptr logger); // Will replace any existing logger with the same name. SPDLOG_API void register_or_replace(std::shared_ptr logger); +// Log aggregator functions +// Get global log aggregator instance +SPDLOG_API std::shared_ptr aggregator(); + +// Set maximum number of logs to keep in aggregator +SPDLOG_API void set_max_aggregated_logs(size_t max_logs); + +// Get all aggregated logs with specified level or higher +SPDLOG_API std::vector get_aggregated_logs(level::level_enum level = level::trace); + +// Clear all aggregated logs +SPDLOG_API void clear_aggregated_logs(); + // Apply a user-defined function on all registered loggers // Example: // spdlog::apply_all([&](std::shared_ptr l) {l->flush();});