feat!: Logging refactor & add LoggerPluginInterface#3107
feat!: Logging refactor & add LoggerPluginInterface#3107johanneskoester merged 99 commits intosnakemake:mainfrom
Conversation
📝 WalkthroughWalkthroughThis pull request makes extensive modifications across multiple modules. It updates the executor’s command-line options and fixes indentation issues. The logging system has been overhauled by introducing a new Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant CLI as Command Line Interface
participant API as Snakemake API
participant LM as LoggerManager
participant LH as Log Handler Plugins
U->>CLI: Run snakemake with --logger option
CLI->>API: Parse CLI arguments and initialize logging configuration
API->>LM: Call setup to configure log handlers and initialize logfile
LM->>LH: Configure default & plugin log handlers
API->>LM: Log workflow start (including workflow ID, host, and resources)
API->>LM: Execute workflow actions
LM->>API: Provide logging context via structured logs
API->>LM: On completion, call cleanup_logfile() and stop logging services
Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (1)`**/*.py`: Do not try to improve formatting. Do not suggest ...
⏰ Context from checks skipped due to timeout of 90000ms (29)
🔇 Additional comments (9)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (1)
snakemake/logging.py (1)
258-258: Normalize log level to lowercase inDefaultFormatter.format.In the
formatmethod,levelis not converted to lowercase before comparisons. This could lead to mismatches if the log level inmsgis in uppercase. It's better to normalizelevelto lowercase for consistent handling.Apply this change:
-def format(self, record): """ Override format method to handle Snakemake-specific log messages. """ msg = record.msg - level = msg.get("level", "INFO") + level = msg.get("level", "INFO").lower()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- snakemake/executors/local.py (1 hunks)
- snakemake/logging.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
snakemake/executors/local.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
🔇 Additional comments (3)
snakemake/executors/local.py (1)
85-85: LGTM. Verify impact on codebase.The addition of "host" to the return value of
additional_general_argsaligns with the PR objectives for refactoring the logging system. This change may affect how the Snakemake executor interacts with the host environment during execution.To ensure this change doesn't introduce unintended side effects, please run the following script to check for any other occurrences of the original argument string and verify that they are updated accordingly:
#!/bin/bash # Description: Check for occurrences of the original argument string and verify updates. # Search for the old argument string echo "Searching for occurrences of the old argument string:" rg --type python '"--quiet progress rules"' # Search for the new argument string echo "Searching for occurrences of the new argument string:" rg --type python '"--quiet progress rules host"' # Search for uses of the additional_general_args method echo "Searching for uses of the additional_general_args method:" rg --type python 'additional_general_args'snakemake/logging.py (2)
586-635:DefaultFilterclass implementation is correct.The
DefaultFilterclass effectively filters log records based on quietness settings and thedebug_dagflag. The logic appears sound and aligns with the intended functionality.
960-964:⚠️ Potential issueFix parameter name when instantiating
DefaultFormatter.In the
setup_loggerfunction, the keyword argument should match the corrected parameter name inDefaultFormatter. The argumentprintshellmdsshould beprintshellcmdsto ensure proper initialization.Apply this diff:
formatter = DefaultFormatter( printreason=printreason, show_failed_logs=show_failed_logs, - printshellmds=printshellcmds, + printshellcmds=printshellcmds, )Likely invalid or redundant comment.
snakemake/logging.py
Outdated
| log_level = { | ||
| "debug": _logging.DEBUG, | ||
| "info": _logging.WARNING, | ||
| "warning": _logging.WARNING, | ||
| "error": _logging.ERROR, | ||
| "critical": _logging.CRITICAL, | ||
| "run_info": _logging.WARNING, | ||
| "job_info": _logging.INFO, | ||
| "group_info": _logging.INFO, | ||
| "job_error": _logging.ERROR, | ||
| "group_error": _logging.ERROR, | ||
| "progress": _logging.INFO, | ||
| "job_finished": _logging.INFO, | ||
| "shellcmd": _logging.INFO, | ||
| "rule_info": _logging.INFO, | ||
| "dag_debug": ( | ||
| _logging.WARNING if self.debug_dag else _logging.DEBUG | ||
| ), # bump debug_dag to warning if we want it | ||
| "resources_info": _logging.WARNING, | ||
| "host": _logging.INFO, | ||
| "job_stats": _logging.WARNING, | ||
| }.get( | ||
| custom_level, _logging.INFO | ||
| ) # Default to INFO if not recognized | ||
|
|
There was a problem hiding this comment.
Correct the log level mapping for 'info' in Logger.handler.
The custom log level 'info' is mistakenly mapped to _logging.WARNING. This may cause 'info' messages to be logged at the wrong level. It should be mapped to _logging.INFO to reflect the appropriate logging severity.
Apply this diff to fix the mapping:
# Map custom levels to standard Python logging levels
log_level = {
"debug": _logging.DEBUG,
- "info": _logging.WARNING,
+ "info": _logging.INFO,
"warning": _logging.WARNING,
"error": _logging.ERROR,
"critical": _logging.CRITICAL,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| log_level = { | |
| "debug": _logging.DEBUG, | |
| "info": _logging.WARNING, | |
| "warning": _logging.WARNING, | |
| "error": _logging.ERROR, | |
| "critical": _logging.CRITICAL, | |
| "run_info": _logging.WARNING, | |
| "job_info": _logging.INFO, | |
| "group_info": _logging.INFO, | |
| "job_error": _logging.ERROR, | |
| "group_error": _logging.ERROR, | |
| "progress": _logging.INFO, | |
| "job_finished": _logging.INFO, | |
| "shellcmd": _logging.INFO, | |
| "rule_info": _logging.INFO, | |
| "dag_debug": ( | |
| _logging.WARNING if self.debug_dag else _logging.DEBUG | |
| ), # bump debug_dag to warning if we want it | |
| "resources_info": _logging.WARNING, | |
| "host": _logging.INFO, | |
| "job_stats": _logging.WARNING, | |
| }.get( | |
| custom_level, _logging.INFO | |
| ) # Default to INFO if not recognized | |
| log_level = { | |
| "debug": _logging.DEBUG, | |
| "info": _logging.INFO, | |
| "warning": _logging.WARNING, | |
| "error": _logging.ERROR, | |
| "critical": _logging.CRITICAL, | |
| "run_info": _logging.WARNING, | |
| "job_info": _logging.INFO, | |
| "group_info": _logging.INFO, | |
| "job_error": _logging.ERROR, | |
| "group_error": _logging.ERROR, | |
| "progress": _logging.INFO, | |
| "job_finished": _logging.INFO, | |
| "shellcmd": _logging.INFO, | |
| "rule_info": _logging.INFO, | |
| "dag_debug": ( | |
| _logging.WARNING if self.debug_dag else _logging.DEBUG | |
| ), # bump debug_dag to warning if we want it | |
| "resources_info": _logging.WARNING, | |
| "host": _logging.INFO, | |
| "job_stats": _logging.WARNING, | |
| }.get( | |
| custom_level, _logging.INFO | |
| ) # Default to INFO if not recognized |
There was a problem hiding this comment.
This was intentional to preserve the existing log output coloring:
snakemake/snakemake/logging.py
Lines 624 to 625 in 19a2ab7
There was a problem hiding this comment.
@cademirch, understood. Thanks for the clarification that the mapping of 'info' to _logging.WARNING is intentional to preserve the existing log output coloring.
(⁀ᗢ⁀)
✏️ Learnings added
Learnt from: cademirch
PR: snakemake/snakemake#3107
File: snakemake/logging.py:806-830
Timestamp: 2024-09-30T17:17:37.227Z
Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
I agree with the coderabbit bot - Snakemake should not warn on infos, as already pointed out in #2727 - upgrading all infos to warnings makes it impossible to then later filter out the infos without losing all actual warnings. If info level messages should be coloured differently, that ought to be fixed elsewhere, not by conflating it with warnings.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
(Belatedly realized this is on outdated changes, so probably the whole conversation can just get resolved. Sorry for the noise!)
There was a problem hiding this comment.
I agree, however this mapping is now gone and we're just using direct calls to info, warning etc. Happy to accept changes to specific logging calls that you think should go to different levels
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (7)
snakemake/logging.py (7)
246-509: LGTM! DefaultFormatter class is well-structured.The DefaultFormatter class is well-organized and follows the single responsibility principle. It provides specific formatting for various log levels, which enhances readability and maintainability.
However, there are a few minor optimizations that could be made:
- In the
handle_infomethod, consider using a list comprehension for better performance:output = [f"{indent}{line}" for line in msg["msg"].split("\n")] return "\n".join(output)
- In the
format_percentagemethod, consider usingf-stringsfor better readability:fmt_precision = lambda p: f"{{:.{p}%}}"These are minor suggestions and don't affect the overall functionality of the class.
587-635: LGTM! DefaultFilter class is well-implemented.The DefaultFilter class is concise and effectively filters log messages based on quietness settings and the debug_dag flag. It makes good use of Snakemake's Quietness enum for filtering.
A minor optimization could be made in the
filtermethod:def filter(self, record): if self.is_quiet_about("all"): return False msg = record.msg level = msg.get("level", "INFO").lower() quietness_map = { "job_info": "rules", "group_info": "rules", "job_error": "rules", "group_error": "rules", "progress": "progress", "shellcmd": "progress", "job_finished": "progress", "resources_info": "progress", "run_info": "progress", "host": "host", "info": "progress", } if level in quietness_map and self.is_quiet_about(quietness_map[level]): return False if level == "dag_debug" and not self.debug_dag: return False return TrueThis change combines the check for level in quietness_map and the corresponding quietness check, slightly improving readability and performance.
🧰 Tools
🪛 Ruff
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
Line range hint
737-920: LGTM! Logger class modifications enhance flexibility, but there's a potential issue with log level mapping.The modifications to the Logger class improve its flexibility and customization options. The addition of default_formatter and default_filter attributes, along with the updated
handlermethod, allows for more fine-grained control over logging behavior.However, there's a potential issue in the
handlermethod (lines 801-842):log_level = { "debug": _logging.DEBUG, "info": _logging.WARNING, # This mapping seems incorrect "warning": _logging.WARNING, "error": _logging.ERROR, "critical": _logging.CRITICAL, # ... other mappings ... }.get(custom_level, _logging.INFO)The "info" level is mapped to _logging.WARNING, which seems incorrect. Typically, "info" should be mapped to _logging.INFO. This mapping might cause "info" level messages to be treated as warnings, potentially leading to confusion or incorrect log filtering.
Consider updating this mapping to:
"info": _logging.INFO,Unless there's a specific reason for this mapping that I'm not aware of, this change would align with standard logging practices.
Line range hint
923-979: LGTM! setup_logger function aligns well with the new logging structure, but contains a minor typo.The modifications to the setup_logger function effectively integrate the new DefaultFormatter, DefaultFilter, and ColorizingTextHandler. This alignment with the new logging structure enhances consistency and maintainability.
However, there's a minor typo in the DefaultFormatter initialization (lines 961-965):
formatter = DefaultFormatter( printreason=printreason, show_failed_logs=show_failed_logs, printshellmds=printshellcmds, # Typo here )The parameter
printshellmdsshould beprintshellcmds. This typo could lead to unexpected behavior if theprintshellcmdssetting is not properly applied.Please correct the typo:
formatter = DefaultFormatter( printreason=printreason, show_failed_logs=show_failed_logs, printshellcmds=printshellcmds, )This correction ensures that the
printshellcmdssetting is properly passed to the DefaultFormatter.
513-513: Consider replacing lambda expressions with defined functions.The static analysis tool suggests replacing lambda expressions with defined functions. This can improve readability and maintainability. For example, on lines 513 and 581:
fmt = lambda fraction: fmt_precision(precision).format(fraction)Could be rewritten as:
def fmt(fraction): return fmt_precision(precision).format(fraction)This change makes the code more explicit and easier to understand.
Also applies to: 581-581
🧰 Tools
🪛 Ruff
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for file operations.On line 523, consider using a context manager (with statement) when opening files. This ensures that the file is properly closed after use, even if an exception occurs. For example:
with open(f, "r") as file: content = file.read()This practice helps prevent resource leaks and is considered a Python best practice.
🧰 Tools
🪛 Ruff
523-523: Use a context manager for opening files
(SIM115)
537-537: Avoid ambiguous variable names.On line 537, the variable name 'l' is used, which can be ambiguous (it looks like the number 1). Consider using a more descriptive variable name, such as 'line' or 'log_line'. This improves code readability.
🧰 Tools
🪛 Ruff
537-537: Ambiguous variable name:
l(E741)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- snakemake/logging.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
🪛 Ruff
snakemake/logging.py
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
🔇 Additional comments (1)
snakemake/logging.py (1)
Line range hint
1-979: Overall assessment: Significant improvements to logging functionality with minor issues.The changes made to
snakemake/logging.pygreatly enhance the logging capabilities of Snakemake. The introduction of DefaultFormatter, DefaultFilter, and ColorizingTextHandler classes provides better separation of concerns and increased flexibility in log handling.Key improvements:
- More granular control over log formatting and filtering.
- Enhanced colorization capabilities for better log readability.
- Improved integration with Snakemake's quietness settings.
While the overall structure and implementation are solid, there are a few minor issues to address:
- A potential incorrect log level mapping in the Logger class.
- A typo in the setup_logger function parameter.
- Some minor optimizations and code style improvements suggested by static analysis.
Addressing these minor issues will further improve the robustness and maintainability of the logging system. Great work on this refactoring effort!
🧰 Tools
🪛 Ruff
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
I think it is still fine to develop them as plugins. They can be default dependencies or internal plugins (like with the dryrun, local, and touch executor plugins). |
Sure, the output can be improved in any way. I have no strong feelings about how things are currently logged. |
Answered here: #2797 (comment) |
johanneskoester
left a comment
There was a problem hiding this comment.
See my answers on the PR message content above. Here comes the code review. I like the structured approach to splitting up the code a lot!
snakemake/logging.py
Outdated
| if level == "info": | ||
| return self.handle_info(msg) | ||
| elif level == "host": | ||
| return self.handle_host(msg) | ||
| elif level == "job_info": | ||
| return self.handle_job_info(msg) | ||
| elif level == "group_info": | ||
| return self.handle_group_info(msg) | ||
| elif level == "job_error": | ||
| return self.handle_job_error(msg) | ||
| elif level == "group_error": | ||
| return self.handle_group_error(msg) | ||
| elif level == "progress": | ||
| return self.handle_progress(msg) | ||
| elif level == "job_finished": | ||
| return self.handle_job_finished(msg) | ||
| elif level == "shellcmd": | ||
| return self.handle_shellcmd(msg) | ||
| elif level == "rule_info": | ||
| return self.handle_rule_info(msg) | ||
| elif level == "d3dag": | ||
| return self.handle_d3dag(msg) | ||
| elif level == "dag_debug": | ||
| return self.handle_dag_debug(msg) | ||
| elif level == "run_info": | ||
| return self.handle_run_info(msg) | ||
| else: | ||
| return msg["msg"] |
There was a problem hiding this comment.
I guess this is something for Python's new match statement.
There was a problem hiding this comment.
Good idea, agreed.
snakemake/logging.py
Outdated
| """Helper method to show logs.""" | ||
| for f in logs: | ||
| try: | ||
| content = open(f, "r").read() |
There was a problem hiding this comment.
I always thought the idiomatic version of this uses a with statement, even if one reads the entire file into memory.
|
Thanks for the review and answering questions @johanneskoester. This helps with the direction to take this. Currently working on consolidating the custom log levels. Will try to have more updates by end of the week. Re logger state: I agree this is a bit tricky, I will try to keep it in mind. |
00a2713 to
5b4aa42
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (3)
snakemake/logging.py (3)
537-537: Rename ambiguous variablelto improve readabilityThe variable
lcan be confused with1orI. Rename it to a more descriptive name to enhance clarity.Apply this diff:
- max_len = min(max(max(len(l) for l in lines), len(logfile_header)), 80) + max_len = min(max(max(len(line) for line in lines), len(logfile_header)), 80)🧰 Tools
🪛 Ruff
537-537: Ambiguous variable name:
l(E741)
678-678: Use!=operator instead ofnot ... ==Using
!=is more readable and idiomatic in Python.Apply this diff:
- return self.is_tty and not platform.system() == "Windows" + return self.is_tty and platform.system() != "Windows"🧰 Tools
🪛 Ruff
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Remove unused variableeThe variable
eis assigned but never used. Removing it cleans up the code.Apply this diff:
- except Exception as e: + except Exception:🧰 Tools
🪛 Ruff
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- snakemake/executors/local.py (1 hunks)
- snakemake/logging.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
snakemake/executors/local.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
📓 Learnings (1)
snakemake/logging.py (1)
Learnt from: cademirch PR: snakemake/snakemake#3107 File: snakemake/logging.py:806-830 Timestamp: 2024-09-30T17:17:37.360Z Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
🪛 Ruff
snakemake/logging.py
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
🔇 Additional comments (1)
snakemake/executors/local.py (1)
85-85: LGTM. Verify impact on logging system.The addition of "host" to the general arguments aligns with the PR objectives for refactoring the logging system. This change suggests that host-related information will now be included in the logging output.
To ensure this change doesn't introduce any unexpected behavior, please run the following verification:
snakemake/logging.py
Outdated
| if level == "dag_debug" and not self.debug_dag: | ||
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Simplify return statement
You can simplify the logic by returning the boolean expression directly.
Apply this diff:
- if level == "dag_debug" and not self.debug_dag:
- return False
- return True
+ return not (level == "dag_debug" and not self.debug_dag)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if level == "dag_debug" and not self.debug_dag: | |
| return False | |
| return True | |
| return not (level == "dag_debug" and not self.debug_dag) |
🧰 Tools
🪛 Ruff
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
snakemake/logging.py
Outdated
| if level in quietness_map: | ||
| if self.is_quiet_about(quietness_map[level]): | ||
| return False | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Combine nested if statements
Simplify the nested if statements by combining their conditions.
Apply this diff:
- if level in quietness_map:
- if self.is_quiet_about(quietness_map[level]):
- return False
+ if level in quietness_map and self.is_quiet_about(quietness_map[level]):
+ return False📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if level in quietness_map: | |
| if self.is_quiet_about(quietness_map[level]): | |
| return False | |
| if level in quietness_map and self.is_quiet_about(quietness_map[level]): | |
| return False | |
🧰 Tools
🪛 Ruff
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
snakemake/logging.py
Outdated
| content = open(f, "r").read() | ||
| except FileNotFoundError: | ||
| yield f"Logfile {f} not found." |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use a context manager when opening files
Using a context manager with open() ensures that files are properly closed after their use, even if an error occurs.
Apply this diff to use a context manager:
- content = open(f, "r").read()
+ with open(f, "r") as file_handle:
+ content = file_handle.read()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| content = open(f, "r").read() | |
| except FileNotFoundError: | |
| yield f"Logfile {f} not found." | |
| with open(f, "r") as file_handle: | |
| content = file_handle.read() | |
| except FileNotFoundError: | |
| yield f"Logfile {f} not found." |
🧰 Tools
🪛 Ruff
523-523: Use a context manager for opening files
(SIM115)
snakemake/logging.py
Outdated
| fmt = lambda fraction: fmt_precision(precision).format(fraction) | ||
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | ||
| precision += 1 |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Refactor lambda expression to a named function
Assigning a lambda expression to a variable can reduce code readability. Consider defining a named function instead.
Apply this diff to refactor the code:
- fmt = lambda fraction: fmt_precision(precision).format(fraction)
+ def fmt(fraction):
+ return fmt_precision(precision).format(fraction)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fmt = lambda fraction: fmt_precision(precision).format(fraction) | |
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | |
| precision += 1 | |
| def fmt(fraction): | |
| return fmt_precision(precision).format(fraction) | |
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | |
| precision += 1 |
🧰 Tools
🪛 Ruff
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
snakemake/logging.py
Outdated
| fmt = lambda fraction: fmt_precision(precision).format(fraction) | ||
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | ||
| precision += 1 |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Refactor lambda expression to a named function
Similar to lines 513-515, consider defining a named function for better readability.
Apply this diff:
- fmt = lambda fraction: fmt_precision(precision).format(fraction)
+ def fmt(fraction):
+ return fmt_precision(precision).format(fraction)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fmt = lambda fraction: fmt_precision(precision).format(fraction) | |
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | |
| precision += 1 | |
| def fmt(fraction): | |
| return fmt_precision(precision).format(fraction) | |
| while fmt(fraction) == "100%" or fmt(fraction) == "0%": | |
| precision += 1 |
🧰 Tools
🪛 Ruff
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
snakemake/logging.py
Outdated
| if isinstance(dict_like, Namedlist): | ||
| items = dict_like.items() | ||
| elif isinstance(dict_like, dict): | ||
| items = dict_like.items() |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Simplify if statements by combining conditions
You can combine the isinstance checks using a tuple to simplify the code.
Apply this diff:
- if isinstance(dict_like, Namedlist):
- items = dict_like.items()
- elif isinstance(dict_like, dict):
+ if isinstance(dict_like, (Namedlist, dict)):
items = dict_like.items()
else:
raise ValueError(
"bug: format_dict applied to something neither a dict nor a Namedlist"
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if isinstance(dict_like, Namedlist): | |
| items = dict_like.items() | |
| elif isinstance(dict_like, dict): | |
| items = dict_like.items() | |
| if isinstance(dict_like, (Namedlist, dict)): | |
| items = dict_like.items() | |
| else: | |
| raise ValueError( | |
| "bug: format_dict applied to something neither a dict nor a Namedlist" | |
| ) |
🧰 Tools
🪛 Ruff
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (2)
snakemake/logging.py (2)
Line range hint
737-920: Improve Logger class implementation and documentation.
Consider moving the log level mapping dictionary in the
handlermethod to a class attribute for better maintainability.Add type hints to methods to improve code readability and maintainability.
Enhance the docstring for the
logfile_hintmethod to be more descriptive.Here's a suggestion for moving the log level mapping to a class attribute:
class Logger: LOG_LEVEL_MAPPING = { "debug": _logging.DEBUG, "info": _logging.WARNING, # ... other mappings ... } def handler(self, msg): custom_level = msg.get("level", "info").lower() log_level = self.LOG_LEVEL_MAPPING.get(custom_level, _logging.INFO) # ... rest of the method ...Add type hints to methods, for example:
def set_stream_handler(self, stream_handler: _logging.Handler) -> None: # ... method implementation ... def set_level(self, level: int) -> None: # ... method implementation ... def handler(self, msg: dict) -> None: # ... method implementation ...Enhance the
logfile_hintmethod docstring:def logfile_hint(self): """ Log the location of the logfile if applicable. This method checks if the current execution mode is DEFAULT and not a dry run. If these conditions are met, it logs the relative path of the logfile. """ # ... method implementation ...These changes will improve the overall code quality and maintainability of the Logger class.
Line range hint
924-978: Improve setup_logger function implementation.
Add type hints to the function parameters and return value to improve code readability and maintainability.
Consider simplifying the quiet parameter handling.
The function is quite long and could potentially be split into smaller, more focused functions.
Here's a suggestion for adding type hints and simplifying the quiet parameter handling:
from typing import List, Optional, Set, Union from snakemake.settings.types import Quietness from snakemake_interface_executor_plugins.settings import ExecMode def setup_logger( handler: List[_logging.Handler] = [], quiet: Union[bool, Set[Quietness], None] = False, printshellcmds: bool = False, printreason: bool = True, debug_dag: bool = False, nocolor: bool = False, stdout: bool = False, debug: bool = False, mode: Optional[ExecMode] = None, show_failed_logs: bool = False, dryrun: bool = False, ) -> None: if mode is None: mode = get_default_exec_mode() quiet_set = set() if isinstance(quiet, bool) and quiet: quiet_set = {Quietness.PROGRESS, Quietness.RULES} elif isinstance(quiet, set): quiet_set = quiet elif quiet is not None: raise ValueError("Unsupported value provided for quiet mode (either bool, None or set allowed).") # ... rest of the function ...To further improve the function, consider splitting it into smaller, more focused functions:
def create_stream_handler(nocolor: bool, stdout: bool, mode: ExecMode) -> ColorizingTextHandler: # ... create and return stream handler ... def create_formatter(printreason: bool, show_failed_logs: bool, printshellcmds: bool) -> DefaultFormatter: # ... create and return formatter ... def create_filter(quiet: Set[Quietness], debug_dag: bool) -> DefaultFilter: # ... create and return filter ... def setup_logger(...) -> None: # ... main setup logic, calling the above functions ...These changes will improve the overall code quality, readability, and maintainability of the setup_logger function.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- snakemake/logging.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
📓 Learnings (1)
snakemake/logging.py (2)
Learnt from: cademirch PR: snakemake/snakemake#3107 File: snakemake/logging.py:806-830 Timestamp: 2024-09-30T17:17:37.360Z Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.Learnt from: cademirch PR: snakemake/snakemake#3107 File: snakemake/logging.py:806-830 Timestamp: 2024-10-08T17:41:54.542Z Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
🪛 Ruff
snakemake/logging.py
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
🔇 Additional comments (1)
snakemake/logging.py (1)
Line range hint
1-978: Overall improvements to the logging system with some minor suggestions for enhancement.The changes made to the
snakemake/logging.pyfile significantly improve the logging system's flexibility and functionality. The introduction ofDefaultFormatter,DefaultFilter, andColorizingTextHandlerclasses provides better separation of concerns and allows for more customized logging behavior.Key improvements:
- More structured and customizable log formatting.
- Better control over log filtering based on quietness settings and debug flags.
- Enhanced colorization of log messages with support for different terminal types.
- Updated
Loggerclass with new methods and attributes for improved logging capabilities.- Refactored
setup_loggerfunction to utilize the new classes and provide more configuration options.While these changes are generally positive, there are some minor issues and areas for improvement:
- Some code duplication (e.g.,
is_quiet_aboutmethod) that could be addressed.- Opportunities for adding type hints to improve code readability and maintainability.
- Potential for further refactoring to improve code organization and reduce complexity in some methods.
Addressing these minor issues will further enhance the overall quality and maintainability of the logging system.
🧰 Tools
🪛 Ruff
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
snakemake/logging.py
Outdated
| class DefaultFormatter(_logging.Formatter): | ||
|
|
||
| def __init__(self, printreason=False, show_failed_logs=False, printshellcmds=False): | ||
| self.printreason = printreason | ||
| self.show_failed_logs = show_failed_logs | ||
| self.printshellcmds = printshellcmds | ||
| self.last_msg_was_job_info = False |
There was a problem hiding this comment.
Fix typo in parameter name and consider adding type hints.
There's a typo in the __init__ method's parameter name. Also, consider adding type hints to improve code readability and maintainability.
Apply this diff to fix the typo and add type hints:
- def __init__(self, printreason=False, show_failed_logs=False, printshellcmds=False):
+ def __init__(self, printreason: bool = False, show_failed_logs: bool = False, printshellcmds: bool = False):
self.printreason = printreason
self.show_failed_logs = show_failed_logs
- self.printshellcmds = printshellmds
+ self.printshellcmds = printshellcmds
self.last_msg_was_job_info = False📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| class DefaultFormatter(_logging.Formatter): | |
| def __init__(self, printreason=False, show_failed_logs=False, printshellcmds=False): | |
| self.printreason = printreason | |
| self.show_failed_logs = show_failed_logs | |
| self.printshellcmds = printshellcmds | |
| self.last_msg_was_job_info = False | |
| class DefaultFormatter(_logging.Formatter): | |
| def __init__(self, printreason: bool = False, show_failed_logs: bool = False, printshellcmds: bool = False): | |
| self.printreason = printreason | |
| self.show_failed_logs = show_failed_logs | |
| self.printshellcmds = printshellcmds | |
| self.last_msg_was_job_info = False |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (1)
snakemake/logging.py (1)
401-438: LGTM: Well-structured _format_job_info method.The
_format_job_infomethod is well-implemented, handling various job information fields efficiently. The use of helper functions likeformat_itemimproves readability.Consider using a dictionary for
singleitemsinstead of conditionally appending 'reason'. This could make the code slightly more declarative:singleitems = ["jobid", "benchmark"] if self.printreason: singleitems.append("reason")Could be refactored to:
singleitems = { "jobid": True, "benchmark": True, "reason": self.printreason }Then, you can iterate over the dictionary:
for item, include in singleitems.items(): if include: fmt = format_item(item, omit=None) if fmt: output.append(fmt)This is a minor suggestion and the current implementation is perfectly acceptable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- snakemake/logging.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
📓 Learnings (1)
snakemake/logging.py (2)
Learnt from: cademirch PR: snakemake/snakemake#3107 File: snakemake/logging.py:806-830 Timestamp: 2024-10-08T17:41:54.542Z Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.Learnt from: cademirch PR: snakemake/snakemake#3107 File: snakemake/logging.py:806-830 Timestamp: 2024-09-30T17:17:37.360Z Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
🪛 Ruff
snakemake/logging.py
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
🔇 Additional comments (5)
snakemake/logging.py (5)
291-308: LGTM: Efficient implementation of format_info method.The
format_infomethod efficiently handles multiline messages and indentation. The use of list comprehension for applying indentation is a good practice.
Line range hint
737-920: LGTM: Well-structured Logger class with intentional log level mapping.The
Loggerclass is well-implemented with clear separation of concerns and good practices. The use of a dictionary to map custom log levels to standard Python logging levels in thehandlermethod is particularly noteworthy.Note: The mapping of the custom log level
'info'to_logging.WARNINGis intentional to preserve the existing log output coloring. This is consistent with the previous implementation and should be maintained.
Line range hint
924-979: LGTM: Comprehensive setup_logger function.The
setup_loggerfunction is well-implemented, providing a thorough initialization of the logging system for Snakemake. It correctly handles different quiet modes and sets up the logger with appropriate handlers and formatters.The function effectively uses the new
ColorizingTextHandler,DefaultFormatter, andDefaultFilterclasses, integrating them into the logging setup. This approach provides a good balance between customization and standard logging practices.
Line range hint
1-979: Overall assessment: Improved logging system with minor suggestions for refinement.The changes to
snakemake/logging.pyrepresent a significant improvement in the modularity and structure of Snakemake's logging system. The introduction ofDefaultFormatter,DefaultFilter, andColorizingTextHandlerclasses provides better separation of concerns and enhances customization options.Key improvements:
- More modular and extensible logging architecture.
- Better handling of different log levels and message types.
- Improved color support for log messages.
Suggestions for further refinement:
- Consider refactoring the
formatmethod inDefaultFormatterfor easier maintenance.- Simplify the
filtermethod inDefaultFilterfor better readability.- Address the duplication of the
is_quiet_aboutmethod betweenDefaultFilterandColorizingTextHandler.- Improve exception handling in the
emitmethod ofColorizingTextHandler.Overall, these changes represent a positive step forward in Snakemake's logging capabilities, with only minor adjustments needed for optimal performance and maintainability.
🧰 Tools
🪛 Ruff
513-513: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
523-523: Use a context manager for opening files
(SIM115)
537-537: Ambiguous variable name:
l(E741)
549-552: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
568-568: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
581-581: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
246-252:⚠️ Potential issueFix typo and consider adding type hints.
There's a typo in the
__init__method's parameter name. Also, consider adding type hints to improve code readability and maintainability.Apply this diff to fix the typo and add type hints:
- def __init__(self, printreason=False, show_failed_logs=False, printshellcmds=False): + def __init__(self, printreason: bool = False, show_failed_logs: bool = False, printshellcmds: bool = False): self.printreason = printreason self.show_failed_logs = show_failed_logs - self.printshellcmds = printshellmds + self.printshellcmds = printshellcmds self.last_msg_was_job_info = FalseLikely invalid or redundant comment.
snakemake/logging.py
Outdated
| def format(self, record): | ||
| """ | ||
| Override format method to format Snakemake-specific log messages. | ||
| """ | ||
| msg = record.msg | ||
| level = msg.get("level", "INFO") # Default to "INFO" if level not in message | ||
|
|
||
| # Call specific handlers based on the log level | ||
| if level == "info": | ||
| return self.format_info(msg) | ||
| elif level == "host": | ||
| return self.format_host(msg) | ||
| elif level == "job_info": | ||
| return self.format_job_info(msg) | ||
| elif level == "group_info": | ||
| return self.format_group_info(msg) | ||
| elif level == "job_error": | ||
| return self.format_job_error(msg) | ||
| elif level == "group_error": | ||
| return self.format_group_error(msg) | ||
| elif level == "progress": | ||
| return self.format_progress(msg) | ||
| elif level == "job_finished": | ||
| return self.format_job_finished(msg) | ||
| elif level == "shellcmd": | ||
| return self.format_shellcmd(msg) | ||
| elif level == "rule_info": | ||
| return self.format_rule_info(msg) | ||
| elif level == "d3dag": | ||
| return self.format_d3dag(msg) | ||
| elif level == "dag_debug": | ||
| return self.format_dag_debug(msg) | ||
| elif level == "run_info": | ||
| return self.format_run_info(msg) | ||
| else: | ||
| return msg["msg"] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider refactoring the format method for better maintainability.
The format method uses a long chain of if-elif statements to handle different log levels. This structure can become hard to maintain as new log levels are added. Consider using a dictionary dispatch or pattern matching (if using Python 3.10+) for better maintainability.
Here's an example of how you could refactor this using a dictionary dispatch:
def format(self, record):
msg = record.msg
level = msg.get("level", "INFO").lower()
format_methods = {
"info": self.format_info,
"host": self.format_host,
"job_info": self.format_job_info,
# ... add other levels here
}
format_method = format_methods.get(level, lambda m: m["msg"])
return format_method(msg)This approach makes it easier to add new log levels in the future and improves the overall readability of the method.
| class DefaultFilter: | ||
| def __init__( | ||
| self, | ||
| quiet, | ||
| debug_dag, | ||
| ) -> None: | ||
| self.quiet = quiet | ||
| self.debug_dag = debug_dag | ||
|
|
||
| def is_quiet_about(self, msg_type: str): | ||
| from snakemake.settings.enums import Quietness | ||
|
|
||
| return ( | ||
| Quietness.ALL in self.quiet | ||
| or Quietness.parse_choice(msg_type) in self.quiet | ||
| ) | ||
|
|
||
| def filter(self, record): | ||
| if self.is_quiet_about("all"): | ||
| return False | ||
|
|
||
| msg = record.msg | ||
| level = msg.get("level", "INFO").lower() | ||
|
|
||
| # Respect quiet mode filtering | ||
| quietness_map = { | ||
| "job_info": "rules", | ||
| "group_info": "rules", | ||
| "job_error": "rules", | ||
| "group_error": "rules", | ||
| "progress": "progress", | ||
| "shellcmd": "progress", # or other quietness types | ||
| "job_finished": "progress", | ||
| "resources_info": "progress", | ||
| "run_info": "progress", | ||
| "host": "host", | ||
| "info": "progress", | ||
| } | ||
|
|
||
| # Check quietness for specific levels and skip accordingly | ||
| if level in quietness_map: | ||
| if self.is_quiet_about(quietness_map[level]): | ||
| return False | ||
|
|
||
| # Handle dag_debug specifically | ||
| if level == "dag_debug" and not self.debug_dag: | ||
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Simplify the filter method for better readability.
The filter method in the DefaultFilter class is well-implemented, but it can be slightly simplified for better readability.
Consider refactoring the filter method as follows:
def filter(self, record):
if self.is_quiet_about("all"):
return False
msg = record.msg
level = msg.get("level", "INFO").lower()
quietness_map = {
"job_info": "rules",
"group_info": "rules",
"job_error": "rules",
"group_error": "rules",
"progress": "progress",
"shellcmd": "progress",
"job_finished": "progress",
"resources_info": "progress",
"run_info": "progress",
"host": "host",
"info": "progress",
}
if level in quietness_map and self.is_quiet_about(quietness_map[level]):
return False
if level == "dag_debug" and not self.debug_dag:
return False
return TrueThis simplification combines the nested if statements and removes the redundant return True at the end of the method.
🧰 Tools
🪛 Ruff
627-628: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
632-635: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
snakemake/logging.py
Outdated
| class ColorizingTextHandler(_logging.StreamHandler): | ||
| """ | ||
| Custom handler that combines colorization and Snakemake-specific formatting. | ||
| """ | ||
|
|
||
| BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) | ||
| RESET_SEQ = "\033[0m" | ||
| COLOR_SEQ = "\033[%dm" | ||
| BOLD_SEQ = "\033[1m" | ||
|
|
||
| colors = { | ||
| "WARNING": YELLOW, | ||
| "INFO": GREEN, | ||
| "DEBUG": BLUE, | ||
| "CRITICAL": MAGENTA, | ||
| "ERROR": RED, | ||
| } | ||
|
|
||
| def __init__( | ||
| self, | ||
| nocolor=False, | ||
| stream=sys.stderr, | ||
| mode=None, | ||
| ): | ||
| super().__init__(stream=stream) | ||
| self.last_msg_was_job_info = False | ||
| self._output_lock = threading.Lock() | ||
| self.nocolor = nocolor or not self.can_color_tty(mode) | ||
| self.mode = mode | ||
|
|
||
| def can_color_tty(self, mode): | ||
| """ | ||
| Check if the terminal supports colors. | ||
| """ | ||
| from snakemake_interface_executor_plugins.settings import ExecMode | ||
|
|
||
| if "TERM" in os.environ and os.environ["TERM"] == "dumb": | ||
| return False | ||
| if mode == ExecMode.SUBPROCESS: | ||
| return True | ||
| return self.is_tty and not platform.system() == "Windows" | ||
|
|
||
| @property | ||
| def is_tty(self): | ||
| isatty = getattr(self.stream, "isatty", None) | ||
| return isatty and isatty() | ||
|
|
||
| def emit(self, record): | ||
| """ | ||
| Emit a log message with custom formatting and color. | ||
| """ | ||
| with self._output_lock: | ||
| try: | ||
|
|
||
| level = record.msg.get("level", "INFO").lower() | ||
|
|
||
| if level == "job_info": | ||
| if not self.last_msg_was_job_info: | ||
| self.stream.write( | ||
| "\n" | ||
| ) # Add a blank line before a new job_info message | ||
| self.last_msg_was_job_info = True | ||
| else: | ||
| # Reset flag if the message is not a 'job_info' | ||
| self.last_msg_was_job_info = False | ||
| formatted_message = self.format(record) | ||
|
|
||
| # Apply color to the formatted message | ||
| self.stream.write(self.decorate(record, formatted_message)) | ||
| self.stream.write(getattr(self, "terminator", "\n")) | ||
| self.flush() | ||
| except BrokenPipeError as e: | ||
| raise e | ||
| except (KeyboardInterrupt, SystemExit): | ||
| pass # Ignore exceptions for these cases | ||
| except Exception as e: | ||
| self.handleError(record) | ||
|
|
||
| def is_quiet_about(self, msg_type: str): | ||
| from snakemake.settings.enums import Quietness | ||
|
|
||
| return ( | ||
| Quietness.ALL in self.quiet | ||
| or Quietness.parse_choice(msg_type) in self.quiet | ||
| ) | ||
|
|
||
| def decorate(self, record, message): | ||
| """ | ||
| Add color to the log message based on its level. | ||
| """ | ||
| message = [message] # Treat the formatted message as a list | ||
|
|
||
| # Use record.levelname to apply color if applicable | ||
| if not self.nocolor and record.levelname in self.colors: | ||
| message.insert(0, self.COLOR_SEQ % (30 + self.colors[record.levelname])) | ||
| message.append(self.RESET_SEQ) | ||
|
|
||
| return "".join(message) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve ColorizingTextHandler class implementation.
The ColorizingTextHandler class is generally well-implemented, but there are a few areas for improvement:
-
The
is_quiet_aboutmethod (lines 716-722) seems out of place in this class and is duplicated from theDefaultFilterclass. Consider removing it or moving it to a common utility module. -
The
emitmethod catches and ignores all exceptions, which might hide important errors. Consider logging these exceptions or handling them more specifically. -
The
can_color_ttymethod can be simplified.
Here's a suggestion for simplifying the can_color_tty method:
def can_color_tty(self, mode):
if "TERM" in os.environ and os.environ["TERM"] == "dumb":
return False
if mode == ExecMode.SUBPROCESS:
return True
return self.is_tty and platform.system() != "Windows"For the emit method, consider modifying the exception handling:
def emit(self, record):
with self._output_lock:
try:
# ... existing code ...
except BrokenPipeError:
raise
except (KeyboardInterrupt, SystemExit):
# Re-raise these exceptions
raise
except Exception:
self.handleError(record)This change will re-raise KeyboardInterrupt and SystemExit exceptions, which are typically used for program flow control, while still catching and handling other exceptions.
Would you like me to propose a common utility module for the is_quiet_about method to resolve the duplication issue?
🧰 Tools
🪛 Ruff
678-678: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
713-713: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
I've been thinking about this lately as I've been digging into the execmodes and its effect on logging. I'm still a bit confused with the API and have been looking into the LocalExecutor to try and understand, but I can't seem to understand if/where the API is constructed by the executor? Do you have any resources/diagrams of the how this works? If I understand what you're saying here is that a logger instance could be passed to the constructor of the API instance? I'm also not sure how much this matters for local execution to be honest. Currently things are setup such that loggers in subprocess don't log anything (which makes sense to me): snakemake/snakemake/executors/local.py Lines 84 to 85 in 1874fa8 I was thinking of making this more explicit by just attaching NullHandler to the logger instead of the StreamHandler when in subproccess mode. Overall, my understanding is that we only care about logs that come from the main Snakemake process (and by extension any exectuor plugins - which should be in the same process?), so having a global logger makes sense in this way. However its possible I'm missing what you're saying here. In a remote context, it makes sense that the logger writes to stderr/file, but that doesn't contend with the main Snakemake process so this is okay. In this scenario, we only "care" about the logs from the main Snakemake process, because remote process is totally independent. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (3)
snakemake/logging.py (3)
291-549: Well-structured formatter with room for improvement.The
DefaultFormatterclass is comprehensive and well-structured, providing formatting for various log types. However, there are a few areas that could be improved:
The
formatmethod (lines 298-335) uses a long chain of if-elif statements. Consider refactoring this to use a dictionary dispatch or pattern matching (if using Python 3.10+) for better maintainability.Some methods like
_format_job_info,_format_job_error, and_format_group_errorare quite long. Consider breaking these down into smaller, more focused methods to improve readability and maintainability.Here's an example of how you could refactor the
formatmethod using a dictionary dispatch:def format(self, record): msg = record.msg level = msg.get("level", "INFO").lower() format_methods = { "info": self.format_info, "host": self.format_host, "job_info": self.format_job_info, # ... add other levels here } format_method = format_methods.get(level, lambda m: m["msg"]) return format_method(msg)This approach makes it easier to add new log levels in the future and improves the overall readability of the method.
575-623: Well-implemented filter with room for optimization.The
DefaultFilterclass is well-structured and provides necessary filtering functionality. However, there are a couple of areas for improvement:
The
filtermethod can be simplified for better readability.The
is_quiet_aboutmethod appears to be duplicated in theColorizingTextHandlerclass. Consider moving this method to a common utility module to avoid duplication.Here's a suggested simplification for the
filtermethod:def filter(self, record): if self.is_quiet_about("all"): return False msg = record.msg level = msg.get("level", "INFO").lower() quietness_map = { "job_info": "rules", "group_info": "rules", "job_error": "rules", "group_error": "rules", "progress": "progress", "shellcmd": "progress", "job_finished": "progress", "resources_info": "progress", "run_info": "progress", "host": "host", "info": "progress", } if level in quietness_map and self.is_quiet_about(quietness_map[level]): return False if level == "dag_debug" and not self.debug_dag: return False return TrueThis simplification combines the nested if statements and removes the redundant
return Trueat the end of the method.Would you like me to propose a common utility module for the
is_quiet_aboutmethod to resolve the duplication issue?🧰 Tools
🪛 Ruff
615-616: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
620-623: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
626-723: Well-implemented handler with areas for improvement.The
ColorizingTextHandlerclass is generally well-implemented, providing useful colorization functionality. However, there are a few areas for improvement:
The
is_quiet_aboutmethod (lines 703-709) seems out of place in this class and is duplicated from theDefaultFilterclass. Consider removing it or moving it to a common utility module.The
emitmethod catches and ignores all exceptions, which might hide important errors. Consider logging these exceptions or handling them more specifically.The
can_color_ttymethod can be simplified.Here's a suggestion for simplifying the
can_color_ttymethod:def can_color_tty(self, mode): if "TERM" in os.environ and os.environ["TERM"] == "dumb": return False if mode == ExecMode.SUBPROCESS: return True return self.is_tty and platform.system() != "Windows"For the
emitmethod, consider modifying the exception handling:def emit(self, record): with self._output_lock: try: # ... existing code ... except BrokenPipeError: raise except (KeyboardInterrupt, SystemExit): # Re-raise these exceptions raise except Exception: self.handleError(record)This change will re-raise KeyboardInterrupt and SystemExit exceptions, which are typically used for program flow control, while still catching and handling other exceptions.
Would you like me to propose a common utility module for the
is_quiet_aboutmethod to resolve the duplication issue?🧰 Tools
🪛 Ruff
666-666: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
700-700: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- snakemake/logging.py (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
snakemake/logging.py (1)
Pattern
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
🪛 Ruff
snakemake/logging.py
253-256: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
272-272: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
285-285: Do not assign a
lambdaexpression, use adefRewrite
fmtas adef(E731)
555-555: Use a context manager for opening files
(SIM115)
569-569: Ambiguous variable name:
l(E741)
615-616: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
620-623: Return the condition
not (level == "dag_debug" and not self.debug_dag)directlyReplace with
return not (level == "dag_debug" and not self.debug_dag)(SIM103)
666-666: Use
platform.system() != "Windows"instead ofnot platform.system() == "Windows"Replace with
!=operator(SIM201)
700-700: Local variable
eis assigned to but never usedRemove assignment to unused variable
e(F841)
929-932: Use ternary operator
quiet = {Quietness.PROGRESS, Quietness.RULES} if quiet else set()instead ofif-else-blockReplace
if-else-block withquiet = {Quietness.PROGRESS, Quietness.RULES} if quiet else set()(SIM108)
🔇 Additional comments (3)
snakemake/logging.py (3)
247-265: LGTM: Well-implemented dictionary formatting function.The
format_dictfunction is well-implemented, providing flexible formatting for bothNamedlistand regular dictionaries. The use ofisinstancechecks is appropriate for type-specific handling.🧰 Tools
🪛 Ruff
253-256: Combine
ifbranches using logicaloroperatorCombine
ifbranches(SIM114)
Line range hint
725-900: Excellent improvements to the Logger class.The updates to the
Loggerclass significantly enhance its functionality and integration with the new formatting and filtering components. Key improvements include:
- Addition of
level,default_formatter, anddefault_filterattributes, which improve the logger's configurability.- Updated
setup_logfilemethod that now properly configures the formatter and filter for the logfile handler.- Restructured
handlermethod with a comprehensive mapping of custom log levels to standard Python logging levels.- New
set_levelmethod for easier log level configuration.These changes make the logger more flexible and easier to customize, which is crucial for a complex system like Snakemake.
Line range hint
906-959: Well-implemented logger setup with improved flexibility.The
setup_loggerfunction has been effectively updated to integrate the new logging components and provide more flexible configuration options. Notable improvements include:
- Use of the new
ColorizingTextHandler,DefaultFormatter, andDefaultFilterclasses, which enhances the logging capabilities.- More robust handling of the 'quiet' parameter, now supporting boolean, None, or set values, which provides greater flexibility in configuring logging verbosity.
- Differentiated setup for subprocess mode, which is an efficient optimization for different execution contexts.
These changes make the logger setup more adaptable to various use cases and execution environments, which is crucial for a versatile tool like Snakemake.
🧰 Tools
🪛 Ruff
929-932: Use ternary operator
quiet = {Quietness.PROGRESS, Quietness.RULES} if quiet else set()instead ofif-else-blockReplace
if-else-block withquiet = {Quietness.PROGRESS, Quietness.RULES} if quiet else set()(SIM108)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.github/workflows/main.yml (2)
128-138: Capture All Pytest Output for Better Logging ConsistencyThe testing job now uses the
--show-capture=allflag, which will capture both stdout and stderr. This aligns well with the refactored logging improvements and should help in diagnosing issues. Please verify that the increased verbosity does not overwhelm the logs.
205-211: Ensure Consistent Output Capture Across JobsWhile the "testing" job uses
--show-capture=all, the "testing-windows" job still uses--show-capture=stderr(line 210). For consistency and to utilize the logging enhancements uniformly, consider updating the Windows job's pytest command to use--show-capture=allas well—unless there is a specific reason for the discrepancy.Proposed Diff if Alignment is Desired:
- python -m pytest --show-capture=stderr -v -x --splits 10 --group ${{ matrix.test_group }} --splitting-algorithm=least_duration tests/tests.py + python -m pytest --show-capture=all -v -x --splits 10 --group ${{ matrix.test_group }} --splitting-algorithm=least_duration tests/tests.py
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.github/workflows/main.yml(3 hunks)snakemake/cli.py(6 hunks)snakemake/dag.py(7 hunks)snakemake/jobs.py(6 hunks)snakemake/scheduler.py(7 hunks)snakemake/script/__init__.py(1 hunks)snakemake/settings/enums.py(1 hunks)snakemake/settings/types.py(2 hunks)snakemake/workflow.py(9 hunks)test-environment.yml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- snakemake/settings/enums.py
- test-environment.yml
- snakemake/script/init.py
- snakemake/settings/types.py
- snakemake/scheduler.py
- snakemake/workflow.py
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.py`: Do not try to improve formatting. Do not suggest ...
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
snakemake/dag.pysnakemake/cli.pysnakemake/jobs.py
🪛 actionlint (1.7.4)
.github/workflows/main.yml
53-53: "exclude" section should not be empty
(syntax-check)
🔇 Additional comments (24)
snakemake/dag.py (10)
17-17: Appropriate import addition for enhanced functionality.Adding the
jsonimport supports the updated implementation of thed3dagmethod which now outputs formatted JSON.
31-31: Good integration with the new logging system.Importing
LogEventfrom the newsnakemake_interface_logger_plugins.commonmodule aligns with the PR objective of implementing a more modular logging framework.
1028-1030: Improved logging with structured context.The change from
logger.dag_debugto a standard Pythonlogger.debugwith structured context in theextraparameter enhances log filtering capabilities and standardizes logging practices.
1116-1119: Structured logging for job selection.This update follows the same pattern of adding structured context to log messages, making it easier to track which jobs are selected during DAG processing.
1121-1128: Enhanced error logging with structured context.The change improves error logging by including detailed structured information about exceptions encountered during DAG processing. The
LogEvent.DEBUG_DAGcategorization helps filter these specific logs.
1204-1211: Structured logging for file presence detection.This change standardizes the logging for the case where no producers are found for a file, but the file exists on disk. The added structured context improves debuggability.
2780-2780: Modern f-string usage.Using an f-string for the print statement is a good practice for better readability and maintenance.
2782-2794: Improved JSON output formatting.The restructured print statement creates more readable and properly formatted JSON output, which is beneficial for downstream consumption by visualization tools.
2812-2812: Enhanced method signature with explicit return type.Adding the explicit return type annotation
tuple[str, dict[str, int]]improves code readability and enables better IDE support and static type checking.
2815-2837: Well-structured refactoring of the stats method.The method has been refactored to:
- Create a tabular output using the
tabulatepackage for better readability- Build a dictionary that can be programmatically accessed
- Return both the formatted message and the statistics dictionary
This change enhances the API by providing structured data alongside the human-readable format, which enables better integration with other components.
snakemake/cli.py (7)
20-21: Well-structured import for the new logger plugin system.This import aligns with the PR objective of creating a more modular logging framework using plugins.
1489-1495: New--loggerargument properly replaces previous logging options.The new argument allows specifying multiple logger plugins and restricts choices to properly registered plugins, which is a good improvement over the previous
--log-handler-scriptand--log-servicearguments. This enables more flexible logging configurations as described in the PR objectives.
1871-1875: Well-designed dictionary comprehension for logger settings.This elegantly collects settings for each specified logger plugin, creating a dictionary that maps plugin names to their settings.
1905-1917: Properly structured OutputSettings configuration.The OutputSettings object is now properly configured with log_handler_settings, which is a clean approach to passing logging configuration throughout the application.
1919-1924: Robust validation and initialization of log handlers.The code properly validates settings for each logger plugin before creating the handlers, which is a good practice. The naming conventions align with the discussions in the PR comments about renaming
logger_plugintolog_handler.
1925-1930: Centralized logger setup using the new LoggerManager.This code sets up the centralized logger manager with the appropriate mode, handlers, and settings, which aligns with the PR's goal of standardizing logging practices.
1732-1732: Registration of logger plugin CLI arguments.This line ensures that any additional CLI arguments from logger plugins are properly registered with the argument parser, which is important for extensibility.
snakemake/jobs.py (7)
33-33: Appropriate import for logging event types.The import of LogEvent from the logger plugins interface allows for structured categorization of log events, which is essential for the refactored logging system.
1068-1099: Improved structured logging in Job.log_info method.The refactored code now uses the standard logger.info method with a comprehensive extra parameter containing all relevant job information. This structured approach makes logs more consistent and machine-parsable, which aligns with the PR's goal of creating a modular logging framework.
1100-1104: Separate structured logging for shell commands.Shell commands are now logged separately with their own event type, making it easier to filter and process these specific log entries. This separation improves clarity and log organization.
1128-1141: Updated error logging field names for consistency.The error logging info now uses consistent field names (rule_name, rule_msg) which aligns with the naming conventions in the job_info method, improving overall consistency.
1146-1149: Improved error logging with structured data.The error logging now passes structured data via the extra parameter, making error logs more consistent and machine-parsable. This is a key improvement for the refactored logging system.
1432-1438: Structured logging for group jobs.The GroupJob.log_info method now uses structured logging with appropriate event types and relevant group information, consistent with the approach used in the Job class.
1446-1455: Well-structured error logging for group jobs.The GroupJob.log_error method now uses structured logging with a comprehensive set of data in the extra dictionary, providing thorough context for debugging group job errors.
| exclude: | ||
| [] | ||
| #- shell: dash | ||
| # py_ver: "3.11" |
There was a problem hiding this comment.
Empty "exclude" Section May Cause Configuration Warnings
The empty exclude key (with an empty list) is flagged by actionlint. If no exclusions are intended, consider removing the key entirely or updating it with a meaningful value or a clarifying comment. This will help avoid static analysis warnings and improve workflow clarity.
Proposed Diff:
- exclude:
- []
- #- shell: dash
- # py_ver: "3.11"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| exclude: | |
| [] | |
| #- shell: dash | |
| # py_ver: "3.11" |
🧰 Tools
🪛 actionlint (1.7.4)
53-53: "exclude" section should not be empty
(syntax-check)
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
snakemake/logging.py (1)
362-362:⚠️ Potential issueFix method reference in _format_group_error method.
The method incorrectly uses
self.show_logs(logs)but there's noshow_logsmethod in theDefaultFormatterclass. It should use the globalshow_logsfunction instead.- output.extend(self.show_logs(logs)) + output.extend(show_logs(logs))
🧹 Nitpick comments (4)
snakemake/logging.py (4)
398-400: Simplify nested if statements.Combine the nested if statements to improve readability.
- if event in quietness_map: - if quietness_map[event] in self.quiet: - return False + if event in quietness_map and quietness_map[event] in self.quiet: + return False🧰 Tools
🪛 Ruff (0.8.2)
398-399: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
403-406: Simplify conditional return.You can simplify this logic by returning the boolean expression directly.
- if event == LogEvent.DEBUG_DAG and not self.debug_dag: - return False - - return True + return not (event == LogEvent.DEBUG_DAG and not self.debug_dag)🧰 Tools
🪛 Ruff (0.8.2)
403-406: Return the negated condition directly
Inline condition
(SIM103)
474-477: Simplify conditional return in can_color_tty method.The method can be simplified by returning the boolean expression directly.
- if has_tty and not is_windows: - return True - - return False + return bool(has_tty and not is_windows)🧰 Tools
🪛 Ruff (0.8.2)
474-477: Return the condition
bool(has_tty and not is_windows)directlyReplace with
return bool(has_tty and not is_windows)(SIM103)
657-657: Simplify dictionary key lookup.Use
key in dictrather thankey in dict.keys()for better performance and readability.- for handler in self.logfile_handlers.keys(): + for handler in self.logfile_handlers: handler.close()🧰 Tools
🪛 Ruff (0.8.2)
657-657: Use
key in dictinstead ofkey in dict.keys()Remove
.keys()(SIM118)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
snakemake/exceptions.py(2 hunks)snakemake/logging.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- snakemake/exceptions.py
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.py`: Do not try to improve formatting. Do not suggest ...
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
snakemake/logging.py
🧠 Learnings (1)
snakemake/logging.py (2)
Learnt from: cademirch
PR: snakemake/snakemake#3107
File: snakemake/logging.py:806-830
Timestamp: 2025-03-12T13:29:22.341Z
Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
Learnt from: johanneskoester
PR: snakemake/snakemake#3107
File: snakemake/logging.py:510-515
Timestamp: 2025-03-12T13:29:09.905Z
Learning: In Snakemake's logging handlers, KeyboardInterrupt and SystemExit exceptions should be ignored (pass) rather than re-raised, as these are control flow signals that should not disrupt the logging process. The exception variable in the final except block should be kept for potential debugging purposes.
🪛 Ruff (0.8.2)
snakemake/logging.py
86-86: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
398-399: Use a single if statement instead of nested if statements
Combine if statements using and
(SIM102)
403-406: Return the negated condition directly
Inline condition
(SIM103)
474-477: Return the condition bool(has_tty and not is_windows) directly
Replace with return bool(has_tty and not is_windows)
(SIM103)
657-657: Use key in dict instead of key in dict.keys()
Remove .keys()
(SIM118)
🔇 Additional comments (4)
snakemake/logging.py (4)
512-512: Exception handling follows project convention.This exception handling correctly ignores KeyboardInterrupt and SystemExit as per project convention, which ensures these control flow signals don't disrupt the logging process.
663-677: Good implementation of error handling in setup_logfile.The addition of error handling for file system operations in setup_logfile is a good improvement. It will prevent crashes if the log directory cannot be created or the log file cannot be written.
525-528: Intentional mapping of INFO to WARNING color for specific events.This code correctly implements the intentional mapping of certain INFO-level events to the WARNING color to preserve the existing log output coloring, as mentioned in retrieved learnings.
536-686: Well-structured LoggerManager class for centralized logging.The new LoggerManager class provides a clear, centralized approach to managing logging, which aligns with the PR objective of creating a more modular logging framework. The implementation handles setup, teardown, and configuration properly.
🧰 Tools
🪛 Ruff (0.8.2)
657-657: Use
key in dictinstead ofkey in dict.keys()Remove
.keys()(SIM118)
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
snakemake/api.py (1)
597-604: Supply a meaningful log message instead ofNone.
PassingNonemay yield unclear logs. Consider this minor improvement:- logger.info(None, extra=dict( + logger.info("Workflow started", extra=dict( event=LogEvent.WORKFLOW_STARTED, workflow_id=uuid.uuid4(), snakefile=self.workflow_api.snakefile, ))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
snakemake/api.py(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.py`: Do not try to improve formatting. Do not suggest ...
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
snakemake/api.py
🔇 Additional comments (7)
snakemake/api.py (7)
15-15: No issues with adding the UUID import.
Everything appears correct, allowing for unique workflow identifiers.
48-49: New logger plugin imports look fine.
This prepares the code for plugin-based logging without apparent issues.
53-53: Refactored import fromsnakemake.loggingis appropriate.
Transitioning to a dedicatedlogger_manageraligns with the revised design.
249-261: Logger setup logic looks solid.
Constructing and validating log handlers on demand aligns well with plugin architecture. No immediate concerns regarding error handling or concurrency.
493-497: Clean integration with the logging setup.
Invokingsetup_loggerwith dynamic parameters (dryrun, exec_mode) is consistent and maintains flexibility.
590-590: File-based logging initialization is consistent with the new design.
Checkinglogger_manager.setup_logfile()here is logical and maintains clear separation of logger concerns.
162-163:Details
❓ Verification inconclusive
Verify logger manager behavior upon cleanup.
Callinglogger_manager.cleanup_logfile()andlogger_manager.stop()is logical, but please ensure no errors arise if the logger was never initialized or is used concurrently.Run the following script to search for partial or skipped initialization patterns in
logger_managerusage:
🏁 Script executed:
#!/bin/bash # Description: Verify no partial initialization or concurrency pitfalls in logger_manager usage. rg -A 5 'logger_manager\.'Length of output: 3416
Verify logger_manager cleanup and concurrent usage
The calls to
logger_manager.cleanup_logfile()andlogger_manager.stop()at lines 162–163 insnakemake/api.pyappear consistent with how logger initialization is conditionally handled elsewhere in the file (e.g., the check forlogger_manager.initializedbefore setup). However, please ensure that:
- The implementations of
cleanup_logfile()andstop()gracefully handle cases where the logger was never initialized.- These methods are thread-safe and idempotent to prevent issues when accessed concurrently.
Please review the internal logic of these methods within the logger manager to confirm that these concerns are adequately addressed.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
snakemake/logging.py (3)
367-406: Simplify conditionals in DefaultFilter.The filter method can be simplified by combining nested conditions and simplifying the return statement.
def filter(self, record): from snakemake.settings.enums import Quietness event, level = get_event_level(record) if self.dryrun and level == "run_info": return True if Quietness.ALL in self.quiet and not self.dryrun: return False quietness_map = { LogEvent.JOB_INFO: Quietness.RULES, LogEvent.GROUP_INFO: Quietness.RULES, LogEvent.JOB_ERROR: Quietness.RULES, LogEvent.GROUP_ERROR: Quietness.RULES, LogEvent.PROGRESS: Quietness.PROGRESS, LogEvent.SHELLCMD: Quietness.PROGRESS, LogEvent.JOB_FINISHED: Quietness.PROGRESS, LogEvent.RESOURCES_INFO: Quietness.PROGRESS, LogEvent.RUN_INFO: Quietness.PROGRESS, } # Check quietness for specific levels - if event in quietness_map: - if quietness_map[event] in self.quiet: - return False + if event in quietness_map and quietness_map[event] in self.quiet: + return False # Handle dag_debug specifically - if event == LogEvent.DEBUG_DAG and not self.debug_dag: - return False - - return True + return not (event == LogEvent.DEBUG_DAG and not self.debug_dag)🧰 Tools
🪛 Ruff (0.8.2)
398-399: Use a single
ifstatement instead of nestedifstatementsCombine
ifstatements usingand(SIM102)
403-406: Return the negated condition directly
Inline condition
(SIM103)
453-482: Simplify can_color_tty method.The method can be simplified for better readability.
def can_color_tty(self, mode): """ Colors are supported when: 1. Terminal is not "dumb" 2. Running in subprocess mode 3. Using a TTY on non-Windows systems """ from snakemake_interface_executor_plugins.settings import ExecMode # Case 1: Check if terminal is "dumb" if os.environ.get("TERM") == "dumb": return False # Case 2: Always support colors in subprocess mode if mode == ExecMode.SUBPROCESS: return True # Case 3: Support colors on TTY except for Windows is_windows = platform.system() == "Windows" has_tty = self.is_tty - if has_tty and not is_windows: - return True - - return False + return has_tty and not is_windows🧰 Tools
🪛 Ruff (0.8.2)
474-477: Return the condition
bool(has_tty and not is_windows)directlyReplace with
return bool(has_tty and not is_windows)(SIM103)
653-659: Clean implementation of cleanup_logfile method.The method correctly closes file handlers to ensure proper resource cleanup.
def cleanup_logfile(self): from snakemake_interface_executor_plugins.settings import ExecMode if self.mode == ExecMode.DEFAULT: - for handler in self.logfile_handlers.keys(): + for handler in self.logfile_handlers: handler.close()This simplifies the code slightly by directly iterating over the dictionary keys.
🧰 Tools
🪛 Ruff (0.8.2)
657-657: Use
key in dictinstead ofkey in dict.keys()Remove
.keys()(SIM118)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
snakemake/logging.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.py`: Do not try to improve formatting. Do not suggest ...
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
snakemake/logging.py
🧠 Learnings (1)
snakemake/logging.py (2)
Learnt from: cademirch
PR: snakemake/snakemake#3107
File: snakemake/logging.py:806-830
Timestamp: 2025-03-12T14:50:45.354Z
Learning: In the Snakemake project's `Logger.handler` method, the mapping of the custom log level `'info'` to `_logging.WARNING` is intentional to preserve the existing log output coloring.
Learnt from: johanneskoester
PR: snakemake/snakemake#3107
File: snakemake/logging.py:510-515
Timestamp: 2025-03-12T14:50:33.209Z
Learning: In Snakemake's logging handlers, KeyboardInterrupt and SystemExit exceptions should be ignored (pass) rather than re-raised, as these are control flow signals that should not disrupt the logging process. The exception variable in the final except block should be kept for potential debugging purposes.
🪛 Ruff (0.8.2)
snakemake/logging.py
86-86: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
398-399: Use a single if statement instead of nested if statements
Combine if statements using and
(SIM102)
403-406: Return the negated condition directly
Inline condition
(SIM103)
474-477: Return the condition bool(has_tty and not is_windows) directly
Replace with return bool(has_tty and not is_windows)
(SIM103)
657-657: Use key in dict instead of key in dict.keys()
Remove .keys()
(SIM118)
⏰ Context from checks skipped due to timeout of 90000ms (30)
- GitHub Check: testing (10, 3.12, bash)
- GitHub Check: testing (10, 3.11, bash)
- GitHub Check: testing (9, 3.12, bash)
- GitHub Check: testing (9, 3.11, bash)
- GitHub Check: testing (8, 3.12, bash)
- GitHub Check: testing (8, 3.11, bash)
- GitHub Check: testing (7, 3.12, bash)
- GitHub Check: testing (7, 3.11, bash)
- GitHub Check: testing (6, 3.12, bash)
- GitHub Check: testing (6, 3.11, bash)
- GitHub Check: testing-windows (10)
- GitHub Check: testing (5, 3.12, bash)
- GitHub Check: testing-windows (9)
- GitHub Check: testing (5, 3.11, bash)
- GitHub Check: testing-windows (8)
- GitHub Check: testing (4, 3.12, bash)
- GitHub Check: testing (4, 3.11, bash)
- GitHub Check: testing-windows (7)
- GitHub Check: testing (3, 3.12, bash)
- GitHub Check: testing-windows (6)
- GitHub Check: testing (3, 3.11, bash)
- GitHub Check: testing-windows (5)
- GitHub Check: testing (2, 3.12, bash)
- GitHub Check: testing-windows (4)
- GitHub Check: testing (2, 3.11, bash)
- GitHub Check: testing-windows (3)
- GitHub Check: testing (1, 3.12, bash)
- GitHub Check: testing-windows (2)
- GitHub Check: testing (1, 3.11, bash)
- GitHub Check: testing-windows (1)
🔇 Additional comments (32)
snakemake/logging.py (32)
32-34: Nice addition of a timestamp helper function.This helper function improves code clarity and promotes reuse by centralizing timestamp formatting.
37-59: Well-implementedshow_logsfunction with proper error handling.The implementation properly handles file opening with a context manager, manages different error cases (FileNotFoundError, UnicodeDecodeError), and formats the log output with appropriate headers and separators.
62-79: Good implementation offormat_dictfunction.The function correctly handles both dict and Namedlist types, properly filters keys and values, and provides clear error messages for invalid inputs.
82-84: Good use of partial function application.Using
partialfrom functools for creating specialized formatting functions is an elegant solution.
86-87: Well-implemented resource name formatting.The function correctly filters out specified resources from the output.
🧰 Tools
🪛 Ruff (0.8.2)
86-86: Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
90-105: Consider simplifying theformat_percentagefunction.The function is well-implemented but the lambda assignment could be replaced with a proper named function for better readability (which has already been implemented in line 100).
108-121: Good implementation ofget_event_levelfunction.The function correctly extracts the log level from a record and provides good type hints and documentation.
124-129: Well-implementedis_quiet_aboutfunction.The function correctly parses and checks the quietness setting against the message type.
132-142: Clean implementation of DefaultFormatter class initialization.The initializer properly handles defaults and stores the configuration values.
144-168: Well-organized formatter dispatch in DefaultFormatter.Using a dictionary to map event types to formatting methods creates a clean, maintainable solution that's easy to extend.
170-187: Good implementation of message formatting with indentation support.The method handles multiline messages correctly and applies consistent indentation.
189-195: Simple and direct implementation of formatters.Both the
format_run_infoandformat_hostmethods are concise and straightforward.
197-209: Well-implemented job info formatting.The method correctly formats job information including timestamp, reason, and other details.
211-215: Simple and clear group info formatting.The method correctly formats group information messages.
217-229: Well-implemented error formatting.Both
format_job_errorandformat_group_errormethods delegate to helper methods for detailed formatting.
231-245: Clear implementation of progress and job finished formatting.The methods provide appropriate formatting for progress and job finished messages, and the shellcmd formatting respects the printshellcmds setting.
247-265: Good implementation of various formatting methods.The
format_d3dagandformat_dag_debugmethods provide appropriate formatting for their respective message types.
267-304: Well-structured job info formatting helper.The helper method provides detailed and consistent formatting of job information.
306-334: Comprehensive job error formatting.The method provides detailed error information and conditionally shows logs based on configuration.
336-364: Thorough group error formatting.The method provides detailed formatting of group errors, including information about individual jobs and logs.
409-433: Well-structured ColorizingTextHandler class with good organization of constants.The class constants are well-defined and the documentation clearly explains the purpose of the class.
434-452: Good initialization of ColorizingTextHandler.The constructor properly initializes the handler with the stream, formatter, and filter.
484-514: Properly implemented emit method with mutex protection.The emit method correctly uses a lock to ensure thread safety and properly handles record formatting and output. The exception handling follows the project's conventions as per the retrieved learning.
516-533: Good implementation of the decorate method.The method correctly adds color to messages based on their level and respects the nocolor setting.
536-545: Well-designed LoggerManager class initialization.The initializer sets up the necessary attributes for managing logging.
546-599: Comprehensive setup method in LoggerManager.The setup method correctly configures handlers based on the execution mode and settings, and sets up a queue listener for asynchronous logging.
600-617: Good implementation of default configuration methods.The methods properly configure plugins with default filters and formatters.
619-638: Well-implemented default handler creation methods.The methods for creating default file and stream handlers properly configure them with the appropriate formatters and filters.
640-651: Good implementation of logfile_hint method.The method correctly logs the location of logfiles and returns them for potential use.
660-677: Good error handling in setup_logfile method.The method includes try-except blocks to handle potential file system errors when creating directories and files.
679-681: Proper resource cleanup in the stop method.The method checks if the queue listener exists and is initialized before stopping it.
684-686: Good global logger configuration.Setting up a global logger and logger manager makes the logging system accessible throughout the application.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
snakemake/cli.py (1)
1871-1875: Naming inconsistency in log handler settingsWhile the implementation is functionally correct, there's a naming inconsistency based on previous discussions in the PR. You're using
log_handler_settingsas the variable name (good), but the code still references "logger" in the context.Based on johanneskoester's feedback, it was decided to rename from
logger_plugintolog_handler. Consider updating variable names accordingly.log_handler_settings = { name: LoggerPluginRegistry().get_plugin(name).get_settings(args) for name in args.logger }Additionally, consider whether the registry class itself should be renamed from
LoggerPluginRegistrytoLogHandlerRegistryfor consistency, but that would be a change in the imported package.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
snakemake/cli.py(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.py`: Do not try to improve formatting. Do not suggest ...
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
snakemake/cli.py
⏰ Context from checks skipped due to timeout of 90000ms (30)
- GitHub Check: testing (10, 3.12, bash)
- GitHub Check: testing (10, 3.11, bash)
- GitHub Check: testing (9, 3.12, bash)
- GitHub Check: testing (9, 3.11, bash)
- GitHub Check: testing (8, 3.12, bash)
- GitHub Check: testing (8, 3.11, bash)
- GitHub Check: testing (7, 3.12, bash)
- GitHub Check: testing (7, 3.11, bash)
- GitHub Check: testing (6, 3.12, bash)
- GitHub Check: testing (6, 3.11, bash)
- GitHub Check: testing (5, 3.12, bash)
- GitHub Check: testing-windows (10)
- GitHub Check: testing (5, 3.11, bash)
- GitHub Check: testing-windows (9)
- GitHub Check: testing (4, 3.12, bash)
- GitHub Check: testing (4, 3.11, bash)
- GitHub Check: testing-windows (7)
- GitHub Check: testing (3, 3.12, bash)
- GitHub Check: testing-windows (6)
- GitHub Check: testing (3, 3.11, bash)
- GitHub Check: testing-windows (5)
- GitHub Check: testing (2, 3.12, bash)
- GitHub Check: testing-windows (4)
- GitHub Check: testing (2, 3.11, bash)
- GitHub Check: testing-windows (3)
- GitHub Check: testing (1, 3.12, bash)
- GitHub Check: testing-windows (2)
- GitHub Check: testing (1, 3.11, bash)
- GitHub Check: testing-windows (1)
- GitHub Check: apidocs
🔇 Additional comments (5)
snakemake/cli.py (5)
20-21: Import added for the new logger plugin registryThis import correctly brings in the LoggerPluginRegistry class, which is essential for the new logging system.
1488-1495: Logger plugin CLI argument looks good but consider renamingThe implementation adds proper support for specifying one or more custom loggers via the
--loggerargument. The choices are correctly restricted to registered logger plugins.Based on previous discussions in the PR comments, there was consideration to rename from
logger_plugintolog_handlerfor consistency. The CLI flag name here still uses "logger" rather than "log-handler", but this might be intentional for user-friendly naming.Was this naming deliberate? If you want to maintain consistency with the discussed naming scheme, you might consider renaming this to
--log-handler.
1732-1732: Registering CLI args for logger pluginsThis correctly registers any additional CLI arguments from the logger plugins, similar to how it's done for other plugin types.
1907-1915: API configuration updated with log handler settingsThis correctly passes the log handler settings to the OutputSettings object, replacing the previous log_handlers parameter. This is part of the breaking change mentioned in the PR objectives, where user-provided log handlers will now need to follow the new plugin interface pattern.
Note: Based on a previous review comment, there was discussion about changing
log_handlersfrom a sequence of objects to a sequence of strings (plugin names):@dataclass class OutputSettings(SettingsBase): ... - log_handlers: Sequence[object] = tuple() + log_handlers: Sequence[str] = tuple()But I don't see that reflected in this PR. If that change was made elsewhere or postponed, that's fine - just making note of it.
2157-2161: Clean main entry pointThis function correctly maintains its simplicity, delegating to the parse_args and args_to_api functions for the actual work.
|
🤖 I have created a release *beep* *boop* --- ## [9.0.0](v8.30.0...v9.0.0) (2025-03-14) ### ⚠ BREAKING CHANGES * Logging refactor & add LoggerPluginInterface ([#3107](#3107)) ### Features * [#3412](#3412) - keep shadow folder of failed job if --keep-incomplete flag is set. ([#3430](#3430)) ([22978c3](22978c3)) * add flag --report-after-run to automatically generate the report after a successfull workflow run ([#3428](#3428)) ([b0a7f03](b0a7f03)) * add flatten function to IO utils ([#3424](#3424)) ([67fa392](67fa392)) * add helper functions to parse input files ([#2918](#2918)) ([63e45a7](63e45a7)) * Add option to print redacted file names ([#3089](#3089)) ([ba4d264](ba4d264)) * add support for validation of polars dataframe and lazyframe ([#3262](#3262)) ([c7473a6](c7473a6)) * added support for rendering dag with mermaid js ([#3409](#3409)) ([7bf8381](7bf8381)) * adding --replace-workflow-config to fully replace workflow configs (from config: directive) with --configfile, instead of merging them ([#3381](#3381)) ([47504a0](47504a0)) * Dynamic module name ([#3401](#3401)) ([024dc32](024dc32)) * Enable saving and reloading IOCache object ([#3386](#3386)) ([c935953](c935953)) * files added in rule params with workflow.source_path will be available in used containers ([#3385](#3385)) ([a6e45bf](a6e45bf)) * Fix keep_local in storage directive and more freedom over remote retrieval behaviour ([#3410](#3410)) ([67b4739](67b4739)) * inherit parameters of use rule and extend/replace individual items them when using 'with' directive ([#3365](#3365)) ([93e4b92](93e4b92)) * Logging refactor & add LoggerPluginInterface ([#3107](#3107)) ([86f1d6e](86f1d6e)) * Maximal file size for checksums ([#3368](#3368)) ([b039f8a](b039f8a)) * Modernize package configuration using Pixi ([#3369](#3369)) ([77992d8](77992d8)) * multiext support for named input/output ([#3372](#3372)) ([05e1378](05e1378)) * optionally auto-group jobs via temp files in case of remote execution ([#3378](#3378)) ([cc9bba2](cc9bba2)) ### Bug Fixes * `--delete-all-output` ignores `--dry-run` ([#3265](#3265)) ([23fef82](23fef82)) * 3342 faster touch runs and warning messages for non-existing files ([#3398](#3398)) ([cd9c3c3](cd9c3c3)) * add default value to max-jobs-per-timespan ([#3043](#3043)) ([2959abe](2959abe)) * checkpoints inside modules are overwritten ([#3359](#3359)) ([fba3ac7](fba3ac7)) * Convert Path to IOFile ([#3405](#3405)) ([c58684c](c58684c)) * Do not perform storage object cleanup with --keep-storage-local-copies set ([#3358](#3358)) ([9a6d14b](9a6d14b)) * edgecases of source deployment in case of remote execution ([#3396](#3396)) ([5da13be](5da13be)) * enhance error message formatting for strict DAG-building mode ([#3376](#3376)) ([a1c39ee](a1c39ee)) * fix bug in checkpoint handling that led to exceptions in case checkpoint output was missing upon rerun ([#3423](#3423)) ([8cf4a2f](8cf4a2f)) * force check all required outputs ([#3341](#3341)) ([495a4e7](495a4e7)) * group job formatting ([#3442](#3442)) ([f0b10a3](f0b10a3)) * in remote jobs, upload storage in topological order such that modification dates are preserved (e.g. in case of group jobs) ([#3377](#3377)) ([eace08f](eace08f)) * only skip eval when resource depends on input ([#3374](#3374)) ([4574c92](4574c92)) * Prevent execution of conda in apptainer when not explicitly requested in software deployment method ([#3388](#3388)) ([c43c5c0](c43c5c0)) * print filenames with quotes around them in RuleException ([#3269](#3269)) ([6baeda5](6baeda5)) * Re-evaluation of free resources ([#3399](#3399)) ([6371293](6371293)) * ReadTheDocs layout issue due to src directory change ([#3419](#3419)) ([695b127](695b127)) * robustly escaping quotes in generated bash scripts (v2) ([#3297](#3297)) ([#3389](#3389)) ([58720bd](58720bd)) * Show apptainer image URL in snakemake report ([#3407](#3407)) ([45f0450](45f0450)) * Update ReadTheDocs configuration for documentation build to use Pixi ([#3433](#3433)) ([3f227a6](3f227a6)) ### Documentation * Add pixi setup instructions to general use tutorial ([#3382](#3382)) ([115e81b](115e81b)) * fix contribution section heading levels, fix docs testing setup order ([#3360](#3360)) ([051dc53](051dc53)) * fix link to github.com/snakemake/poetry-snakemake-plugin ([#3436](#3436)) ([ec6d97c](ec6d97c)) * fix quoting ([#3394](#3394)) ([b40f599](b40f599)) * fix rerun-triggers default ([#3403](#3403)) ([4430e23](4430e23)) * fix typo 'safe' -> 'save' ([#3384](#3384)) ([7755861](7755861)) * mention code formatting in the contribution section ([#3431](#3431)) ([e8682b7](e8682b7)) * remove duplicated 'functions'. ([#3356](#3356)) ([7c595db](7c595db)) * update broken links documentation ([#3437](#3437)) ([e3d0d88](e3d0d88)) * Updating contributing guidelines with new pixi dev setup ([#3415](#3415)) ([8e95a12](8e95a12)) --- 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: snakemake-bot <snakemake-bot-admin@googlegroups.com>
Followup to snakemake#2830. This PR attempts to make Snakemake's logging more modular to make room for a potential logging plugin interface, or other logging/monitoring additions, as well as make logging in Snakemake more standardized. Currently, most of logging logic is deep within `logging.Logger`, mainly in `text_handler()` with direct calls to `logger.info/warn/etc`. In this PR, this logic has been refactored to more comply with the standard `logging` practices. To do so, three new classes were added: 1. `ColorizingTextHandler` manages adding color to log records and emitting to stream. 2. `DefaultFormatter` formats log records to be printed. Much of the logic from `text_handler()` is here now. 3. `DefaultFilter` filters log records from being emitted based on level and quietness settings. Together, these classes should output identical logs to the current logging implementation. I've tested this briefly, but further testing would be good. With these changes, it is much easier to add new `logHandlers` via `logger.addHandler`. New handlers can decide how to format and filter logs themselves, an important characteristic missing in the current implementation that motivated this refactoring (snakemake#2830 (comment)). This would be a breaking change for `--log-handler-script`, as user provided log handlers will need to subclass `logging.logHandler`. Furthermore, logic for adding the user handler (and formatter/filter) will need to be added. As for the logging plug interface (snakemake#2830 (comment)), I'm not sure that is the route to take, but would appreciate thoughts on this. First, this functionality is already largely provided by `--log-handler-script` and adding a logging plugin would be redundant. Second, I think that development of such plugins would likely be minimal, and coordinated effort in this repo to maintain a good set of logging options would be preferable. For example: - A standard HTTP log handler send emit log records to a user supplied address on which a 3rd party monitoring app is listening. - A handler/formatter that uses [rich](https://github.com/Textualize/rich) to make a prettier/interactive console on which logs are written to. I think both of these additions would benefit being standardized to Snakemake, as well as default options to users. Additionally I think it would be worthwhile to unify logging in Snakemake. Currently, the use of the wrapper `logging.Logger` class and `setup_logger()` function being called in multiple places makes it difficult to work with the logger and is likely the source of some issues (snakemake#3044, snakemake#2974, snakemake#2882, snakemake#2797, snakemake#2727). Unifying the logger and its configuration as recommended by the [logging docs](https://docs.python.org/3/howto/logging-cookbook.html#a-cli-application-starter-template) would make it easier to work the the logger. Another option would be to use something like [loguru](https://loguru.readthedocs.io/en/stable/index.html). Whichever option, I think simplifying and unifying the logger should be the next step of this PR. One main question I have is how much we would like to preserve the current stderr output formatting. Most of logging logic is in formatting rule info and such. Moving forward we can now try different formatting. Personally, I find the current detailed rule output helpful for sanity checks when developing and doing a dry run. However, when actually running the workflow (especially a large one) the terminal is flooded and errors can quickly be lost which requires searching the log file. Of course this can be alleviated with --quiet. Another question is regarding the two calls of setup_logger() brought up in snakemake#2797. I still don't understand why these are necessary and understanding this would be helpful in unifying this. I'm happy to keep pushing this forward but would appreciate your thoughts before continuing @johanneskoester! I <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new CLI option (--logger) for configuring custom loggers. - Enhanced structured logging delivers clearer runtime and resource insights, including rule graph logging. - Added a new method for logging rule graph information. - Added a new enumeration value `REASON` to the `Quietness` class. - **Refactor** - Overhauled the logging architecture across execution, scheduling, and workflow components for improved traceability and error handling. - **Chores** - Updated dependencies and setup configurations to include new logger plugins. - Adjusted test configurations to capture all output, ensuring more comprehensive test diagnostics. - **Bug Fixes** - Resolved minor command and formatting inconsistencies for smoother operations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Johannes Köster <johannes.koester@tu-dortmund.de> Co-authored-by: Johannes Köster <johannes.koester@uni-due.de> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [9.0.0](snakemake/snakemake@v8.30.0...v9.0.0) (2025-03-14) ### ⚠ BREAKING CHANGES * Logging refactor & add LoggerPluginInterface ([snakemake#3107](snakemake#3107)) ### Features * [snakemake#3412](snakemake#3412) - keep shadow folder of failed job if --keep-incomplete flag is set. ([snakemake#3430](snakemake#3430)) ([22978c3](snakemake@22978c3)) * add flag --report-after-run to automatically generate the report after a successfull workflow run ([snakemake#3428](snakemake#3428)) ([b0a7f03](snakemake@b0a7f03)) * add flatten function to IO utils ([snakemake#3424](snakemake#3424)) ([67fa392](snakemake@67fa392)) * add helper functions to parse input files ([snakemake#2918](snakemake#2918)) ([63e45a7](snakemake@63e45a7)) * Add option to print redacted file names ([snakemake#3089](snakemake#3089)) ([ba4d264](snakemake@ba4d264)) * add support for validation of polars dataframe and lazyframe ([snakemake#3262](snakemake#3262)) ([c7473a6](snakemake@c7473a6)) * added support for rendering dag with mermaid js ([snakemake#3409](snakemake#3409)) ([7bf8381](snakemake@7bf8381)) * adding --replace-workflow-config to fully replace workflow configs (from config: directive) with --configfile, instead of merging them ([snakemake#3381](snakemake#3381)) ([47504a0](snakemake@47504a0)) * Dynamic module name ([snakemake#3401](snakemake#3401)) ([024dc32](snakemake@024dc32)) * Enable saving and reloading IOCache object ([snakemake#3386](snakemake#3386)) ([c935953](snakemake@c935953)) * files added in rule params with workflow.source_path will be available in used containers ([snakemake#3385](snakemake#3385)) ([a6e45bf](snakemake@a6e45bf)) * Fix keep_local in storage directive and more freedom over remote retrieval behaviour ([snakemake#3410](snakemake#3410)) ([67b4739](snakemake@67b4739)) * inherit parameters of use rule and extend/replace individual items them when using 'with' directive ([snakemake#3365](snakemake#3365)) ([93e4b92](snakemake@93e4b92)) * Logging refactor & add LoggerPluginInterface ([snakemake#3107](snakemake#3107)) ([86f1d6e](snakemake@86f1d6e)) * Maximal file size for checksums ([snakemake#3368](snakemake#3368)) ([b039f8a](snakemake@b039f8a)) * Modernize package configuration using Pixi ([snakemake#3369](snakemake#3369)) ([77992d8](snakemake@77992d8)) * multiext support for named input/output ([snakemake#3372](snakemake#3372)) ([05e1378](snakemake@05e1378)) * optionally auto-group jobs via temp files in case of remote execution ([snakemake#3378](snakemake#3378)) ([cc9bba2](snakemake@cc9bba2)) ### Bug Fixes * `--delete-all-output` ignores `--dry-run` ([snakemake#3265](snakemake#3265)) ([23fef82](snakemake@23fef82)) * 3342 faster touch runs and warning messages for non-existing files ([snakemake#3398](snakemake#3398)) ([cd9c3c3](snakemake@cd9c3c3)) * add default value to max-jobs-per-timespan ([snakemake#3043](snakemake#3043)) ([2959abe](snakemake@2959abe)) * checkpoints inside modules are overwritten ([snakemake#3359](snakemake#3359)) ([fba3ac7](snakemake@fba3ac7)) * Convert Path to IOFile ([snakemake#3405](snakemake#3405)) ([c58684c](snakemake@c58684c)) * Do not perform storage object cleanup with --keep-storage-local-copies set ([snakemake#3358](snakemake#3358)) ([9a6d14b](snakemake@9a6d14b)) * edgecases of source deployment in case of remote execution ([snakemake#3396](snakemake#3396)) ([5da13be](snakemake@5da13be)) * enhance error message formatting for strict DAG-building mode ([snakemake#3376](snakemake#3376)) ([a1c39ee](snakemake@a1c39ee)) * fix bug in checkpoint handling that led to exceptions in case checkpoint output was missing upon rerun ([snakemake#3423](snakemake#3423)) ([8cf4a2f](snakemake@8cf4a2f)) * force check all required outputs ([snakemake#3341](snakemake#3341)) ([495a4e7](snakemake@495a4e7)) * group job formatting ([snakemake#3442](snakemake#3442)) ([f0b10a3](snakemake@f0b10a3)) * in remote jobs, upload storage in topological order such that modification dates are preserved (e.g. in case of group jobs) ([snakemake#3377](snakemake#3377)) ([eace08f](snakemake@eace08f)) * only skip eval when resource depends on input ([snakemake#3374](snakemake#3374)) ([4574c92](snakemake@4574c92)) * Prevent execution of conda in apptainer when not explicitly requested in software deployment method ([snakemake#3388](snakemake#3388)) ([c43c5c0](snakemake@c43c5c0)) * print filenames with quotes around them in RuleException ([snakemake#3269](snakemake#3269)) ([6baeda5](snakemake@6baeda5)) * Re-evaluation of free resources ([snakemake#3399](snakemake#3399)) ([6371293](snakemake@6371293)) * ReadTheDocs layout issue due to src directory change ([snakemake#3419](snakemake#3419)) ([695b127](snakemake@695b127)) * robustly escaping quotes in generated bash scripts (v2) ([snakemake#3297](snakemake#3297)) ([snakemake#3389](snakemake#3389)) ([58720bd](snakemake@58720bd)) * Show apptainer image URL in snakemake report ([snakemake#3407](snakemake#3407)) ([45f0450](snakemake@45f0450)) * Update ReadTheDocs configuration for documentation build to use Pixi ([snakemake#3433](snakemake#3433)) ([3f227a6](snakemake@3f227a6)) ### Documentation * Add pixi setup instructions to general use tutorial ([snakemake#3382](snakemake#3382)) ([115e81b](snakemake@115e81b)) * fix contribution section heading levels, fix docs testing setup order ([snakemake#3360](snakemake#3360)) ([051dc53](snakemake@051dc53)) * fix link to github.com/snakemake/poetry-snakemake-plugin ([snakemake#3436](snakemake#3436)) ([ec6d97c](snakemake@ec6d97c)) * fix quoting ([snakemake#3394](snakemake#3394)) ([b40f599](snakemake@b40f599)) * fix rerun-triggers default ([snakemake#3403](snakemake#3403)) ([4430e23](snakemake@4430e23)) * fix typo 'safe' -> 'save' ([snakemake#3384](snakemake#3384)) ([7755861](snakemake@7755861)) * mention code formatting in the contribution section ([snakemake#3431](snakemake#3431)) ([e8682b7](snakemake@e8682b7)) * remove duplicated 'functions'. ([snakemake#3356](snakemake#3356)) ([7c595db](snakemake@7c595db)) * update broken links documentation ([snakemake#3437](snakemake#3437)) ([e3d0d88](snakemake@e3d0d88)) * Updating contributing guidelines with new pixi dev setup ([snakemake#3415](snakemake#3415)) ([8e95a12](snakemake@8e95a12)) --- 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: snakemake-bot <snakemake-bot-admin@googlegroups.com>



Followup to #2830.
This PR attempts to make Snakemake's logging more modular to make room for a potential logging plugin interface, or other logging/monitoring additions, as well as make logging in Snakemake more standardized.
Currently, most of logging logic is deep within
logging.Logger, mainly intext_handler()with direct calls tologger.info/warn/etc. In this PR, this logic has been refactored to more comply with the standardloggingpractices. To do so, three new classes were added:ColorizingTextHandlermanages adding color to log records and emitting to stream.DefaultFormatterformats log records to be printed. Much of the logic fromtext_handler()is here now.DefaultFilterfilters log records from being emitted based on level and quietness settings.Together, these classes should output identical logs to the current logging implementation. I've tested this briefly, but further testing would be good.
With these changes, it is much easier to add new
logHandlersvialogger.addHandler. New handlers can decide how to format and filter logs themselves, an important characteristic missing in the current implementation that motivated this refactoring (#2830 (comment)).This would be a breaking change for
--log-handler-script, as user provided log handlers will need to subclasslogging.logHandler. Furthermore, logic for adding the user handler (and formatter/filter) will need to be added.As for the logging plug interface (#2830 (comment)), I'm not sure that is the route to take, but would appreciate thoughts on this. First, this functionality is already largely provided by
--log-handler-scriptand adding a logging plugin would be redundant. Second, I think that development of such plugins would likely be minimal, and coordinated effort in this repo to maintain a good set of logging options would be preferable. For example:I think both of these additions would benefit being standardized to Snakemake, as well as default options to users.
Additionally I think it would be worthwhile to unify logging in Snakemake. Currently, the use of the wrapper
logging.Loggerclass andsetup_logger()function being called in multiple places makes it difficult to work with the logger and is likely the source of some issues (#3044, #2974, #2882, #2797, #2727). Unifying the logger and its configuration as recommended by the logging docs would make it easier to work the the logger. Another option would be to use something like loguru. Whichever option, I think simplifying and unifying the logger should be the next step of this PR.One main question I have is how much we would like to preserve the current stderr output formatting. Most of logging logic is in formatting rule info and such. Moving forward we can now try different formatting. Personally, I find the current detailed rule output helpful for sanity checks when developing and doing a dry run. However, when actually running the workflow (especially a large one) the terminal is flooded and errors can quickly be lost which requires searching the log file. Of course this can be alleviated with --quiet.
Another question is regarding the two calls of setup_logger() brought up in #2797. I still don't understand why these are necessary and understanding this would be helpful in unifying this.
I'm happy to keep pushing this forward but would appreciate your thoughts before continuing @johanneskoester! I
Summary by CodeRabbit
New Features
REASONto theQuietnessclass.Refactor
Chores
Bug Fixes