C++如何按第二列sessionid合并邮件日志并过滤不完整会话
邮件日志会话合并实现
问题背景
现有email.log邮件日志文件,截取的日志样例如下:
2021-04-30T23:55:00.127629 886715E6D6C9D4FB status=rejected 2021-04-30T23:55:00.791921 F8F63278A6A3AD87 from=<sarah.smith@example.com> 2021-04-30T23:55:01.470432 418512384DDDD2C6 from=<robert.rodriguez@example.com> 2021-04-30T23:55:01.697902 0D8760D4ADAB456D message-id=<696b66ea-f493-4ba2-9e8a-553bd03b7d37@2PPZOR2ULU> 2021-04-30T23:55:01.736492 0D8760D4ADAB456D from=<william.smith@example.com> 2021-04-30T23:55:02.043100 0D8760D4ADAB456D to=<william.davis@example.com> 2021-04-30T23:55:02.842802 EC5AD35BADC381F9 client=10.2.38.215 2021-04-30T23:55:03.132660 2AB0E95297136E70 client=2001:db8::8f75:20e2:c47f 2021-04-30T23:55:03.550296 BAB22895DB867DFF status=sent 2021-04-30T23:55:04.392986 5BE423F6370D1D1B client=10.38.217.222 2021-04-30T23:55:04.661467 5D11914582F8C85A client=2001:db8::7bbb:6743:c8c5 2021-04-30T23:55:05.306358 E2E8D917BB751176 message-id=<da3d4d7c-643c-4a15-989a-3cd6269030f4@30Q7E75V7B> 2021-04-30T23:55:05.872830 F8F63278A6A3AD87 to=<patricia.garcia@example.com> 2021-04-30T23:55:06.272336 F8F63278A6A3AD87 status=sent 2021-04-30T23:55:06.716495 C7CC8201A67C8E52 from=<thomas.wilson@example.com> 2021-04-30T23:55:07.056882 5BE423F6370D1D1B message-id=<d5db8bc8-871e-48de-9e64-e23fca0c0134@56WZW5K1C2> 2021-04-30T23:55:07.113379 0D8760D4ADAB456D status=sent 2021-04-30T23:55:07.370491 5D11914582F8C85A message-id=<b041aedf-07ec-4e67-9a1a-e9f23a6ab434@82AZ76YJPI> 2021-04-30T23:55:07.732459 2AB0E95297136E70 message-id=<c948f7eb-144c-4688-b122-1191eea2cb29@4Q1QAJ0BLI> 2021-04-30T23:55:08.608998 C1D805D68377D513 to=<karen.smith@example.com> 2021-04-30T23:55:08.782778 418512384DDDD2C6 to=<jessica.jones@example.com> 2021-04-30T23:55:09.383173 E2E8D917BB751176 from=<barbara.rodriguez@example.com> 2021-04-30T23:55:09.676896 33DD2B20F2AB9262 status=sent 2021-04-30T23:55:10.157677 452FAD67C2867C47 client=10.38.217.222 2021-04-30T23:55:11.064902 C7CC8201A67C8E52 to=<patricia.jones@example.com> 2021-04-30T23:55:11.709673 E2E8D917BB751176 to=<richard.garcia@example.com> 2021-04-30T23:55:12.667447 EC5AD35BADC381F9 message-id=<b2fd4dac-2513-4895-ac84-3c68ecabc3ec@U83K85L7HK>
当前已实现日志读取到Session结构体向量的逻辑,需要实现以下需求:
- 按第二列的sessionid字段合并同一会话的所有事件
- 兼容日志事件并行、时间重叠的场景
- 缺少任意必填字段的不完整会话直接忽略
已编写的初始代码如下:
#include <iostream> #include <string> #include <fstream> #include <vector> #include <algorithm> using namespace std; struct Session { string time; string sessionid; string other; friend istream& operator>>(istream& input, Session& session); friend ostream& operator<<(ostream& output, Session& session); }; istream& operator>>(istream& input, Session& session) { input >> session.time; input >> session.sessionid; input >> session.other; return input; } ostream& operator<<(ostream& output, Session& session) { output << session.time; output << session.sessionid; output << session.other; return output; } int main() { vector<Session> session; vector<Session> complete_session; Session record; ifstream read("email.log"); while (read >> record) { session.push_back(record); } read.close(); return 0; }
实现思路
- 重构Session结构体:原结构用单一
other字段存储所有属性无法做字段校验,拆分出邮件会话的核心字段:客户端IP、message-id、发件人、收件人、投递状态,同时保留会话最早时间、sessionid两个基础字段。 - 哈希表聚合:用
unordered_map<string, Session>做中间聚合容器,键为sessionid,值为对应会话的聚合对象。遍历所有日志行时,解析每行第三列的键值对,填充到对应sessionid的Session对象中,自动覆盖同字段重复值(日志顺序中后出现的同字段值为准,符合日志写入逻辑)。 - 完整性校验:所有日志行遍历完成后,遍历哈希表中的所有会话,校验5个核心字段是否全部非空,非空则为完整会话,加入结果向量;缺任意字段则直接丢弃。
完整实现代码
#include <iostream> #include <string> #include <fstream> #include <vector> #include <unordered_map> using namespace std; struct Session { string first_time; // 会话最早出现的时间 string sessionid; // 核心必填字段 string client; string message_id; string from; string to; string status; // 校验是否为完整会话 bool is_complete() const { return !client.empty() && !message_id.empty() && !from.empty() && !to.empty() && !status.empty(); } }; // 解析单行日志为临时结构,只做基础字段拆分 struct LogLine { string time; string sessionid; string key; string value; friend istream& operator>>(istream& input, LogLine& line) { string kv; if (!(input >> line.time >> line.sessionid >> kv)) { return input; } // 拆分key=value size_t eq_pos = kv.find('='); if (eq_pos == string::npos) { input.setstate(ios::failbit); return input; } line.key = kv.substr(0, eq_pos); line.value = kv.substr(eq_pos + 1); return input; } }; int main() { vector<Session> complete_session; unordered_map<string, Session> session_map; LogLine line; ifstream read("email.log"); while (read >> line) { Session& s = session_map[line.sessionid]; // 新会话初始化基础字段 if (s.sessionid.empty()) { s.sessionid = line.sessionid; s.first_time = line.time; } // 根据key填充对应字段 if (line.key == "client") { s.client = line.value; } else if (line.key == "message-id") { s.message_id = line.value; } else if (line.key == "from") { s.from = line.value; } else if (line.key == "to") { s.to = line.value; } else if (line.key == "status") { s.status = line.value; } } read.close(); // 筛选完整会话 for (auto& pair : session_map) { if (pair.second.is_complete()) { complete_session.push_back(pair.second); } } // 测试输出,可按需删除 for (auto& s : complete_session) { cout << "完整会话: " << s.sessionid << endl; cout << "时间: " << s.first_time << endl; cout << "客户端: " << s.client << endl; cout << "Message-ID: " << s.message_id << endl; cout << "发件人: " << s.from << endl; cout << "收件人: " << s.to << endl; cout << "状态: " << s.status << endl << endl; } return 0; }
说明
- 该实现不依赖日志顺序,无论同sessionid的日志行是否连续、是否和其他会话日志交叉出现,都能正确聚合,适配并行会话、时间重叠的场景。
- 字段解析逻辑可按需扩展,如果后续新增其他必填字段,只需要在Session结构体中新增对应字段、在解析分支增加赋值逻辑、在
is_complete方法中增加非空判断即可。 - 样例日志运行后,会输出
0D8760D4ADAB456D、F8F63278A6A3AD87两个完整会话,其余缺字段的会话会被自动过滤。
内容的提问来源于stack exchange,提问作者Dominic Ask
相关产品推荐
相关产品推荐

