Skip to content

Commit 9eb1aa7

Browse files
committed
[fix#cli]: Improve exception handling and error output
A global exception handler has been added that intercepts critical errors (for example, `FileNotFoundError` or other unexpected failures) and outputs a clear message to the user instead of a full stack of calls. - For debugging, the full traceback is now sent to logs, if the logging level allows it. - For this, support for the `exc_info` parameter has been added to `Console.print`. - The "Validation failed" message now has a WARNING level.
1 parent 6ba03a2 commit 9eb1aa7

3 files changed

Lines changed: 62 additions & 8 deletions

File tree

‎src/code_validator/cli.py‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,24 @@ def run_from_cli() -> None:
105105
console.print("Validation successful.", level=LogLevel.INFO, is_verdict=True)
106106
sys.exit(ExitCode.SUCCESS)
107107
else:
108-
console.print("Validation failed.", level=LogLevel.INFO, is_verdict=True)
108+
console.print("Validation failed.", level=LogLevel.WARNING, is_verdict=True)
109109
sys.exit(ExitCode.VALIDATION_FAILED)
110110

111111
except CodeValidatorError as e:
112-
console.print("Error: Internal Error of validator!", level=LogLevel.CRITICAL)
113-
logger.exception(f"Traceback for CodeValidatorError: {e}")
112+
console.print(
113+
f"Error: An internal validator error occurred: {e}", level=LogLevel.CRITICAL, show_user=True, exc_info=True
114+
)
114115
sys.exit(ExitCode.VALIDATION_FAILED)
115116
except FileNotFoundError as e:
116-
console.print(f"Error: File not found - {e.filename}!", level=LogLevel.CRITICAL)
117-
logger.exception(f"Traceback for FileNotFoundError: {e}")
117+
console.print(
118+
f"Error: Input file not found: {e.filename}", level=LogLevel.CRITICAL, show_user=True, exc_info=True
119+
)
118120
sys.exit(ExitCode.FILE_NOT_FOUND)
119121
except Exception as e:
120-
console.print(f"An unexpected error occurred: {e.__class__.__name__}!", level=LogLevel.CRITICAL)
121-
logger.exception(f"Traceback for unexpected error: {e}")
122+
console.print(
123+
f"Error: An unexpected error occurred: {e.__class__.__name__}. See logs for detailed traceback.",
124+
level=LogLevel.CRITICAL,
125+
show_user=True,
126+
exc_info=True,
127+
)
122128
sys.exit(ExitCode.UNEXPECTED_ERROR)

‎src/code_validator/core.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,49 @@ def _parse_ast_tree(self) -> bool:
166166
self._console.print(f"Syntax Error found: {e}", level=LogLevel.ERROR)
167167
return False
168168

169+
def _report_errors(self) -> None:
170+
"""Formats and prints collected validation errors to the console.
171+
172+
This method is responsible for presenting the final list of failed
173+
rules to the user. It respects the `--max-messages` configuration
174+
to avoid cluttering the terminal. If the number of found errors
175+
exceeds the specified limit, it truncates the output and displays
176+
a summary message indicating how many more errors were found.
177+
178+
The method retrieves the list of failed rules from `self._failed_rules`
179+
and the display limit from `self._config`. All user-facing output is
180+
channeled through the `self._console` object.
181+
182+
It performs the following steps:
183+
1. Checks if any errors were recorded. If not, it returns immediately.
184+
2. Determines the subset of errors to display based on the configured
185+
`max_messages` limit (a value of 0 means no limit).
186+
3. Iterates through the selected error rules and prints their
187+
failure messages.
188+
4. If the error list was truncated, prints a summary line, e.g.,
189+
"... (5 more errors found)".
190+
"""
191+
max_errors = self._config.max_messages
192+
num_errors = len(self._failed_rules)
193+
194+
if num_errors == 0:
195+
return None
196+
197+
errors_to_show = self._failed_rules
198+
if 0 < max_errors < num_errors:
199+
errors_to_show = self._failed_rules[:max_errors]
200+
201+
for rule in errors_to_show:
202+
self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
203+
204+
if 0 < max_errors < num_errors:
205+
remaining_count = num_errors - max_errors
206+
self._console.print(
207+
f"... ({remaining_count} more error{'s' if remaining_count > 1 else ''} found)",
208+
level=LogLevel.WARNING,
209+
show_user=True,
210+
)
211+
169212
def run(self) -> bool:
170213
"""Runs the entire validation process from start to finish.
171214
@@ -184,6 +227,7 @@ def run(self) -> bool:
184227
self._load_and_parse_rules()
185228

186229
if not self._parse_ast_tree():
230+
self._report_errors()
187231
return False
188232

189233
self._console.print("Lead source code, load and parse rules and parsing code - PASS", level=LogLevel.DEBUG)
@@ -223,4 +267,6 @@ def run(self) -> bool:
223267
else:
224268
self._console.print(f"Rule {rule.config.rule_id} - PASS", level=LogLevel.INFO)
225269

270+
self._report_errors()
271+
226272
return not self._failed_rules

‎src/code_validator/output.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ def print(
210210
level: LogLevel | Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = LogLevel.TRACE,
211211
is_verdict: bool = False,
212212
show_user: bool = False,
213+
exc_info: bool = False,
213214
) -> None:
214215
"""Prints a message to stdout and logs it simultaneously.
215216
@@ -228,9 +229,10 @@ def print(
228229
show_user: If True and `is_verdict=False`, allows
229230
printing non-verdict messages to stdout. Defaults to
230231
False.
232+
exc_info: If True this work as loggings.exception("<message>").
231233
"""
232234
level_num = logging.getLevelName(level if isinstance(level, LogLevel) else level)
233-
self._logger.log(level_num, message, stacklevel=2)
235+
self._logger.log(level_num, message, stacklevel=2, exc_info=exc_info)
234236

235237
if (not self._is_quiet) and ((not is_verdict and show_user) or (is_verdict and self._show_verdict)):
236238
print(message, file=self._stdout)

0 commit comments

Comments
 (0)