Skip to content

Commit d36d778

Browse files
committed
feat(cli): group common network log disconnects
2 parents 97d0d94 + 503059c commit d36d778

2 files changed

Lines changed: 133 additions & 8 deletions

File tree

include/vix/cli/commands/logs/LogsAnalyzer.hpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ namespace vix::commands::logs::analyzer
3636
int count{0};
3737
};
3838

39+
/**
40+
* @struct NetworkDisconnectGroup
41+
* @brief Represents a known network disconnect family.
42+
*/
43+
struct NetworkDisconnectGroup
44+
{
45+
/**
46+
* @brief Human-readable group name.
47+
*/
48+
std::string name{};
49+
50+
/**
51+
* @brief Number of log lines matching this network group.
52+
*/
53+
int count{0};
54+
};
55+
3956
/**
4057
* @struct RepeatedLogReport
4158
* @brief Summary produced by repeated log analysis.
@@ -47,6 +64,11 @@ namespace vix::commands::logs::analyzer
4764
*/
4865
std::vector<RepeatedLogEntry> entries{};
4966

67+
/**
68+
* @brief Common network disconnect groups sorted by descending count.
69+
*/
70+
std::vector<NetworkDisconnectGroup> networkGroups{};
71+
5072
/**
5173
* @brief Total number of log lines analyzed.
5274
*/
@@ -56,17 +78,22 @@ namespace vix::commands::logs::analyzer
5678
* @brief Number of repeated message groups detected.
5779
*/
5880
int repeatedGroups{0};
81+
82+
/**
83+
* @brief Number of common network disconnect groups detected.
84+
*/
85+
int networkDisconnectGroups{0};
5986
};
6087

6188
/**
6289
* @brief Analyze log lines and detect repeated normalized error messages.
6390
*
6491
* This function normalizes variable parts of log lines, such as timestamps,
6592
* IP addresses, numbers, and long values, then groups equivalent messages
66-
* together.
93+
* together. It also detects common network disconnect families.
6794
*
6895
* @param lines Raw log lines to analyze.
69-
* @return Report containing repeated error groups and counters.
96+
* @return Report containing repeated error groups and network disconnect groups.
7097
*/
7198
RepeatedLogReport analyze_repeated_errors(
7299
const std::vector<std::string> &lines);

src/commands/logs/LogsAnalyzer.cpp

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,51 @@ namespace vix::commands::logs::analyzer
9191

9292
return line;
9393
}
94+
95+
std::string detect_network_group(const std::string &line)
96+
{
97+
if (line.find("connection reset by peer") != std::string::npos ||
98+
line.find("reset by peer") != std::string::npos)
99+
{
100+
return "connection reset by peer";
101+
}
102+
103+
if (line.find("client closed connection") != std::string::npos ||
104+
line.find("client prematurely closed connection") != std::string::npos ||
105+
line.find("broken pipe") != std::string::npos)
106+
{
107+
return "client disconnected";
108+
}
109+
110+
if (line.find("upstream prematurely closed connection") != std::string::npos)
111+
{
112+
return "upstream disconnected";
113+
}
114+
115+
if (line.find("connection refused") != std::string::npos ||
116+
line.find("connect() failed") != std::string::npos ||
117+
line.find("connect failed") != std::string::npos)
118+
{
119+
return "connection refused";
120+
}
121+
122+
if (line.find("timed out") != std::string::npos ||
123+
line.find("timeout") != std::string::npos ||
124+
line.find("upstream timed out") != std::string::npos)
125+
{
126+
return "timeout";
127+
}
128+
129+
if (line.find("websocket") != std::string::npos &&
130+
(line.find("close") != std::string::npos ||
131+
line.find("closed") != std::string::npos ||
132+
line.find("disconnect") != std::string::npos))
133+
{
134+
return "websocket disconnected";
135+
}
136+
137+
return {};
138+
}
94139
}
95140

96141
RepeatedLogReport analyze_repeated_errors(
@@ -99,7 +144,8 @@ namespace vix::commands::logs::analyzer
99144
RepeatedLogReport report;
100145
report.totalLines = static_cast<int>(lines.size());
101146

102-
std::unordered_map<std::string, int> counts;
147+
std::unordered_map<std::string, int> repeatedCounts;
148+
std::unordered_map<std::string, int> networkCounts;
103149

104150
for (const std::string &line : lines)
105151
{
@@ -108,10 +154,16 @@ namespace vix::commands::logs::analyzer
108154
if (normalized.empty())
109155
continue;
110156

111-
++counts[normalized];
157+
++repeatedCounts[normalized];
158+
159+
const std::string networkGroup =
160+
detect_network_group(normalized);
161+
162+
if (!networkGroup.empty())
163+
++networkCounts[networkGroup];
112164
}
113165

114-
for (const auto &[message, count] : counts)
166+
for (const auto &[message, count] : repeatedCounts)
115167
{
116168
if (count <= 1)
117169
continue;
@@ -122,6 +174,14 @@ namespace vix::commands::logs::analyzer
122174
count});
123175
}
124176

177+
for (const auto &[name, count] : networkCounts)
178+
{
179+
report.networkGroups.push_back(
180+
NetworkDisconnectGroup{
181+
name,
182+
count});
183+
}
184+
125185
std::sort(
126186
report.entries.begin(),
127187
report.entries.end(),
@@ -133,9 +193,23 @@ namespace vix::commands::logs::analyzer
133193
return a.message < b.message;
134194
});
135195

196+
std::sort(
197+
report.networkGroups.begin(),
198+
report.networkGroups.end(),
199+
[](const NetworkDisconnectGroup &a, const NetworkDisconnectGroup &b)
200+
{
201+
if (a.count != b.count)
202+
return a.count > b.count;
203+
204+
return a.name < b.name;
205+
});
206+
136207
report.repeatedGroups =
137208
static_cast<int>(report.entries.size());
138209

210+
report.networkDisconnectGroups =
211+
static_cast<int>(report.networkGroups.size());
212+
139213
return report;
140214
}
141215

@@ -160,16 +234,40 @@ namespace vix::commands::logs::analyzer
160234
vix::cli::util::ok_line(
161235
out,
162236
"no repeated errors detected");
237+
}
238+
else
239+
{
240+
for (const RepeatedLogEntry &entry : report.entries)
241+
{
242+
vix::cli::util::kv(
243+
out,
244+
std::to_string(entry.count) + "x",
245+
entry.message);
246+
}
247+
}
248+
249+
vix::cli::util::section(out, "Common Network Disconnects");
250+
251+
vix::cli::util::kv(
252+
out,
253+
"Detected groups",
254+
std::to_string(report.networkDisconnectGroups));
255+
256+
if (report.networkGroups.empty())
257+
{
258+
vix::cli::util::ok_line(
259+
out,
260+
"no common network disconnects detected");
163261

164262
return;
165263
}
166264

167-
for (const RepeatedLogEntry &entry : report.entries)
265+
for (const NetworkDisconnectGroup &group : report.networkGroups)
168266
{
169267
vix::cli::util::kv(
170268
out,
171-
std::to_string(entry.count) + "x",
172-
entry.message);
269+
std::to_string(group.count) + "x",
270+
group.name);
173271
}
174272
}
175273
}

0 commit comments

Comments
 (0)