From 81c8da4d75d3dedfd8ff779233e309a72602d13a Mon Sep 17 00:00:00 2001 From: Martin Kourim Date: Mon, 3 Aug 2026 11:01:57 +0200 Subject: [PATCH] fix(logfiles): apply ignore rules to first search of a log file The ignore rules expiry for a log file without a recorded search state was evaluated against the current time instead of the recorded search timestamp. A rule with an expire time in the past was then not applied to the file, even though the whole unsearched file history may contain the ignored errors, and previously ignored errors surfaced as false failures. This contradicted the documented add_ignore_rule contract (a rule expires only when there are no yet to be searched log messages created before the expire time). Use the recorded timestamp as is - a file that was not searched yet has timestamp 0.0, so no rule is expired for it. Also fix a comment typo. --- cardano_node_tests/utils/logfiles.py | 10 +++++----- framework_tests/test_logfiles.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cardano_node_tests/utils/logfiles.py b/cardano_node_tests/utils/logfiles.py index 3ba3c9a39..dad4224de 100644 --- a/cardano_node_tests/utils/logfiles.py +++ b/cardano_node_tests/utils/logfiles.py @@ -512,7 +512,7 @@ def _validated_start(seek: int | None, size: int) -> int: def _search_log_lines( # noqa: C901 logfile: pl.Path, rotated_logs: list[RotableLog], - errors_re: re.Pattern[str], # The the error regex needs to be unanchored + errors_re: re.Pattern[str], # The error regex needs to be unanchored *, errors_ignored_re: re.Pattern[str] | None = None, look_back_map: dict[str, str] | None = None, @@ -1016,10 +1016,10 @@ def _search( # and inode of the log file the seek offset was recorded for seek, timestamp, inode = _load_search_state(logfile=logfile) - # Get ignore rules for the log file - ignore_rules = _get_ignore_rules( - cluster_env=cluster_env, timestamp=timestamp or time.time() - ) + # Get ignore rules for the log file. A log file that was not searched yet (or + # whose offset file was lost) has timestamp 0.0, so no rule is expired for it - + # the whole file is going to be searched and the rules apply to all of it. + ignore_rules = _get_ignore_rules(cluster_env=cluster_env, timestamp=timestamp) errors_ignored = _get_ignore_regex( ignore_rules=ignore_rules, regexes=ERRORS_IGNORED, logfile=logfile ) diff --git a/framework_tests/test_logfiles.py b/framework_tests/test_logfiles.py index f7d0463d0..8f5993dd2 100644 --- a/framework_tests/test_logfiles.py +++ b/framework_tests/test_logfiles.py @@ -932,3 +932,29 @@ def test_constrains_match_end(pattern: str, expected: bool): """ regex_b = re.compile(pattern.encode("utf-8")) assert logfiles._constrains_match_end(regex_b) is expected + + +def test_search_cluster_logs_first_search_expiry(cluster_env: cluster_nodes.ClusterEnv): + """Check that ignore rules apply to the first search of a log file. + + A log file that was not searched yet may contain ignored errors from any time in + the past. An ignore rule must not expire for such file, even when its expire time + already passed. For further searches the rule is expired. + """ + logfile = _write_log( + state_dir=cluster_env.state_dir, name="node1.stdout", content="ignored error one\n" + ) + # The expire time is far in the past + logfiles.add_ignore_rule( + files_glob="*.stdout", regex="ignored error", ignore_file_id="id1", skip_after=100.0 + ) + + # First search: the rule applies to the whole unsearched file history + assert logfiles.search_cluster_logs() == [] + + # Further searches: the rule is expired, new occurrences are reported + with open(logfile, "a", encoding="utf-8") as outfile: + outfile.write("ignored error two\n") + + errors = logfiles.search_cluster_logs() + assert [e[1] for e in errors] == ["ignored error two"]