Skip to content

Commit 1df1330

Browse files
authored
Merge pull request #372 from simvue-io/hotfix/user-alerts-for-error-status
Use alerts for executor process reporting and stdout if stderr not available
2 parents 7ec1a96 + d3ceea5 commit 1df1330

File tree

2 files changed

+54
-10
lines changed

2 files changed

+54
-10
lines changed

simvue/executor.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def __init__(self, simvue_runner: "simvue.Run", keep_logs: bool = True) -> None:
9292
self._exit_codes = self._manager.dict()
9393
self._std_err = self._manager.dict()
9494
self._std_out = self._manager.dict()
95+
self._alert_ids: dict[str, str] = {}
9596
self._command_str: typing.Dict[str, str] = {}
9697
self._processes: typing.Dict[str, multiprocessing.Process] = {}
9798

@@ -221,6 +222,9 @@ def callback_function(status_code: int, std_out: str, std_err: str) -> None:
221222
env,
222223
),
223224
)
225+
self._alert_ids[identifier] = self._runner.create_alert(
226+
name=f"{identifier}_exit_status", source="user"
227+
)
224228
logger.debug(f"Executing process: {' '.join(_command)}")
225229
self._processes[identifier].start()
226230

@@ -239,6 +243,14 @@ def exit_status(self) -> int:
239243

240244
return 0
241245

246+
def get_error_summary(self) -> dict[str, typing.Optional[str]]:
247+
"""Returns the summary messages of all errors"""
248+
return {
249+
identifier: self._get_error_status(identifier)
250+
for identifier, value in self._exit_codes.items()
251+
if value
252+
}
253+
242254
def get_command(self, process_id: str) -> str:
243255
"""Returns the command executed within the given process.
244256
@@ -256,7 +268,19 @@ def get_command(self, process_id: str) -> str:
256268
raise KeyError(f"Failed to retrieve '{process_id}', no such process")
257269
return self._command_str[process_id]
258270

259-
def _log_events(self) -> None:
271+
def _get_error_status(self, process_id: str) -> typing.Optional[str]:
272+
err_msg: typing.Optional[str] = None
273+
274+
# Return last 10 lines of stdout if stderr empty
275+
if not (err_msg := self._std_err[process_id]) and (
276+
std_out := self._std_out[process_id]
277+
):
278+
err_msg = " Tail STDOUT:\n\n"
279+
start_index = -10 if len(lines := std_out.split("\n")) > 10 else 0
280+
err_msg += "\n".join(lines[start_index:])
281+
return err_msg
282+
283+
def _update_alerts(self) -> None:
260284
"""Send log events for the result of each process"""
261285
for proc_id, code in self._exit_codes.items():
262286
if code != 0:
@@ -265,11 +289,9 @@ def _log_events(self) -> None:
265289
if self._runner._dispatcher:
266290
self._runner._dispatcher.purge()
267291

268-
_err = self._std_err[proc_id]
269-
_msg = f"Process {proc_id} returned non-zero exit status {code} with:\n{_err}"
292+
self._runner.log_alert(self._alert_ids[proc_id], "critical")
270293
else:
271-
_msg = f"Process {proc_id} completed successfully."
272-
self._runner.log_event(_msg)
294+
self._runner.log_alert(self._alert_ids[proc_id], "ok")
273295

274296
# Wait for the dispatcher to send the latest information before
275297
# allowing the executor to finish (and as such the run instance to exit)
@@ -321,7 +343,7 @@ def wait_for_completion(self) -> None:
321343
for process in self._processes.values():
322344
if process.is_alive():
323345
process.join()
324-
self._log_events()
346+
self._update_alerts()
325347
self._save_output()
326348

327349
if not self.success:

simvue/run.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,19 @@ def __exit__(
178178
self._dispatcher.join()
179179

180180
if _non_zero := self.executor.exit_status:
181-
logger.error(
182-
f"Simvue process executor terminated with non-zero exit status {_non_zero}"
181+
_error_msgs: dict[str, typing.Optional[str]] = (
182+
self.executor.get_error_summary()
183+
)
184+
_error_msg = "\n".join(
185+
f"{identifier}:\n{msg}" for identifier, msg in _error_msgs.items()
186+
)
187+
if _error_msg:
188+
_error_msg = f":\n{_error_msg}"
189+
click.secho(
190+
"Simvue process executor terminated with non-zero exit status "
191+
f"{_non_zero}{_error_msg}",
192+
fg="red",
193+
bold=True,
183194
)
184195
sys.exit(_non_zero)
185196

@@ -1375,8 +1386,19 @@ def close(self) -> bool:
13751386
self._dispatcher.join()
13761387

13771388
if _non_zero := self.executor.exit_status:
1378-
logger.error(
1379-
f"Simvue process executor terminated with non-zero exit status {_non_zero}"
1389+
_error_msgs: dict[str, typing.Optional[str]] = (
1390+
self.executor.get_error_summary()
1391+
)
1392+
_error_msg = "\n".join(
1393+
f"{identifier}:\n{msg}" for identifier, msg in _error_msgs.items()
1394+
)
1395+
if _error_msg:
1396+
_error_msg = f":\n{_error_msg}"
1397+
click.secho(
1398+
"Simvue process executor terminated with non-zero exit status "
1399+
f"{_non_zero}{_error_msg}",
1400+
fg="red",
1401+
bold=True,
13801402
)
13811403
sys.exit(_non_zero)
13821404

0 commit comments

Comments
 (0)