Skip to content

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] - #302

Merged
richm merged 2 commits into
mainfrom
fingerprint-write-to-file
Aug 6, 2026
Merged

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]#302
richm merged 2 commits into
mainfrom
fingerprint-write-to-file

Conversation

@spetrosi

@spetrosi spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl

@spetrosi spetrosi self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (1)
  • [citest_skip]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 642cf1cb-f176-4374-a784-6ee6e56941fd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The module now collects structured role fingerprints, formats them for syslog and JSONL, optionally persists records with locking and trimming, supports check mode, and validates write and size errors. Unit tests cover formatting, metadata, persistence, trimming, execution, and timestamps.

Changes

Structured fingerprint logging

Layer / File(s) Summary
Fingerprint contract and record formatting
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module derives fingerprint metadata, builds canonical records, and formats deterministic syslog and JSONL output. Tests cover fields, metadata, quoting, and timestamps.
Locked JSONL persistence and trimming
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module creates parent directories, appends JSONL records under a lock, and removes older records when the size limit is exceeded. Tests cover append, JSON types, trimming, and disabled limits.
Module interface and execution handling
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module accepts structured arguments, supports check mode, validates negative sizes, and converts write failures to fail_json. Tests cover handler responses and errors.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description Format ⚠️ Warning The description includes Reason, Result, and Signed-off-by with an email, but it has neither the required Enhancement: nor Feature: section. Add an Enhancement: or Feature: section that describes the change, while retaining the existing Reason, Result, and Signed-off-by sections.
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits format and accurately describes the fingerprint log file feature.
Description check ✅ Passed The description explains the feature, reason, and result, but it uses Feature instead of Enhancement and omits the issue tracker section.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
tests/unit/test_sr_fingerprint.py (4)

121-122: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the partial-distro cases.

_get_managed_node_distro uses if distribution and distribution_version, so it returns "unknown" when either value is empty. This test covers only the both-empty case. The single-empty cases are the realistic ones, because a fact-gathering failure usually drops one value and keeps the other.

Adding them pins the and semantics. A future change to or would otherwise pass this test while emitting RedHat- into the fingerprint.

💚 Proposed additional assertions
     def test_get_managed_node_distro_missing(self):
         self.assertEqual(sr_fingerprint._get_managed_node_distro("", ""), "unknown")
+        self.assertEqual(
+            sr_fingerprint._get_managed_node_distro("RedHat", ""), "unknown"
+        )
+        self.assertEqual(sr_fingerprint._get_managed_node_distro("", "9.4"), "unknown")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 121 - 122, Add assertions to
test_get_managed_node_distro_missing covering both partial-distro inputs: a
non-empty distribution with an empty version and an empty distribution with a
non-empty version. Verify each returns "unknown", preserving the current and
semantics and guarding against emitting an incomplete fingerprint.

306-326: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the non-check-mode syslog path.

No test covers _handle_fingerprint with check_mode=False and write_log_file=False. That path calls module.log(log_message) at line 320 of library/sr_fingerprint.py and then exit_json(changed=False, fingerprint=...) at line 333. It is the main runtime path for the begin status, which the PR describes as syslog-only.

_FakeModule.log already appends to self.logged, but no test asserts that attribute. The syslog emission is therefore unverified end to end.

💚 Proposed additional test
    def test_handle_fingerprint_logs_to_syslog_without_log_file(self):
        module = _FakeModule(
            {
                "status": "begin",
                "write_log_file": False,
                "max_log_size": 2000000,
                "role_name": "systemd",
                "role_path": "/usr/share/ansible/roles/linux-system-roles.systemd",
                "ansible_play_hosts_all": ["host1", "host2"],
                "distribution": "RedHat",
                "distribution_version": "9.4",
            },
            check_mode=False,
        )
        with self.assertRaises(_ExitJsonException) as ctx:
            sr_fingerprint._handle_fingerprint(module)

        self.assertEqual(len(module.logged), 1)
        message = module.logged[0]
        self.assertIn("role_name=systemd", message)
        self.assertIn("status=begin", message)
        self.assertIn("play_hosts_number=2", message)

        result = ctx.exception.kwargs
        self.assertFalse(result["changed"])
        self.assertEqual(result["fingerprint"]["status"], "begin")
        self.assertNotIn("jsonl_row", result)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 306 - 326, Add a unit test
alongside test_handle_fingerprint_check_mode_without_log_file covering
_handle_fingerprint with check_mode=False and write_log_file=False. Assert
exactly one message is appended to module.logged and includes role_name=systemd,
status=begin, and play_hosts_number=2; then verify the exit result is unchanged,
contains the begin fingerprint, and omits jsonl_row.

274-288: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a test for a record larger than max_size.

No test covers the case where a single serialized record exceeds max_size. In that case _trim_log_file drains every line through the while lines exhaustion condition, and _write_jsonl_log still appends the oversized record. The resulting file exceeds max_size.

That behavior is reasonable, because the alternative is to drop the record. A test would pin it and document the intent for a misconfigured max_log_size.

💚 Proposed additional test
    def test_record_larger_than_max_size_is_still_written(self):
        with tempfile.NamedTemporaryFile(delete=False, suffix=".jsonl") as tmp:
            log_file = tmp.name

        try:
            record = _sample_fingerprint_record()
            line_size = len(sr_fingerprint._format_fingerprint_jsonl(record) + "\n")
            sr_fingerprint._write_jsonl_log(log_file, record, max_size=line_size)
            # The next write cannot fit; every older record is removed.
            sr_fingerprint._write_jsonl_log(log_file, record, max_size=line_size // 2)

            with open(log_file, "r") as log_fd:
                lines = log_fd.read().splitlines()

            self.assertEqual(len(lines), 1)
            self.assertEqual(json.loads(lines[0]), record)
        finally:
            _cleanup_log(log_file)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 274 - 288, Extend the test
coverage in the existing fingerprint log tests with a case for _write_jsonl_log
where the serialized record exceeds max_size. Verify that older entries are
removed as needed but the oversized record is still written as the sole
remaining line, and assert its decoded contents match the original record.

131-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Run Black before committing.

tests/unit/test_sr_fingerprint.py:133-135 has a parenthesized string assignment that can fit on one line. The project requires Python Black formatting, so run tox -e black,flake8 and commit the formatter’s result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 131 - 140, Run Black on
tests/unit/test_sr_fingerprint.py, allowing the parenthesized role_path
assignment in test_format_fingerprint_syslog_quotes_values_with_spaces to be
collapsed onto one line. Verify the result with tox -e black,flake8.

Source: Path instructions

library/sr_fingerprint.py (1)

328-331: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider widening the failure handling around the JSONL write.

The handler catches (IOError, OSError). _trim_log_file opens the existing log file in text mode with the locale default encoding. If the log file contains non-ASCII bytes that the locale codec rejects, Python raises UnicodeDecodeError, which is a ValueError and not an OSError. That exception escapes this handler and produces a raw traceback instead of a clean module failure.

This module writes ASCII-only lines, because json.dumps defaults to ensure_ascii=True. The decode failure therefore requires an externally modified log file. If that happens, every later role run that writes fingerprints fails with a traceback.

Two options: catch ValueError here as well, or read with an explicit encoding in _trim_log_file.

♻️ Proposed handling for a corrupted log file
-        except (IOError, OSError) as exc:
+        except (IOError, OSError, ValueError) as exc:
             module.fail_json(
                 msg="Failed to write fingerprint log file %s: %s" % (log_file, exc)
             )

Alternatively, make the read robust in _trim_log_file (outside the selected range):

with open(log_file, "r", encoding="utf-8", errors="replace") as log_fd:
    lines = log_fd.readlines()

Note that the encoding keyword is Python 3 only. If this module must keep Python 2 support, prefer the ValueError diff above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 328 - 331, Widen the exception
handling around the JSONL write in the fingerprint logging flow to include
ValueError, so UnicodeDecodeError raised by _trim_log_file is converted into the
existing module failure via module.fail_json. Preserve the current error message
and IOError/OSError handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@library/sr_fingerprint.py`:
- Around line 328-331: Widen the exception handling around the JSONL write in
the fingerprint logging flow to include ValueError, so UnicodeDecodeError raised
by _trim_log_file is converted into the existing module failure via
module.fail_json. Preserve the current error message and IOError/OSError
handling.

In `@tests/unit/test_sr_fingerprint.py`:
- Around line 121-122: Add assertions to test_get_managed_node_distro_missing
covering both partial-distro inputs: a non-empty distribution with an empty
version and an empty distribution with a non-empty version. Verify each returns
"unknown", preserving the current and semantics and guarding against emitting an
incomplete fingerprint.
- Around line 306-326: Add a unit test alongside
test_handle_fingerprint_check_mode_without_log_file covering _handle_fingerprint
with check_mode=False and write_log_file=False. Assert exactly one message is
appended to module.logged and includes role_name=systemd, status=begin, and
play_hosts_number=2; then verify the exit result is unchanged, contains the
begin fingerprint, and omits jsonl_row.
- Around line 274-288: Extend the test coverage in the existing fingerprint log
tests with a case for _write_jsonl_log where the serialized record exceeds
max_size. Verify that older entries are removed as needed but the oversized
record is still written as the sole remaining line, and assert its decoded
contents match the original record.
- Around line 131-140: Run Black on tests/unit/test_sr_fingerprint.py, allowing
the parenthesized role_path assignment in
test_format_fingerprint_syslog_quotes_values_with_spaces to be collapsed onto
one line. Verify the result with tox -e black,flake8.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f15b7c7d-e77e-4489-b1a6-fef81713914f

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae5978 and ed81839.

📒 Files selected for processing (2)
  • library/sr_fingerprint.py
  • tests/unit/test_sr_fingerprint.py

@spetrosi spetrosi changed the title feat: Write roles fingerprints to /var/log/sysroles.jsonl feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] Aug 6, 2026
Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl
Signed-off-by: Sergei Petrosian <spetrosi@redhat.com>
@spetrosi
spetrosi force-pushed the fingerprint-write-to-file branch from ed81839 to a2f54bf Compare August 6, 2026 15:01
The sr_fingerprint module was rewritten to accept structured parameters
(status, role_name, role_path, etc.) instead of a free-form sr_message.
Update the role tasks and tests to match the new module interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@spetrosi

spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

[citest]

@richm
richm merged commit 459448f into main Aug 6, 2026
10 checks passed
@richm
richm deleted the fingerprint-write-to-file branch August 6, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants