Skip to content

Commit c235304

Browse files
committed
feat(report): add report schema identity
1 parent 47fccd6 commit c235304

9 files changed

Lines changed: 30 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable user-visible changes should be recorded here.
77
### Added
88

99
- Added sanitized golden `report.md` / `report.json` regression fixtures to lock report contracts.
10+
- Added `schema` and `schema_version` fields to `report.json` so downstream tooling can identify the report artifact contract.
1011
- Expanded parser coverage for `Accepted publickey` and selected `pam_faillock` / `pam_sss` variants.
1112
- Added compact host-level summaries for multi-host reports.
1213
- Added optional CSV export for findings and warnings when explicitly requested.

docs/report-artifacts.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Without `--csv`, LogLens does not create, overwrite, or delete existing CSV file
1818
The JSON report keeps parser observability visible next to findings:
1919

2020
- `tool`
21+
- `schema`
22+
- `schema_version`
2123
- `input`
2224
- `input_mode`
2325
- `assume_year` for syslog-style input when a year is supplied
@@ -44,6 +46,11 @@ Finding objects contain `rule_id`, `rule`, `subject_kind`, `subject`, `grouping_
4446

4547
Warning objects contain the original `line_number`, parser `category`, and parser `reason`.
4648

49+
`schema` and `schema_version` identify the report artifact contract, not the
50+
application release. They are intended for downstream tooling that needs a
51+
stable way to reject incompatible report shapes. The current JSON contract is
52+
`loglens.report.v1` with `schema_version` set to `1`.
53+
4754
Parser failure categories are stable reviewer-facing buckets for unsupported
4855
lines: `unknown_timestamp`, `unknown_program`,
4956
`known_program_unknown_message`, `malformed_source_ip`, and

src/report.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,8 @@ std::string render_json_report(const ReportData& data) {
637637

638638
output << "{\n";
639639
output << " \"tool\": \"LogLens\",\n";
640+
output << " \"schema\": \"loglens.report.v1\",\n";
641+
output << " \"schema_version\": 1,\n";
640642
output << " \"input\": \"" << escape_json(data.input_path.generic_string()) << "\",\n";
641643
output << " \"input_mode\": \"" << to_string(data.parse_metadata.input_mode) << "\",\n";
642644
if (data.parse_metadata.assume_year.has_value()) {

tests/fixtures/report_contracts/journalctl_short_full/report.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"tool": "LogLens",
3+
"schema": "loglens.report.v1",
4+
"schema_version": 1,
35
"input": "tests/fixtures/report_contracts/journalctl_short_full/input.log",
46
"input_mode": "journalctl_short_full",
57
"timezone_present": true,

tests/fixtures/report_contracts/multi_host_journalctl_short_full/report.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"tool": "LogLens",
3+
"schema": "loglens.report.v1",
4+
"schema_version": 1,
35
"input": "tests/fixtures/report_contracts/multi_host_journalctl_short_full/input.log",
46
"input_mode": "journalctl_short_full",
57
"timezone_present": true,

tests/fixtures/report_contracts/multi_host_syslog_legacy/report.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"tool": "LogLens",
3+
"schema": "loglens.report.v1",
4+
"schema_version": 1,
35
"input": "tests/fixtures/report_contracts/multi_host_syslog_legacy/input.log",
46
"input_mode": "syslog_legacy",
57
"assume_year": 2026,

tests/fixtures/report_contracts/syslog_legacy/report.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"tool": "LogLens",
3+
"schema": "loglens.report.v1",
4+
"schema_version": 1,
35
"input": "tests/fixtures/report_contracts/syslog_legacy/input.log",
46
"input_mode": "syslog_legacy",
57
"assume_year": 2026,

tests/test_report.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ void test_json_finding_includes_explainability_fields() {
183183
"expected json finding to include evidence event ids");
184184
}
185185

186+
void test_json_report_includes_schema_identity() {
187+
const auto json = loglens::render_json_report(make_report_data());
188+
189+
expect(json.find("\"schema\": \"loglens.report.v1\"") != std::string::npos,
190+
"expected json report to include schema identifier");
191+
expect(json.find("\"schema_version\": 1") != std::string::npos,
192+
"expected json report to include schema version");
193+
}
194+
186195
void test_reports_include_total_input_line_count() {
187196
auto data = make_report_data();
188197
data.parser_quality.total_lines = 3;
@@ -349,6 +358,7 @@ int main() {
349358
test_markdown_table_cells_escape_user_controlled_values();
350359
test_json_escapes_generic_control_characters();
351360
test_json_finding_includes_explainability_fields();
361+
test_json_report_includes_schema_identity();
352362
test_reports_include_total_input_line_count();
353363
test_csv_neutralizes_formula_like_fields();
354364
test_write_reports_fails_when_report_path_is_directory();

tests/test_report_contracts.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ std::vector<std::string> extract_json_contract_lines(const std::string& json) {
138138
}
139139

140140
if (starts_with(line, "\"tool\": ")
141+
|| starts_with(line, "\"schema\": ")
142+
|| starts_with(line, "\"schema_version\": ")
141143
|| starts_with(line, "\"input\": ")
142144
|| starts_with(line, "\"input_mode\": ")
143145
|| starts_with(line, "\"assume_year\": ")

0 commit comments

Comments
 (0)