Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom log file behaviour #159

Merged
merged 39 commits into from
Jan 8, 2025
Merged

feat: custom log file behaviour #159

merged 39 commits into from
Jan 8, 2025

Conversation

cmeesters
Copy link
Member

@cmeesters cmeesters commented Oct 20, 2024

This PR should allow to

  • set a custom log directory for SLURM jobs
  • enable auto-deletion of older SLURM log files after 10 days
  • allow changing the that behaviour or disabling auto deletion all together
  • auto delete log files of successful SLURM jobs (and ignore this default behaviour)

The idea behind this PR is to

  • limit the number of SLURM log files to keep, as a workflow can easily produce thousands of log files. In case of a successful job, the information is redundant anyway, hence the proposed auto-deletion of successful jobs.
  • users may select a custom directory for SLURM log files - this way different workflows can point to one prefix. Together with the auto-deletion of older log files, this futher limits the number of present log files.

It addresses issues #94 and #123.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced SLURM job log management with a configurable log directory.
    • Added options to control log file retention and automatic cleanup of old logs.
    • Introduced new command-line flags for specifying log management settings.
  • Documentation

    • Updated documentation with new command-line flags for log management.
    • Improved guidance on managing job logs and their retention policies.
  • Improvements

    • More flexible control over SLURM job log handling.
    • Better support for cleaning up empty directories after job completion.

Copy link
Contributor

coderabbitai bot commented Oct 31, 2024

Walkthrough

The pull request introduces enhancements to the Snakemake SLURM executor plugin, focusing on improved log management. Key changes include the addition of a method for cleaning old log files, expanded settings for log configuration, and new command-line flags for user control over log retention and directory management. The documentation has been updated to reflect these changes, providing clearer guidance on utilizing the new features effectively.

Changes

File Change Summary
snakemake_executor_plugin_slurm/__init__.py - Added clean_old_logs() method to delete old log files
- Expanded ExecutorSettings with new fields:
* logdir: Custom log directory
* keep_successful_logs: Retain logs for successful jobs
* delete_logfiles_older_than: Log file retention period
docs/further.md - Added documentation for new command-line flags:
* --slurm-logdir
* --slurm-keep-successful-logs
* --slurm-delete-logfiles-older-than
- Updated sections on log file management and retention policies
snakemake_executor_plugin_slurm/utils.py - Added delete_empty_dirs(path: Path) method to remove empty directories after job completion

Poem

🐰 Logs dancing in the SLURM breeze,
Cleanup magic with elegant ease
Old files whisper their goodbye
Neat and tidy, logs now fly
A rabbit's touch of file finesse! 🧹✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9c7b8e and d6f5567.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snakemake_executor_plugin_slurm/init.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cmeesters cmeesters marked this pull request as ready for review November 18, 2024 15:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (8)
snakemake_executor_plugin_slurm/utils.py (2)

19-23: Enhance function documentation and add type hints.

The function documentation should include:

  • Parameter descriptions with types
  • Return value (if any)
  • Examples of usage
  • Explanation of the age cutoff unit (days)
-def clean_old_logs(logdir, age_cutoff):
+def clean_old_logs(logdir: str, age_cutoff: float) -> None:
     """
-    Function to delete files older than 'age_cutoff'
-    in the SLURM 'logdir'
+    Delete files in the SLURM log directory that are older than the specified age cutoff.
+
+    Args:
+        logdir: Path to the SLURM log directory
+        age_cutoff: Maximum age of files in days before deletion (0 or negative to keep all files)
+
+    Example:
+        >>> clean_old_logs("/path/to/logs", 10.0)  # Delete files older than 10 days
     """

24-24: Replace magic number with a named constant.

The conversion factor from days to seconds should be defined as a constant at module level for better maintainability.

+# Constants
+SECONDS_PER_DAY = 24 * 60 * 60  # 86400 seconds in a day
+
 def clean_old_logs(logdir: str, age_cutoff: float) -> None:
     """
     ...
     """
-    cutoff_secs = age_cutoff * 86400
+    cutoff_secs = age_cutoff * SECONDS_PER_DAY
snakemake_executor_plugin_slurm/__init__.py (6)

51-54: Correct typographical error in help text

There's a misspelling in the help text for keep_successful_logs. The word "sucessful" should be "successful". Additionally, consider replacing "Per default" with "By default" for improved clarity.

Apply this diff to fix the typo and improve phrasing:

-        Per default SLURM log files will be deleted upon sucessful completion
+        By default, SLURM log files will be deleted upon successful completion

150-152: Remove commented-out code

The method delete_old_logs is commented out and appears to be obsolete due to the introduction of clean_old_logs. Removing unused code helps maintain clarity.

Apply this diff to remove the commented code:

- # def delete_old_logs(self):
- #    self.workflow.executor_settings.delete_logfiles_older_than

432-436: Handle exceptions when removing log files

When deleting log files using os.remove, it's advisable to handle potential exceptions such as FileNotFoundError to prevent the application from crashing if the file does not exist.

Modify the code to include exception handling:

try:
    os.remove(j.aux["slurm_logfile"])
except FileNotFoundError:
    self.logger.warning(f"Log file {j.aux['slurm_logfile']} not found when attempting to remove.")
except Exception as e:
    self.logger.error(f"Error removing log file {j.aux['slurm_logfile']}: {e}")

51-54: Standardize help text formatting

The help texts for the fields in ExecutorSettings have inconsistent indentation and spacing. Standardizing the formatting improves readability and maintains consistency across the codebase.

Consider adjusting the help texts to align properly and use consistent indentation.

Also applies to: 64-65, 71-73


Line range hint 139-147: Ensure done flag in warn_on_jobcontext method is correctly managed

In the warn_on_jobcontext method, the done parameter is checked but might not be managed correctly. Since done is not stored or updated outside the method scope, the recursive call to warn_on_jobcontext(done=True) has no practical effect.

Consider refactoring the method to properly manage the done state or remove the parameter if it's unnecessary.

🧰 Tools
🪛 Ruff

139-140: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)


Line range hint 281-283: Handle potential exceptions when parsing slurm_jobid

When parsing slurm_jobid from the sbatch output, ensure that the code handles cases where the output might not be as expected, which could lead to an IndexError.

Add a check before accessing out.strip().split(";")[0]:

slurm_jobid_parts = out.strip().split(";")
if slurm_jobid_parts:
    slurm_jobid = slurm_jobid_parts[0]
else:
    raise WorkflowError("Failed to retrieve SLURM job ID from sbatch output.")
🧰 Tools
🪛 Ruff

15-15: pathlib imported but unused

(F401)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 531ebc6 and db4172a.

📒 Files selected for processing (2)
  • snakemake_executor_plugin_slurm/__init__.py (6 hunks)
  • snakemake_executor_plugin_slurm/utils.py (1 hunks)
🧰 Additional context used
🪛 Ruff
snakemake_executor_plugin_slurm/__init__.py

15-15: pathlib imported but unused

(F401)

🔇 Additional comments (4)
snakemake_executor_plugin_slurm/__init__.py (4)

61-66: Clarify behavior when delete_logfiles_older_than is set to 0

The help text mentions that setting delete_logfiles_older_than to '0' disables deletion of old log files. Since the field is defined as an Optional[int], it's more appropriate to use 0 (without quotes) to represent an integer value. Ensure that this behavior is correctly implemented and consider clarifying the help text.


181-184: Update assertion to reflect new slurm_logdir construction

With the changes to slurm_logfile, ensure that the assertion checking for %j in the directory path is still valid and correctly reflects the new path construction.


186-186: Ensure slurm_logdir is correctly created

After reconstructing slurm_logfile using os.path.join, verify that slurm_logdir is correctly obtained using os.path.dirname(slurm_logfile) and that the directory is created as intended.


61-66: Confirm type handling for delete_logfiles_older_than

Ensure that the delete_logfiles_older_than field, which is an Optional[int], correctly handles the value 0 to disable log deletion. Verify that the logic in clean_old_logs interprets 0 appropriately.

snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
snakemake_executor_plugin_slurm/__init__.py (2)

169-171: Use consistent f-string formatting

The comment string construction can be simplified using f-strings consistently.

Apply this diff to improve readability:

-           comment_str = f"{group_or_rule}"
-       else:
-           comment_str = f"{group_or_rule}_wildcards_{wildcard_str}"
+           comment_str = group_or_rule
+       else:
+           comment_str = f"{group_or_rule}_wildcards_{wildcard_str}"

504-505: LGTM! Consider using a single f-string

The error logging improvement is good, but the string concatenation can be simplified.

Apply this diff for better readability:

-               "The job status query failed with"
-               f" command: {command}\n"
+               f"The job status query failed with command: {command}\n"
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between db4172a and 34adb4e.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (6 hunks)

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (3)
snakemake_executor_plugin_slurm/__init__.py (3)

51-54: Fix typos in help text

There are typos in the help text:

  • "sucessful" should be "successful"
  • The sentence structure could be improved
                   Per default SLURM log files will be deleted upon
-                  sucessful completion
+                  successful completion
                   of a job. Whenever a SLURM job fails, its log file will be preserved.

455-459: Add error handling for log file deletion

The log file deletion should handle potential errors gracefully, as the file might have been deleted by another process or the user.

                     if not self.workflow.executor_settings.keep_successful_logs:
                         self.logger.debug(
                             f"removing log for successful job with SLURM ID '{j.external_jobid}'"
                         )
-                        os.remove(j.aux["slurm_logfile"])
+                        try:
+                            if os.path.exists(j.aux["slurm_logfile"]):
+                                os.remove(j.aux["slurm_logfile"])
+                        except OSError as e:
+                            self.logger.warning(f"Could not remove log file {j.aux['slurm_logfile']}: {e}")

173-175: Remove commented-out code

The commented-out method delete_old_logs should be removed as it's no longer needed and can cause confusion.

-    # def delete_old_logs(self):
-    #    self.workflow.executor_settings.delete_logfiles_older_than
-
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 34adb4e and 730433f.

📒 Files selected for processing (2)
  • snakemake_executor_plugin_slurm/__init__.py (6 hunks)
  • snakemake_executor_plugin_slurm/utils.py (1 hunks)
🧰 Additional context used
🪛 Ruff
snakemake_executor_plugin_slurm/__init__.py

30-30: .utils.clean_old_logs imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


131-131: Redefinition of unused clean_old_logs from line 30

(F811)

snakemake_executor_plugin_slurm/utils.py

4-4: time imported but unused

Remove unused import: time

(F401)

🔇 Additional comments (1)
snakemake_executor_plugin_slurm/__init__.py (1)

35-46: ⚠️ Potential issue

Use os.path.expanduser('~') for more robust home directory handling

Using os.environ['USER'] directly can fail if the USER environment variable is not set. Using os.path.expanduser('~') is more robust and cross-platform compatible.

     logdir: Optional[str] = field(
-        default=f"/home/{os.environ['USER']}/.snakemake/slurm_logs",
+        default=os.path.join(os.path.expanduser('~'), ".snakemake", "slurm_logs"),
         metadata={

Likely invalid or redundant comment.

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (1)
snakemake_executor_plugin_slurm/__init__.py (1)

177-179: Remove commented-out code

The commented-out delete_old_logs method is no longer needed as the functionality has been implemented elsewhere.

Remove these commented-out lines to improve code cleanliness.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 730433f and e567349.

📒 Files selected for processing (2)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
  • snakemake_executor_plugin_slurm/utils.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snakemake_executor_plugin_slurm/utils.py

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
snakemake_executor_plugin_slurm/__init__.py (2)

47-60: Fix typo in documentation string

There's a typo in the help text: "sucessful" should be "successful".

-                   Per default SLURM log files will be deleted upon sucessful
+                   Per default SLURM log files will be deleted upon successful

61-72: Validate delete_logfiles_older_than value

While the code checks for values ≤ 0, there should be validation for extremely large values that could cause performance issues. Consider adding an upper limit.

     delete_logfiles_older_than: Optional[int] = field(
         default=10,
+        validators=[field(lambda x: 0 <= x <= 365, "Value must be between 0 and 365 days")],
         metadata={
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e567349 and 4770c8b.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
🔇 Additional comments (3)
snakemake_executor_plugin_slurm/__init__.py (3)

199-204: 🛠️ Refactor suggestion

Use os.path.join for log file path construction

The current implementation uses string concatenation which is error-prone and not cross-platform friendly.

-        slurm_logfile = (
-            self.workflow.executor_settings.logdir
-            + os.path.sep
-            + f"{group_or_rule}/{wildcard_str}/%j.log"
-        )
+        slurm_logfile = os.path.join(
+            self.workflow.executor_settings.logdir,
+            group_or_rule,
+            wildcard_str,
+            "%j.log"
+        )

Likely invalid or redundant comment.


135-163: 🛠️ Refactor suggestion

Move clean_old_logs outside post_init

The current implementation has several issues:

  1. Function defined inside post_init reduces maintainability and testability
  2. Potential race condition in directory removal
  3. Missing type hints

Move the function to class level and improve its implementation:

class Executor(RemoteExecutor):
+    def clean_old_logs(self, logdir: str, age_cutoff: int) -> None:
+        """Delete files older than specified age from the SLURM log directory.
+        
+        Args:
+            logdir: Path to the log directory
+            age_cutoff: Number of days after which files should be deleted
+        """
+        if age_cutoff <= 0:
+            return
+        cutoff_secs = age_cutoff * 86400
+        current_time = time.time()
+        self.logger.info(f"Cleaning up log files older than {age_cutoff} day(s)")
+
+        for root, _, files in os.walk(logdir, topdown=False):
+            for fname in files:
+                file_path = os.path.join(root, fname)
+                try:
+                    file_age = current_time - os.stat(file_path).st_mtime
+                    if file_age > cutoff_secs:
+                        os.remove(file_path)
+                except (OSError, FileNotFoundError) as e:
+                    self.logger.warning(f"Could not delete file {file_path}: {e}")
+            # Remove empty directories
+            try:
+                if os.path.exists(root) and not os.listdir(root):
+                    os.rmdir(root)
+            except (OSError, FileNotFoundError) as e:
+                self.logger.warning(f"Could not remove empty directory {root}: {e}")

    def __post_init__(self):
        # ... other initialization code ...
        atexit.register(
            self.clean_old_logs,
            self.workflow.executor_settings.logdir,
            self.workflow.executor_settings.delete_logfiles_older_than,
        )

Likely invalid or redundant comment.


35-46: ⚠️ Potential issue

Make logdir path construction more robust

The current implementation directly accesses os.environ['USER'] which can raise KeyError if USER is not set. Use os.path.expanduser('~') for a more robust and cross-platform solution.

-    logdir: Optional[str] = field(
-        default=f"/home/{os.environ['USER']}/.snakemake/slurm_logs",
+    logdir: Optional[str] = field(
+        default=os.path.join(os.path.expanduser('~'), ".snakemake", "slurm_logs"),

Likely invalid or redundant comment.

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
snakemake_executor_plugin_slurm/__init__.py (1)

51-51: Fix typo in documentation

The word "sucessful" is misspelled in the help text.

Apply this diff to fix the typo:

-                   Per default SLURM log files will be deleted upon sucessful
+                   Per default SLURM log files will be deleted upon successful
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4770c8b and 39cf201.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
🔇 Additional comments (4)
snakemake_executor_plugin_slurm/__init__.py (4)

459-470: LGTM: Log cleanup implementation is robust

The implementation includes proper error handling, path existence checks, and appropriate logging.


199-204: 🛠️ Refactor suggestion

Use os.path.join for path construction

The current implementation uses string concatenation which is error-prone and not cross-platform friendly.

Apply this diff to improve path construction:

-        slurm_logfile = (
-            self.workflow.executor_settings.logdir
-            + os.path.sep
-            + f"{group_or_rule}/{wildcard_str}/%j.log"
-        )
+        slurm_logfile = os.path.join(
+            self.workflow.executor_settings.logdir,
+            group_or_rule,
+            wildcard_str,
+            "%j.log"
+        )

Likely invalid or redundant comment.


35-46: ⚠️ Potential issue

Use os.path.expanduser('~') for home directory path

The current implementation has potential issues:

  1. Using os.environ['USER'] can raise KeyError if USER is not set
  2. The help text mentions '~' but the code uses an absolute path

Apply this diff to fix the path construction:

    logdir: Optional[str] = field(
-        default=f"/home/{os.environ['USER']}/.snakemake/slurm_logs",
+        default=os.path.join(os.path.expanduser('~'), ".snakemake", "slurm_logs"),

Likely invalid or redundant comment.


135-163: 🛠️ Refactor suggestion

Improve clean_old_logs implementation

Several issues with the current implementation:

  1. Function defined inside post_init reduces maintainability and testability
  2. Missing error handling for directory removal
  3. Missing type hints
  4. Potential race condition in directory removal logic

Apply this diff to improve the implementation:

+ def clean_old_logs(self, logdir: str, age_cutoff: int) -> None:
+     """Delete files older than specified age from the SLURM log directory.
+     
+     Args:
+         logdir: Path to the log directory
+         age_cutoff: Number of days after which files should be deleted
+     """
+     if age_cutoff <= 0:
+         return
+     cutoff_secs = age_cutoff * 86400
+     current_time = time.time()
+     self.logger.info(f"Cleaning up log files older than {age_cutoff} day(s)")
+
+     for root, _, files in os.walk(logdir, topdown=False):
+         for fname in files:
+             file_path = os.path.join(root, fname)
+             try:
+                 file_age = current_time - os.stat(file_path).st_mtime
+                 if file_age > cutoff_secs:
+                     os.remove(file_path)
+             except (OSError, FileNotFoundError) as e:
+                 self.logger.warning(f"Could not delete file {file_path}: {e}")
+         # Remove empty directories
+         try:
+             if os.path.exists(root) and not os.listdir(root):
+                 os.rmdir(root)
+         except (OSError, FileNotFoundError) as e:
+             self.logger.warning(f"Could not remove empty directory {root}: {e}")

def __post_init__(self):
    # ... other initialization code ...
    atexit.register(
        self.clean_old_logs,
        self.workflow.executor_settings.logdir,
        self.workflow.executor_settings.delete_logfiles_older_than,
    )

Likely invalid or redundant comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
snakemake_executor_plugin_slurm/__init__.py (2)

35-72: LGTM! Clear and well-documented settings.

The new settings for log management are well-structured with appropriate defaults and helpful documentation. The path construction for logdir is safe and cross-platform compatible.

Consider fixing the typo in the help text:

-                   Per default SLURM log files will be deleted upon sucessful
+                   Per default SLURM log files will be deleted upon successful

177-179: Remove commented-out code.

The commented-out delete_old_logs method appears to be obsolete now that the new log cleanup functionality has been implemented.

Apply this diff to remove the commented-out code:

-    # def delete_old_logs(self):
-    #    self.workflow.executor_settings.delete_logfiles_older_than
-
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 39cf201 and 568080a.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
🔇 Additional comments (2)
snakemake_executor_plugin_slurm/__init__.py (2)

460-471: LGTM! Well-implemented log cleanup for successful jobs.

The implementation includes:

  • Proper conditional check for keep_successful_logs setting
  • Comprehensive error handling for file operations
  • Appropriate debug and warning logging

135-163: 🛠️ Refactor suggestion

Refactor log cleanup implementation for better maintainability.

While the log cleanup functionality works correctly, there are some improvements that could make it more maintainable:

  1. Move the function definition outside post_init to make it a proper class method
  2. Add error handling for directory removal operations
  3. Add type hints for better code clarity

Here's the suggested implementation:

-    def __post_init__(self):
-        # ... other init code ...
-        def clean_old_logs(logdir, age_cutoff):
-            """
-            Function to delete files older than 'age_cutoff'
-            in the SLURM 'logdir'
-            """
+    def clean_old_logs(self, logdir: str, age_cutoff: int) -> None:
+        """Delete files older than specified age from the SLURM log directory.
+        
+        Args:
+            logdir: Path to the log directory
+            age_cutoff: Number of days after which files should be deleted
+        """
+        if age_cutoff <= 0:
+            return
+        cutoff_secs = age_cutoff * 86400
+        current_time = time.time()
+        self.logger.info(f"Cleaning up log files older than {age_cutoff} day(s)")
+
+        for root, _, files in os.walk(logdir, topdown=False):
+            for fname in files:
+                file_path = os.path.join(root, fname)
+                try:
+                    file_age = current_time - os.stat(file_path).st_mtime
+                    if file_age > cutoff_secs:
+                        os.remove(file_path)
+                except (OSError, FileNotFoundError) as e:
+                    self.logger.warning(f"Could not delete file {file_path}: {e}")
+            # remove empty rule top dir, if empty
+            try:
+                if len(os.listdir(root)) == 0:
+                    os.rmdir(root)
+            except (OSError, FileNotFoundError) as e:
+                self.logger.warning(f"Could not remove empty directory {root}: {e}")

     def __post_init__(self):
+        # ... other init code ...
         atexit.register(
             self.clean_old_logs,
             self.workflow.executor_settings.logdir,
             self.workflow.executor_settings.delete_logfiles_older_than,
         )

Likely invalid or redundant comment.

snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
@johanneskoester
Copy link
Contributor

@johanneskoester care to take a look - seems as if the slurmdbd in the CI is somehow broken, though?

The action is fixed again. It was using an old slurm plugin that is deprecated in the new slurm release that is on the current ubuntu.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f750600 and aaad25d.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (8 hunks)
🔇 Additional comments (3)
snakemake_executor_plugin_slurm/__init__.py (3)

6-6: LGTM: Required imports added.

The new imports atexit and pathlib.Path are necessary for the log cleanup functionality.

Also applies to: 10-10


194-201: LGTM: Improved log file path construction.

The use of os.path.join for constructing the log file path is more robust and cross-platform friendly.


455-467: LGTM: Proper error handling for log file deletion.

The implementation includes appropriate error handling and logging for log file deletion operations.

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
@cmeesters
Copy link
Member Author

@johanneskoester Happy New Year!

IMO, it's just the issue with the default path, which might be subject to change. Please take a look and weight the argument.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
docs/further.md (4)

261-261: Clarify log retention configuration options

The documentation should specify how to disable the log deletion feature and explain the format for the retention period value.

-This executor will remove SLURM logs of sucessful jobs immediately when they are finished. You can change this behaviour with the flag `--slurm-keep-successful-logs`. A log file for a failed job will be preserved per default for 10 days. You may change this value using the `--slurm-delete-logfiles-older-than` flag.
+This executor will remove SLURM logs of successful jobs immediately when they are finished. You can change this behavior with the flag `--slurm-keep-successful-logs`. A log file for a failed job will be preserved by default for 10 days. You may change this value using the `--slurm-delete-logfiles-older-than` flag (e.g., `--slurm-delete-logfiles-older-than 30` for 30 days, or `--slurm-delete-logfiles-older-than 0` to disable auto-deletion).

265-267: Enhance the YAML configuration example

The example could be more comprehensive by showing all log-related settings.

 ```YAML
-slurm-logdir: "/home/<username>/.snakemake/slurm_logs"
+# Log management settings
+slurm-logdir: "/home/<username>/.snakemake/slurm_logs"
+slurm-keep-successful-logs: false  # Set to true to keep logs of successful jobs
+slurm-delete-logfiles-older-than: 10  # Number of days to keep failed job logs

---

`263-263`: **Fix grammar and spelling**

There are several issues in this line:
1. Missing "to" before "avoid"
2. American English consistency

```diff
-The default location of Snakemake log files are relative to the directory where the workflow is started or relative to the directory indicated with `--directory`. SLURM logs, produced by Snakemake, can be redirected using `--slurm-logdir`. If you want avoid that log files accumulate in different directories, you can store them in your home directory. Best put the parameter in your profile then, e.g.:
+The default location of Snakemake log files is relative to the directory where the workflow is started or relative to the directory indicated with `--directory`. SLURM logs, produced by Snakemake, can be redirected using `--slurm-logdir`. If you want to avoid log files accumulating in different directories, you can store them in your home directory. It's best to put this parameter in your profile, e.g.:
🧰 Tools
🪛 LanguageTool

[grammar] ~263-~263: Did you mean “to avoid”?
Context: ...ted using --slurm-logdir. If you want avoid that log files accumulate in different ...

(MISSING_TO_BEFORE_A_VERB)


257-258: Improve section heading consistency

The heading style should match other sections in the document. Also, consider moving this section after the "Retries" section since log management is typically a more advanced topic.

-### Log Files - Getting Information on Failures
+### Log Files
snakemake_executor_plugin_slurm/utils.py (2)

21-27: Fix documentation issues

The function documentation has a typo and is missing return value documentation.

Apply these improvements:

     """
     Function to delete all empty directories in a given path.
     This is needed to clean up the working directory after
-    a job has sucessfully finished. This function is needed because
+    a job has successfully finished. This function is needed because
     the shutil.rmtree() function does not delete empty
     directories.
+
+    Args:
+        path (Path): The directory path to clean up
+
+    Returns:
+        list: List of directories that were processed
+
+    Raises:
+        OSError: If a directory cannot be removed
     """

29-37: Consider improving the recursive directory traversal

The current implementation of get_dirs might not handle deep directory structures efficiently due to Python's recursion limit.

Consider using an iterative approach:

-    def get_dirs(path: Path, result=None):
-        if result is None:
-            result = []
-        for p in path.iterdir():
-            if p.is_dir():
-                result.append(p)
-                if any(p.iterdir()):
-                    get_dirs(p)
-        return result
+    def get_dirs(path: Path):
+        result = []
+        stack = [path]
+        while stack:
+            current = stack.pop()
+            for p in current.iterdir():
+                if p.is_dir():
+                    result.append(p)
+                    if any(p.iterdir()):
+                        stack.append(p)
+        return result
🧰 Tools
🪛 Ruff (0.8.2)

30-30: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63b4f59 and 9764842.

📒 Files selected for processing (3)
  • docs/further.md (2 hunks)
  • snakemake_executor_plugin_slurm/__init__.py (7 hunks)
  • snakemake_executor_plugin_slurm/utils.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snakemake_executor_plugin_slurm/init.py
🧰 Additional context used
🪛 Ruff (0.8.2)
snakemake_executor_plugin_slurm/utils.py

30-30: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

🪛 LanguageTool
docs/further.md

[grammar] ~263-~263: Did you mean “to avoid”?
Context: ...ted using --slurm-logdir. If you want avoid that log files accumulate in different ...

(MISSING_TO_BEFORE_A_VERB)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (1)
snakemake_executor_plugin_slurm/utils.py (1)

4-4: LGTM: Clean import addition

The Path import from pathlib is appropriately placed and properly utilized in the new function.

snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/utils.py Outdated Show resolved Hide resolved
cmeesters and others added 2 commits January 7, 2025 19:33
not convinced, that this is better, but replacing [] for None as the default arg in `get_dirs`

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
snakemake_executor_plugin_slurm/utils.py (2)

21-27: Fix typo and enhance docstring.

  1. Fix the typo: "sucessfully" → "successfully"
  2. Add information about the recursive nature of the function
     """
     Function to delete all empty directories in a given path.
     This is needed to clean up the working directory after
-    a job has sucessfully finished. This function is needed because
+    a job has successfully finished. This function is needed because
     the shutil.rmtree() function does not delete empty
     directories.
+
+    The function uses a bottom-up approach to recursively process
+    and delete nested empty directories.
     """

28-42: LGTM! Consider adding warning logs.

The implementation correctly handles recursive deletion of empty directories using a bottom-up approach. The error handling is robust, re-raising exceptions with proper context.

Consider adding warning logs before re-raising the exception to help with debugging:

 import os
 from pathlib import Path
+import logging
+
+logger = logging.getLogger(__name__)

 def delete_empty_dirs(path: Path) -> None:
     # ... existing code ...
     try:
         if not any(path.iterdir()):
             path.rmdir()
     except (OSError, FileNotFoundError) as e:
+        logger.warning(f"Failed to remove empty directory {path}: {e}")
         raise OSError(f"Failed to remove empty directory {path}: {e}") from e
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59cf40e and c9c7b8e.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/utils.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: formatting
  • GitHub Check: testing
🔇 Additional comments (1)
snakemake_executor_plugin_slurm/utils.py (1)

4-4: LGTM!

The import statement and spacing between functions follow Python's PEP 8 style guidelines.

Also applies to: 18-19

@cmeesters
Copy link
Member Author

@johanneskoester all changes are in - I added docs, too. To me, everything is now good enough. Some refactoring is due, soon.

snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
snakemake_executor_plugin_slurm/__init__.py Outdated Show resolved Hide resolved
@johanneskoester johanneskoester merged commit cc3d21b into main Jan 8, 2025
5 checks passed
@johanneskoester johanneskoester deleted the feat/logfeatures branch January 8, 2025 11:47
cmeesters pushed a commit that referenced this pull request Jan 8, 2025
🤖 I have created a release *beep* *boop*
---


##
[0.12.0](v0.11.2...v0.12.0)
(2025-01-08)


### Features

* custom log file behaviour
([#159](#159))
([cc3d21b](cc3d21b))
* experimenting with automated release post to Mastodon
([#166](#166))
([c06325d](c06325d))


### Documentation

* fix headings in further.md
([#168](#168))
([531ebc6](531ebc6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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