Skip to content

Commit 09e1396

Browse files
committed
feat(cli): support JSON log analysis output
2 parents e4a8af0 + 8545888 commit 09e1396

5 files changed

Lines changed: 78 additions & 7 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ namespace vix::commands::logs::analyzer
112112
void print_repeated_report(
113113
std::ostream &out,
114114
const RepeatedLogReport &report);
115+
116+
/**
117+
* @brief Print a repeated error analysis report as JSON.
118+
*
119+
* @param out Output stream.
120+
* @param report Repeated error report to print.
121+
*/
122+
void print_repeated_report_json(
123+
std::ostream &out,
124+
const RepeatedLogReport &report);
115125
}
116126

117127
#endif

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ namespace vix::commands::logs
5656
*/
5757
bool repeated{false};
5858

59+
/**
60+
* @brief Print machine-readable JSON output when supported.
61+
*/
62+
bool json{false};
63+
5964
/**
6065
* @brief Number of lines to show.
6166
*/

src/commands/LogsCommand.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ namespace vix::commands
150150
consume_flag(args, "--errors");
151151

152152
options.repeated = consume_flag(args, "--repeated");
153+
options.json = consume_flag(args, "--json");
153154

154155
if (!consume_value(args, "--since", options.since))
155156
{
@@ -242,6 +243,7 @@ namespace vix::commands
242243
<< " -f Alias for --follow\n"
243244
<< " --errors Filter logs by common error keywords\n"
244245
<< " --repeated Detect repeated errors\n"
246+
<< " --json Print supported output as JSON\n"
245247
<< " --since Filter app logs by systemd time expression\n"
246248
<< " --lines Show last N lines\n"
247249
<< " -n Alias for --lines\n"
@@ -254,6 +256,7 @@ namespace vix::commands
254256
<< " vix logs --follow\n"
255257
<< " vix logs --errors\n"
256258
<< " vix logs errors --repeated\n"
259+
<< " vix logs errors --repeated --json\n"
257260
<< " vix logs --since \"1 hour ago\"\n"
258261
<< " vix logs -n 200\n\n"
259262
<< "Config:\n"

src/commands/logs/LogsAnalyzer.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <vix/cli/commands/logs/LogsAnalyzer.hpp>
1414
#include <vix/cli/util/Ui.hpp>
1515

16+
#include <nlohmann/json.hpp>
17+
1618
#include <algorithm>
1719
#include <cctype>
1820
#include <ostream>
@@ -21,6 +23,8 @@
2123
#include <unordered_map>
2224
#include <vector>
2325

26+
using json = nlohmann::json;
27+
2428
namespace vix::commands::logs::analyzer
2529
{
2630
namespace
@@ -297,4 +301,40 @@ namespace vix::commands::logs::analyzer
297301
group.name);
298302
}
299303
}
304+
305+
void print_repeated_report_json(
306+
std::ostream &out,
307+
const RepeatedLogReport &report)
308+
{
309+
json root = json::object();
310+
311+
root["analyzed_lines"] = report.totalLines;
312+
root["repeated_groups"] = report.repeatedGroups;
313+
root["network_disconnect_groups"] = report.networkDisconnectGroups;
314+
root["hidden_normal_noise_lines"] = report.hiddenNormalNoiseLines;
315+
316+
root["repeated_errors"] = json::array();
317+
318+
for (const RepeatedLogEntry &entry : report.entries)
319+
{
320+
root["repeated_errors"].push_back(
321+
{
322+
{"message", entry.message},
323+
{"count", entry.count},
324+
});
325+
}
326+
327+
root["network_disconnects"] = json::array();
328+
329+
for (const NetworkDisconnectGroup &group : report.networkGroups)
330+
{
331+
root["network_disconnects"].push_back(
332+
{
333+
{"name", group.name},
334+
{"count", group.count},
335+
});
336+
}
337+
338+
out << root.dump(2) << '\n';
339+
}
300340
}

src/commands/logs/LogsRunner.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,11 @@ namespace vix::commands::logs::runner
194194
const std::string appCommand =
195195
build_journalctl_analysis_command(cfg, options);
196196

197-
output::step(std::cout, "Analyze App Errors");
198-
output::ok(std::cout, "reading systemd app errors");
197+
if (!options.json)
198+
{
199+
output::step(std::cout, "Analyze App Errors");
200+
output::ok(std::cout, "reading systemd app errors");
201+
}
199202

200203
std::vector<std::string> appLines =
201204
read_command_lines(appCommand);
@@ -215,8 +218,11 @@ namespace vix::commands::logs::runner
215218
options,
216219
false);
217220

218-
output::step(std::cout, "Analyze Proxy Errors");
219-
output::ok(std::cout, "reading Nginx proxy errors");
221+
if (!options.json)
222+
{
223+
output::step(std::cout, "Analyze Proxy Errors");
224+
output::ok(std::cout, "reading Nginx proxy errors");
225+
}
220226

221227
std::vector<std::string> proxyLines =
222228
read_command_lines(proxyCommand);
@@ -226,7 +232,7 @@ namespace vix::commands::logs::runner
226232
proxyLines.begin(),
227233
proxyLines.end());
228234
}
229-
else
235+
else if (!options.json)
230236
{
231237
output::warn(
232238
std::cerr,
@@ -240,7 +246,10 @@ namespace vix::commands::logs::runner
240246
const analyzer::RepeatedLogReport report =
241247
analyzer::analyze_repeated_errors(lines);
242248

243-
analyzer::print_repeated_report(std::cout, report);
249+
if (options.json)
250+
analyzer::print_repeated_report_json(std::cout, report);
251+
else
252+
analyzer::print_repeated_report(std::cout, report);
244253

245254
return true;
246255
}
@@ -356,7 +365,11 @@ namespace vix::commands::logs::runner
356365
const LogsConfig &cfg,
357366
const LogsOptions &options)
358367
{
359-
output::print_summary(std::cout, cfg, options);
368+
if (!options.json)
369+
output::print_summary(std::cout, cfg, options);
370+
371+
if (options.repeated)
372+
return show_repeated_errors(cfg, options) ? 0 : 1;
360373

361374
if (options.repeated)
362375
return show_repeated_errors(cfg, options) ? 0 : 1;

0 commit comments

Comments
 (0)