You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Boost.Log连续重复日志处理:实现重复消息计数与跳过输出

实现Boost.Log重复日志消息的自动计数跳过功能

嘿,我之前封装Boost.Log日志库的时候也碰到过一模一样的需求——就是不想让重复日志刷屏,像syslog那样自动计数跳过。Boost.Log本身没有内置这个功能,但咱们可以通过自定义sink前端来实现,完全兼容你现有的Text File Backend和已配置的TimeStamp、Filter功能。

核心思路

咱们要在日志消息到达文件后端之前加个"中间层":

  1. 缓存最近一次的日志消息内容
  2. 遇到重复消息时只计数,不直接写入文件
  3. 遇到新消息时,先输出之前的重复计数提示,再写入新消息
  4. 程序退出时要处理剩余的重复计数,避免丢失最后一批的提示

具体实现步骤

1. 编写重复消息跟踪类

这个类负责缓存消息、计数,以及生成跳过提示:

#include <boost/log/sinks.hpp>
#include <string>
#include <mutex>
#include <boost/log/expressions.hpp>
#include <boost/log/core.hpp>

class DuplicateMessageTracker {
private:
    std::mutex m_mutex;
    std::string m_last_msg;
    size_t m_dup_count = 0;
    boost::log::sinks::sink_ptr m_target_sink;

    // 输出重复跳过提示的辅助函数
    void emit_skip_notice() {
        if (m_dup_count > 0) {
            std::string notice = "Skipped " + std::to_string(m_dup_count) + " duplicate messages!";
            // 构造日志记录并传给目标sink
            boost::log::record rec = boost::log::core::get()->open_record();
            if (rec) {
                boost::log::record_ostream strm(rec);
                strm << notice;
                strm.flush();
                m_target_sink->consume(rec);
            }
            m_dup_count = 0;
        }
    }

public:
    explicit DuplicateMessageTracker(boost::log::sinks::sink_ptr target_sink) 
        : m_target_sink(std::move(target_sink)) {}

    void process_record(const boost::log::record_view& rec) {
        std::lock_guard<std::mutex> lock(m_mutex);
        // 提取日志消息内容(这里要和你的日志属性名对应,默认是"Message")
        auto msg_opt = boost::log::extract<std::string>("Message", rec);
        if (!msg_opt) {
            // 如果提取不到消息,直接转发给目标sink
            m_target_sink->consume(rec);
            return;
        }
        std::string current_msg = *msg_opt;

        if (current_msg == m_last_msg) {
            // 重复消息,只计数
            m_dup_count++;
        } else {
            // 新消息,先输出之前的跳过提示
            emit_skip_notice();
            // 转发当前消息到目标sink
            m_target_sink->consume(rec);
            // 更新缓存的消息
            m_last_msg = std::move(current_msg);
        }
    }

    // 程序退出时调用,确保剩余的重复计数被输出
    void final_flush() {
        std::lock_guard<std::mutex> lock(m_mutex);
        emit_skip_notice();
    }
};

2. 包装成Boost.Log兼容的Sink

Boost.Log的核心只认符合sink接口的对象,所以咱们把上面的跟踪类包装成标准sink:

class DuplicateFilterSink : public boost::log::sinks::sink {
private:
    DuplicateMessageTracker m_tracker;

public:
    explicit DuplicateFilterSink(boost::log::sinks::sink_ptr target_sink) 
        : m_tracker(std::move(target_sink)) {}

    // 实现sink接口的必要方法
    bool is_enabled() const override {
        return m_tracker.m_target_sink->is_enabled();
    }

    void set_enabled(bool enabled) override {
        m_tracker.m_target_sink->set_enabled(enabled);
    }

    void consume(const boost::log::record_view& rec) override {
        m_tracker.process_record(rec);
    }

    void flush() override {
        m_tracker.final_flush();
        m_tracker.m_target_sink->flush();
    }
};

3. 替换原有的File Sink

现在把你原本的Text File Sink用这个自定义sink包装起来,替换原来的添加逻辑:

// 1. 创建你原本的Text File Backend(保持你现有的配置)
auto file_backend = boost::make_shared<boost::log::sinks::text_file_backend>(
    boost::log::keywords::file_name = "your_log_pattern.log",
    boost::log::keywords::rotation_size = 10*1024*1024, // 按大小切割
    // 其他你原本的配置...
);

// 2. 创建原始的同步sink前端
auto raw_file_sink = boost::make_shared<boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend>>(file_backend);

// 3. 设置你原本的日志格式(保留TimeStamp、Filter这些)
raw_file_sink->set_formatter(
    boost::log::expressions::stream
        << "[" << boost::log::expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d %H:%M:%S") << "]"
        << " [" << boost::log::expressions::attr<std::string>("Filter") << "]"
        << " " << boost::log::expressions::smessage
);

// 4. 用自定义的去重sink包装原始sink
auto dedup_sink = boost::make_shared<DuplicateFilterSink>(raw_file_sink);

// 5. 添加到日志核心(替换你原本的add_sink调用)
boost::log::core::get()->add_sink(dedup_sink);

4. 程序退出时的收尾

一定要在程序结束前调用flush,确保最后一批重复消息的计数提示被输出:

// 比如在main函数末尾,或者全局对象的析构函数里
dedup_sink->flush();

关键注意事项

  • 消息属性名:代码里的"Message"是Boost.Log默认的消息属性名,如果你的ADD_LOG宏用了自定义的属性名(比如你自己定义了"LogMsg"),要修改extract<std::string>("Message", rec)里的字符串。
  • 线程安全:用了std::mutex保护共享状态,确保多线程日志场景下不会出问题。
  • 动态消息处理:如果你的日志消息本身包含动态内容(比如把时间戳写在消息里),那这个逻辑会把它们当成不同的消息——不过你的TimeStamp是单独的属性,所以消息内容应该是固定的,没问题。
  • 可扩展性:你可以轻松扩展这个类,比如添加"重复N次才开始跳过"的阈值,或者"相同消息间隔超过X秒就重新打印"的超时逻辑。

测试效果

当你调用三次ADD_LOG("Hello"),再调用一次ADD_LOG("Some Different Hello"),日志文件里会输出:

[2024-05-20 15:30:00] [YourFilterTag] Hello
[2024-05-20 15:30:00] [YourFilterTag] Skipped 2 duplicate messages!
[2024-05-20 15:30:00] [YourFilterTag] Some Different Hello

内容的提问来源于stack exchange,提问作者Jafar Gh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 20:22:54