feat: add optional CSV export for findings and warnings#24
feat: add optional CSV export for findings and warnings#24
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1499c4b8f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| return 0; | ||
| } | ||
| #include "config.hpp" #include "detector.hpp" #include "parser.hpp" #include "report.hpp" #include <charconv> #include <filesystem> #include <iostream> #include <optional> #include <stdexcept> #include <string_view> namespace { struct CliOptions { std::optional<std::filesystem::path> config_path; std::optional<loglens::InputMode> input_mode; std::optional<int> assumed_year; bool emit_csv = false; std::filesystem::path input_path; std::filesystem::path output_directory; }; void print_usage() { std::cerr << "Usage: loglens [--config <config.json>] [--mode <syslog|journalctl-short-full>] [--year <YYYY>] [--csv] <input_log> [output_dir]\n"; } int parse_year_argument(std::string_view value) { int parsed_year = 0; const auto* begin = value.data(); const auto* end = value.data() + value.size(); const auto result = std::from_chars(begin, end, parsed_year); if (result.ec != std::errc{} || result.ptr != end || parsed_year <= 0) { throw std::runtime_error("invalid year value: " + std::string(value)); } return parsed_year; } CliOptions parse_cli_options(int argc, char* argv[]) { if (argc < 2) { throw std::runtime_error("missing required arguments"); } int index = 1; CliOptions options; while (index < argc) { const std::string_view argument = argv[index]; if (argument == "--config") { if (index + 1 >= argc) { throw std::runtime_error("missing path after --config"); } options.config_path = std::filesystem::path{argv[index + 1]}; index += 2; continue; } if (argument == "--mode") { if (index + 1 >= argc) { throw std::runtime_error("missing value after --mode"); } const auto parsed_mode = loglens::parse_input_mode(argv[index + 1]); if (!parsed_mode.has_value()) { throw std::runtime_error("unsupported mode: " + std::string{argv[index + 1]}); } options.input_mode = *parsed_mode; index += 2; continue; } if (argument == "--year") { if (index + 1 >= argc) { throw std::runtime_error("missing value after --year"); } options.assumed_year = parse_year_argument(argv[index + 1]); index += 2; continue; } if (argument == "--csv") { options.emit_csv = true; ++index; continue; } if (argument.starts_with('-')) { throw std::runtime_error("unknown option: " + std::string{argv[index]}); } break; } const int remaining = argc - index; if (remaining < 1 || remaining > 2) { throw std::runtime_error("invalid argument count"); } options.input_path = std::filesystem::path{argv[index]}; options.output_directory = remaining == 2 ? std::filesystem::path{argv[index + 1]} : std::filesystem::current_path(); return options; } loglens::ParserConfig resolve_parser_config(const CliOptions& options, const loglens::AppConfig& config) { const auto resolved_mode = options.input_mode.has_value() ? options.input_mode : config.input_mode; if (!resolved_mode.has_value()) { throw std::runtime_error("input mode is required; use --mode or input_mode in config.json"); } loglens::ParserConfig parser_config; parser_config.input_mode = *resolved_mode; if (parser_config.input_mode == loglens::InputMode::SyslogLegacy) { parser_config.assumed_year = options.assumed_year.has_value() ? options.assumed_year : config.timestamp.assume_year; if (!parser_config.assumed_year.has_value()) { throw std::runtime_error("syslog mode requires --year or timestamp.assume_year in config.json"); } } return parser_config; } } // namespace int main(int argc, char* argv[]) { CliOptions options; try { options = parse_cli_options(argc, argv); } catch (const std::exception& error) { print_usage(); std::cerr << "LogLens failed: " << error.what() << '\n'; return 1; } try { const auto app_config = options.config_path.has_value() ? loglens::load_app_config(*options.config_path) : loglens::AppConfig{}; const auto parser_config = resolve_parser_config(options, app_config); const loglens::AuthLogParser parser(parser_config); const auto parsed = parser.parse_file(options.input_path); const loglens::Detector detector(app_config.detector); const auto findings = detector.analyze(parsed.events); const loglens::ReportData report_data{ options.input_path, parsed.metadata, parsed.quality, parsed.events, findings, parsed.warnings, app_config.detector.auth_signal_mappings}; loglens::write_reports(report_data, options.output_directory, options.emit_csv); std::cout << "Parsed events: " << parsed.events.size() << '\n'; std::cout << "Findings: " << findings.size() << '\n'; std::cout << "Warnings: " << parsed.warnings.size() << '\n'; std::cout << "Markdown report: " << (options.output_directory / "report.md").string() << '\n'; std::cout << "JSON report: " << (options.output_directory / "report.json").string() << '\n'; if (options.emit_csv) { std::cout << "Findings CSV: " << (options.output_directory / "findings.csv").string() << '\n'; std::cout << "Warnings CSV: " << (options.output_directory / "warnings.csv").string() << '\n'; } } catch (const std::exception& error) { std::cerr << "LogLens failed: " << error.what() << '\n'; return 1; } return 0; } No newline at end of file |
There was a problem hiding this comment.
Preserve line breaks in C++ translation units
This commit rewrites src/main.cpp as a single physical line, which causes the preprocessor to treat nearly the whole file as a malformed #include directive instead of compiling the program body. In a detached checkout of 375fba0, cmake --build fails when linking loglens with undefined reference to main; the same line-flattening pattern also appears in src/report.cpp, src/report.hpp, and the modified tests. As committed, the project is not buildable.
Useful? React with 👍 / 👎.
|
Closing this PR because the branch content was published with corrupted newlines. Replacing it with a fresh branch rebuilt from latest main. |
Closes #23
Summary
--csvCLI option for optional CSV exportfindings.csvandwarnings.csvalongside the existing reports only when CSV is explicitly requestedScope
This PR is limited to report and output-format behavior.
It does not change parser behavior, detector thresholds, enrichment, cross-host correlation, or existing Markdown/JSON report semantics.
This is a report-only change; no detector thresholds changed.
What changed
--csvas an explicit CLI flagfindings.csvandwarnings.csvfixtures for one single-host and one multi-host sanitized caseCSV schema
findings.csvrulesubject_kindsubjectevent_countwindow_startwindow_endusernamessummarywarnings.csvkindmessageVerification
cmake --preset dev-debugcmake --build --preset dev-debugctest --preset dev-debugcmake --preset ci-releasecmake --build --preset ci-releasectest --preset ci-releaseDeferred