当前位置: 代码网 > it编程>编程语言>C/C++ > 使用C++实现的错误信息收集器

使用C++实现的错误信息收集器

2026年07月29日 C/C++ 我要评论
1. 背景在大型c++项目的开发过程中,错误处理是一个不容忽视的核心环节。年初笔者接手了一个工程,近几个月在修改,发现其中的错误信息处理比较凌乱,考虑到方便日后的自己的维护,因此要进行必要的重构。缺乏

1. 背景

在大型c++项目的开发过程中,错误处理是一个不容忽视的核心环节。年初笔者接手了一个工程,近几个月在修改,发现其中的错误信息处理比较凌乱,考虑到方便日后的自己的维护,因此要进行必要的重构。缺乏统一错误收集机制带来以下一些困扰:

  • 信息碎片化:不同模块各自定义错误格式,日志散落在不同文件中,排查问题时需要手动关联
  • 重复记录:同一个错误可能被多个层级重复记录,导致日志膨胀
  • 特殊字符干扰:错误消息中包含的换行符、制表符破坏日志格式,影响后续分析
  • 缺乏分类:无法区分致命错误、警告和普通提示信息

为了解决这些问题,笔者着手设计并实现了一个轻量级的errorcollector类。本文将从需求分析、设计思路、实现细节到测试验证,完整分享这个组件的开发过程。

ps:近年来,笔者基本都会因其他共事的人升职、转岗等原因接手不同性质代码工程,但还没能力向他人移交工程或事务。从技术角度看,需要写不同类别的代码,是件好事,但从个人成长角度看,不是个事,特别是处于年龄越来越大,头发越来越白的尴尬的人生阶段。

2. 设计思路

2.1 核心需求

经过需求梳理,我们确定了以下核心功能点:

  1. 去重存储:相同的错误消息只保留一份,避免冗余
  2. 分类标记:每条消息关联一个错误码(0表示普通信息,非0表示错误)
  3. 字符清洗:自动移除\t\n\r等特殊字符
  4. 灵活查询:支持按类型(全部/仅错误)、按错误码过滤查询
  5. 空值安全:对空字符串和空指针有防护
  6. 简单易用:提供直观的接口,且只有头文件,减少使用成本

2.2 数据结构设计

采用两个并行的std::vector分别存储错误码和消息文本:

std::vector<int> m_code;          // 错误码列表
std::vector<std::string> m_list;  // 消息列表(与m_code一一对应)

这种设计的优势有以下几点:

  • 索引对应:通过相同下标关联错误码和消息,无需额外的映射结构(注意索引需大于等于0)
  • 顺序保留:保持添加顺序,便于追溯问题发生的时间线
  • 内存友好:相比std::mapstd::unordered_map,内存开销更小

去重机制则通过std::findm_list中线性查找实现。虽然时间复杂度为o(n),但考虑到错误收集场景下消息数量通常有限(一般不超过几十条),这种实现足够高效且代码简洁。

2.3 接口设计原则

  • 重载友好:同时支持std::stringconst char*参数,适应不同使用场景
  • 默认参数code参数默认为0,简化普通信息记录的调用
  • 防御性编程:在函数入口进行参数校验,提前返回避免无效操作
  • const正确性:查询接口标记为const,确保不修改对象状态

3. 实践

3.1 源码

errorcollector.h文件代码如下:

#include <algorithm> 
#include <vector>
#include <unordered_set>
#include <string>

// 错误信息、过程信息记录器
class errorcollector {
public:
    // 构造函数,初始化变量为空
    errorcollector() {
        m_code.clear();
        m_list.clear();
    }
    
    // 析构函数(可选)
    ~errorcollector() {
        m_code.clear();
        m_list.clear();
    }

    void addinfo(const std::string& msg1, int code = 0) {
        // 判断字符串是否为空
        if (msg1.empty()) {
            return;
        }
        std::string msg = msg1;

        msg.erase(std::remove_if(msg.begin(), msg.end(),
        [](char c) { return c == '\t' || c == '\n' || c == '\r'; }), 
        msg.end());

        // 删除特殊字符后再次判断是否为空
        if (msg.empty()) {
            return;
        }
        if (std::find(m_list.begin(), m_list.end(), msg) == m_list.end()) {
            m_code.push_back(code);
            m_list.push_back(msg);
        }
    }
    void addinfo(const char* msg1, int code = 0) {
        // 判断指针是否为空,以及字符串是否为空
        if (msg1 == nullptr || msg1[0] == '\0') {
            return;
        }
        std::string msg = msg1;
        addinfo(msg, code);
    }

    // 返回竖线分隔的字符串,格式:错误1 | 错误2 | 错误3
    // all 表示所有的信息,如只返回错误,设置为false
    std::string getinfos(bool all = true) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_list.size(); i++) {
            if (!all && m_code[i] == 0) {
                continue;
            }
            if (cnt > 0) {
                result += " | ";
            }
            cnt++;
            result += m_list[i];
        }
        
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    // 同上,指定code
    std::string getinfobycode(int code) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_list.size(); i++) {
            if (m_code[i] != code) {
                continue;
            }
            if (cnt > 0) {
                result += " | ";
            }
            cnt++;
            result += m_list[i];
        }
        
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    std::string getcodes() const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_code.size(); i++) {
            if (m_code[i] == 0) {
                continue;
            }
            if (cnt > 0) {
                result += "|"; // 不要空格紧凑一些
            }
            cnt++;
            result += std::to_string(m_code[i]);
        }
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }
    // 可指定忽略的code
    std::string getcodes(int skipcode) const {
        std::string result;
        int cnt = 0;
        for (size_t i = 0; i < m_code.size(); i++) {
            if (m_code[i] == 0 || m_code[i] == skipcode) {
                continue;
            }
            if (cnt > 0) {
                result += "|"; // 不要空格紧凑一些
            }
            cnt++;
            result += std::to_string(m_code[i]);
        }
        if (!result.empty() && result.back() == '|') {
            result.pop_back();
        }
        return result;
    }

    bool empty() {return m_list.empty();}

private:
    std::vector<int> m_code;
    std::vector<std::string> m_list;
};

3.2 关键实现解析

特殊字符清洗

msg.erase(std::remove_if(msg.begin(), msg.end(),
    [](char c) { return c == '\t' || c == '\n' || c == '\r'; }), 
    msg.end());

这里使用了经典的"erase-remove"惯用法,高效地移除了三种常见的格式干扰字符。清洗后再判断消息是否为空,确保不会存储仅由特殊字符组成的无效消息。

去重逻辑

if (std::find(m_list.begin(), m_list.end(), msg) == m_list.end()) {
    m_code.push_back(code);
    m_list.push_back(msg);
}

值得注意的是,此处考虑的设计决策是:去重基于消息内容,而不是消息+错误码的组合。这意味着如果同一个错误消息先后以不同的错误码添加,只有第一次会被保留,后续的添加会被忽略。这种设计在某些场景下可能丢失错误码信息,但换来的是消息列表的简洁性。在实际使用中,我们约定错误消息应该包含足够的上下文信息,使得重复消息确实不需要再次记录。

输出格式控制

if (cnt > 0) {
    result += " | ";
}
cnt++;
result += m_list[i];

此处通过计数器控制分隔符的插入位置,避免在字符串开头或结尾出现多余的分隔符。getcodes方法中则使用更紧凑的"|"(无空格)作为分隔符,方便后续解析。

3.3 使用示例

errorcollector collector;

// 记录普通信息
collector.addinfo("开始数据迁移");

// 记录错误
collector.addinfo("连接数据库失败", 1001);
collector.addinfo("读取配置文件异常", 1002);

// 获取所有信息(包含提示和错误)
std::string all = collector.getinfos(true);
// 输出: "开始数据迁移 | 连接数据库失败 | 读取配置文件异常"

// 仅获取错误信息
std::string errors = collector.getinfos(false);
// 输出: "连接数据库失败 | 读取配置文件异常"

// 获取错误码列表
std::string codes = collector.getcodes();
// 输出: "1001|1002"

// 按错误码查询
std::string info = collector.getinfobycode(1001);
// 输出: "连接数据库失败"

3.4 应用场景

errorcollector在以下场景中特别有用:

  1. api参数校验:收集所有校验失败的信息,一次性返回给调用方
  2. 批量操作:执行多个独立操作时,收集所有异常而非遇到第一个错误就中断
  3. 配置加载:加载配置文件时,记录所有格式错误或缺失的配置项
  4. 单元测试:收集测试过程中的断言失败信息

4. 测试

4.1 测试代码

测试代码文件main.cpp:

#include <iostream>
#include "errorcollector.h"

void printtestresult(const std::string& testname, bool passed) {
    std::cout << (passed ? "[pass]" : "[fail]") << " " << testname << std::endl;
}

int main() {
    errorcollector collector;
    
    // 测试1:基本添加和获取
    std::cout << "\n=== 测试1:基本功能 ===" << std::endl;
    collector.addinfo("数据库连接失败", 1001);
    collector.addinfo("配置文件缺失", 1002);
    collector.addinfo("用户认证超时", 0);  // code为0表示提示信息
    
    std::string infos = collector.getinfos(true);
    std::cout << "所有信息: " << infos << std::endl;
    std::cout << "仅错误: " << collector.getinfos(false) << std::endl;
    std::cout << "错误码: " << collector.getcodes() << std::endl;
    printtestresult("基本功能", infos == "数据库连接失败 | 配置文件缺失 | 用户认证超时");
    
    // 测试2:去重功能
    std::cout << "\n=== 测试2:去重功能 ===" << std::endl;
    errorcollector collector2;
    collector2.addinfo("重复错误", 100);
    collector2.addinfo("重复错误", 200);  // 相同消息,不同code
    collector2.addinfo("唯一错误", 300);
    
    std::string result = collector2.getinfos(true);
    std::cout << "去重后结果: " << result << std::endl;
    std::cout << "错误码: " << collector2.getcodes() << std::endl;
    printtestresult("去重功能", result == "重复错误 | 唯一错误" && collector2.getcodes() == "100|300");
    
    // 测试3:空字符串和特殊字符
    std::cout << "\n=== 测试3:空值和特殊字符过滤 ===" << std::endl;
    errorcollector collector3;
    collector3.addinfo("", 100);
    collector3.addinfo("\t\n\r", 200);
    collector3.addinfo("有效消息", 300);
    collector3.addinfo(nullptr, 400);
    
    std::string result3 = collector3.getinfos(true);
    std::cout << "过滤后结果: " << result3 << std::endl;
    printtestresult("空值过滤", result3 == "有效消息");
    printtestresult("空状态检测", !collector3.empty());
    
    // 测试4:特殊字符清理(制表符、换行符)
    std::cout << "\n=== 测试4:特殊字符清理 ===" << std::endl;
    errorcollector collector4;
    collector4.addinfo("消息\t包含\t制表符", 100);
    collector4.addinfo("第一行\n第二行\r\n", 200);
    
    std::string result4 = collector4.getinfos(true);
    std::cout << "清理后结果: " << result4 << std::endl;
    printtestresult("制表符清理", result4 == "消息包含制表符 | 第一行第二行");
    
    // 测试5:按错误码过滤
    std::cout << "\n=== 测试5:按错误码获取 ===" << std::endl;
    errorcollector collector5;
    collector5.addinfo("错误a", 100);
    collector5.addinfo("提示b", 0);
    collector5.addinfo("错误c", 200);
    collector5.addinfo("错误d", 300);
    
    std::cout << "错误码100的信息: " << collector5.getinfobycode(100) << std::endl;
    std::cout << "跳过错误码200: " << collector5.getcodes(200) << std::endl;
    printtestresult("按码获取", collector5.getinfobycode(100) == "错误a");
    printtestresult("跳过码获取", collector5.getcodes(200) == "100|300");
    
    // 测试6:格式验证
    std::cout << "\n=== 测试6:输出格式验证 ===" << std::endl;
    errorcollector collector6;
    collector6.addinfo("a", 1);
    collector6.addinfo("b", 2);
    collector6.addinfo("c", 3);
    
    std::string result6 = collector6.getinfos(true);
    std::cout << "格式结果: " << result6 << std::endl;
    bool formatok = (result6 == "a | b | c") && 
                    (result6.back() != '|') && 
                    (result6.front() != '|');
    printtestresult("格式正确", formatok);
    
    // 测试7:空状态检测
    std::cout << "\n=== 测试7:空状态检测 ===" << std::endl;
    errorcollector collector7;
    std::cout << "初始empty: " << (collector7.empty() ? "true" : "false") << std::endl;
    collector7.addinfo("测试", 100);
    std::cout << "添加后empty: " << (collector7.empty() ? "true" : "false") << std::endl;
    printtestresult("空状态检测", collector7.empty() == false);
    
    // 测试8:大量数据性能
    std::cout << "\n=== 测试8:大量数据性能 ===" << std::endl;
    errorcollector collector8;
    for (int i = 0; i < 1000; i++) {
        collector8.addinfo("错误_" + std::to_string(i), i);
    }
    std::string result8 = collector8.getinfos(true);
    bool haserror500 = result8.find("错误_500") != std::string::npos;
    std::cout << "包含第500条: " << (haserror500 ? "是" : "否") << std::endl;
    printtestresult("大量数据处理", haserror500);
    
    std::cout << "\n=== 所有测试完成 ===" << std::endl;
    return 0;
}

编译命令:

g++ -std=c++11  main.cpp

执行输出结果:

# ./a.out

=== 测试1:基本功能 ===
所有信息: 数据库连接失败 | 配置文件缺失 | 用户认证超时
仅错误: 数据库连接失败 | 配置文件缺失
错误码: 1001|1002
[pass] 基本功能

=== 测试2:去重功能 ===
去重后结果: 重复错误 | 唯一错误
错误码: 100|300
[pass] 去重功能

=== 测试3:空值和特殊字符过滤 ===
过滤后结果: 有效消息
[pass] 空值过滤
[pass] 空状态检测

=== 测试4:特殊字符清理 ===
清理后结果: 消息包含制表符 | 第一行第二行
[pass] 制表符清理

=== 测试5:按错误码获取 ===
错误码100的信息: 错误a
跳过错误码200: 100|300
[pass] 按码获取
[pass] 跳过码获取

=== 测试6:输出格式验证 ===
格式结果: a | b | c
[pass] 格式正确

=== 测试7:空状态检测 ===
初始empty: true
添加后empty: false
[pass] 空状态检测

=== 测试8:大量数据性能 ===
包含第500条: 是
[pass] 大量数据处理

=== 所有测试完成 ===

4.2 关键测试用例

用例1:基本功能验证

collector.addinfo("数据库连接失败", 1001);
collector.addinfo("配置文件缺失", 1002);
collector.addinfo("用户认证超时", 0);

用例2:去重验证

    collector2.addinfo("重复错误", 100);
    collector2.addinfo("重复错误", 200);  // 相同消息,不同code
    collector2.addinfo("唯一错误", 300);

用例3:特殊字符清理

    errorcollector collector4;
    collector4.addinfo("消息\t包含\t制表符", 100);
    collector4.addinfo("第一行\n第二行\r\n", 200);

4.3 测试结论

所有测试用例全部通过,验证了实现的正确性和健壮性。

测试项预期结果实际结果状态
基本添加和获取正确存储和检索通过
消息去重相同消息只保留一个通过
空值过滤空字符串被忽略通过
特殊字符清理移除\t\n\r通过
按错误码获取正确过滤通过
跳过指定错误码正确排除通过
输出格式竖线分隔,无多余符号通过
空状态检测状态正确通过
性能测试1000条数据正常处理通过

5. 其它说明

errorcollector虽然是一个简单的工具类,但它在实际项目中发挥了重要作用。以下是一些要点说明和未来改进方向:

  1. 其它信息收集errorcollector虽然名为错误收集器,但通过创建多个副本,可实现其它信息的收集。另外,可以根据
  2. 线程安全:在多线程环境下,需要添加互斥锁保护
  3. 容量限制:防止错误消息无限增长,可以设置最大容量
  4. 持久化:支持将错误信息序列化到文件或数据库

6. 小结

错误收集看似简单,但设计一个好用、健壮的错误收集器需要对使用场景有深入理解。errorcollector的实践证明,即使是小型工具类,只要设计得当,也能在项目中发挥"四两拨千斤"的效果。

在软件开发中,我们常常追求复杂而全面的解决方案,但有时候,一个简洁、专注的工具可能更符合"够用就好"的工程哲学。正如这个错误收集器所展示的:在正确的抽象层次上,解决正确的问题,就是好的设计

以上就是使用c++实现的错误信息收集器的详细内容,更多关于c++错误信息收集器的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com