Skip to content

Commit c678a8a

Browse files
committed
Improve/fix logging
This patch improves/fixes logs: - Skip backup documentation directories to avoid showing unnecessary errors - Prevent concurrent file edits - Fix incorrect error messages when there's nothing wrong with the source files - Remove duplicated log messages - Show correct status of applied fixes AI: Code generated by Cursor
1 parent 05f08fa commit c678a8a

1 file changed

Lines changed: 105 additions & 32 deletions

File tree

scripts/rhoso_adoc_docs_to_text.py

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import re
2626
import subprocess
2727
import tempfile
28+
import fcntl
29+
import time
2830

2931
LOG = logging.getLogger()
3032
logging.basicConfig(level=logging.INFO)
@@ -1679,58 +1681,125 @@ def preprocess_adoc_tables(
16791681
return result, fixes
16801682

16811683

1684+
class FileLock:
1685+
"""Context manager for file locking to prevent concurrent modifications.
1686+
1687+
Uses fcntl-based advisory locks on Linux systems. Creates a .lock file
1688+
next to the target file for locking purposes.
1689+
"""
1690+
1691+
def __init__(
1692+
self, file_path: Path, timeout: int = 300, check_interval: float = 0.1
1693+
):
1694+
"""Initialize the file lock.
1695+
1696+
Args:
1697+
file_path: Path to the file to lock
1698+
timeout: Maximum time in seconds to wait for lock (default: 300s = 5 min)
1699+
check_interval: Time in seconds between lock acquisition attempts (default: 0.1s)
1700+
"""
1701+
self.file_path = file_path
1702+
self.lock_path = file_path.parent / f".{file_path.name}.lock"
1703+
self.timeout = timeout
1704+
self.check_interval = check_interval
1705+
self.lock_file = None
1706+
1707+
def __enter__(self):
1708+
"""Acquire the file lock."""
1709+
start_time = time.time()
1710+
while True:
1711+
try:
1712+
# Open lock file (create if it doesn't exist)
1713+
self.lock_file = open(self.lock_path, "w")
1714+
# Try to acquire exclusive lock (non-blocking)
1715+
fcntl.flock(self.lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
1716+
# Successfully acquired lock
1717+
LOG.debug(f"Acquired lock for {self.file_path}")
1718+
return self
1719+
except (IOError, OSError):
1720+
# Lock is held by another process
1721+
if time.time() - start_time > self.timeout:
1722+
if self.lock_file:
1723+
self.lock_file.close()
1724+
raise TimeoutError(
1725+
f"Could not acquire lock for {self.file_path} after {self.timeout}s"
1726+
)
1727+
# Wait and retry
1728+
time.sleep(self.check_interval)
1729+
1730+
def __exit__(self, exc_type, exc_val, exc_tb):
1731+
"""Release the file lock."""
1732+
if self.lock_file:
1733+
try:
1734+
fcntl.flock(self.lock_file.fileno(), fcntl.LOCK_UN)
1735+
self.lock_file.close()
1736+
LOG.debug(f"Released lock for {self.file_path}")
1737+
# Try to remove the lock file (best effort)
1738+
try:
1739+
self.lock_path.unlink()
1740+
except Exception:
1741+
pass
1742+
except Exception as e:
1743+
LOG.warning(f"Error releasing lock for {self.file_path}: {e}")
1744+
1745+
16821746
def fix_adoc_file(file_path: Path) -> list[str]:
16831747
"""Apply all AsciiDoc fixes to a source file and report changes.
16841748
16851749
This function reads an .adoc file, applies all preprocessing fixes,
16861750
writes the changes back to the file if any fixes were made, and
16871751
returns a list of descriptions of what was fixed.
16881752
1753+
Uses file locking to prevent concurrent modifications when multiple
1754+
workers are processing files in parallel.
1755+
16891756
Args:
16901757
file_path: Path to the .adoc file to fix
16911758
16921759
Returns:
16931760
List of fix descriptions (empty if no fixes were needed)
16941761
"""
1695-
try:
1696-
with open(file_path, "r", encoding="utf-8") as f:
1697-
original_content = f.read()
1698-
except Exception as e:
1699-
LOG.error(f"Failed to read {file_path}: {e}")
1700-
return []
1762+
# Acquire exclusive lock before processing the file
1763+
with FileLock(file_path):
1764+
try:
1765+
with open(file_path, "r", encoding="utf-8") as f:
1766+
original_content = f.read()
1767+
except Exception as e:
1768+
LOG.error(f"Failed to read {file_path}: {e}")
1769+
return []
17011770

1702-
content = original_content
1703-
all_fixes = []
1771+
content = original_content
1772+
all_fixes = []
17041773

1705-
# Apply all preprocessing steps
1706-
content, fixes = preprocess_adoc_link_brackets(content, file_path)
1707-
all_fixes.extend(fixes)
1774+
# Apply all preprocessing steps
1775+
content, fixes = preprocess_adoc_link_brackets(content, file_path)
1776+
all_fixes.extend(fixes)
17081777

1709-
content, fixes = preprocess_adoc_callout_numbering(content, file_path)
1710-
all_fixes.extend(fixes)
1778+
content, fixes = preprocess_adoc_callout_numbering(content, file_path)
1779+
all_fixes.extend(fixes)
17111780

1712-
content, fixes = preprocess_adoc_callout_placement(content, file_path)
1713-
all_fixes.extend(fixes)
1781+
content, fixes = preprocess_adoc_callout_placement(content, file_path)
1782+
all_fixes.extend(fixes)
17141783

1715-
content, fixes = preprocess_adoc_callouts(content, file_path)
1716-
all_fixes.extend(fixes)
1784+
content, fixes = preprocess_adoc_callouts(content, file_path)
1785+
all_fixes.extend(fixes)
17171786

1718-
content, fixes = preprocess_adoc_callout_spacing(content, file_path)
1719-
all_fixes.extend(fixes)
1787+
content, fixes = preprocess_adoc_callout_spacing(content, file_path)
1788+
all_fixes.extend(fixes)
17201789

1721-
content, fixes = preprocess_adoc_tables(content, file_path)
1722-
all_fixes.extend(fixes)
1790+
content, fixes = preprocess_adoc_tables(content, file_path)
1791+
all_fixes.extend(fixes)
17231792

1724-
# Only write back if changes were made
1725-
if content != original_content:
1726-
try:
1727-
with open(file_path, "w", encoding="utf-8") as f:
1728-
f.write(content)
1729-
except Exception as e:
1730-
LOG.error(f"Failed to write fixes to {file_path}: {e}")
1731-
return []
1793+
# Only write back if changes were made
1794+
if content != original_content:
1795+
try:
1796+
with open(file_path, "w", encoding="utf-8") as f:
1797+
f.write(content)
1798+
except Exception as e:
1799+
LOG.error(f"Failed to write fixes to {file_path}: {e}")
1800+
return []
17321801

1733-
return all_fixes
1802+
return all_fixes
17341803

17351804

17361805
def fix_adoc_files_in_directory(base_dir: Path) -> dict[Path, list[str]]:
@@ -2589,6 +2658,8 @@ def convert(self, input_path: Path, output_path: Path) -> dict[Path, list[str]]:
25892658

25902659
LOG.info("Successfully converted: %s -> %s", input_path, output_path)
25912660

2661+
return fixes_by_file
2662+
25922663
except Exception as e:
25932664
LOG.error(
25942665
"Failed to convert: %s -> %s (%s)", input_path, output_path, e
@@ -2873,7 +2944,8 @@ def convert(self, input_path: Path, output_path: Path) -> None:
28732944
all_fixes[file_path] = fixes
28742945
except Exception as e:
28752946
failed_conversions.append((str(input_path), str(e)))
2876-
LOG.error("Continuing with next document after failure...")
2947+
LOG.error("Failed to convert %s: %s", input_path, str(e))
2948+
LOG.error("Continuing with next document...")
28772949

28782950
if args.relnotes_dir:
28792951
relnotes_converter = RelNotesConverter(attributes_file=args.attributes_file)
@@ -2891,7 +2963,8 @@ def convert(self, input_path: Path, output_path: Path) -> None:
28912963
all_fixes[file_path] = fixes
28922964
except Exception as e:
28932965
failed_conversions.append((str(input_path), str(e)))
2894-
LOG.error("Continuing with next document after failure...")
2966+
LOG.error("Failed to convert %s: %s", input_path, str(e))
2967+
LOG.error("Continuing with next document...")
28952968

28962969
# Print summary
28972970
LOG.info("\n" + "=" * 80)

0 commit comments

Comments
 (0)