From 8f50204b9f10eb6eded10a56f21e04255e8a41a4 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Tue, 28 Oct 2025 16:43:00 +0000 Subject: [PATCH 1/3] Log exception for better output --- src/fastcs/control_system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fastcs/control_system.py b/src/fastcs/control_system.py index 3f9fb4cde..82dbf019d 100644 --- a/src/fastcs/control_system.py +++ b/src/fastcs/control_system.py @@ -139,8 +139,8 @@ async def block_forever(): await asyncio.gather(*coros) except asyncio.CancelledError: pass - except Exception as e: - raise RuntimeError("Unhandled exception in serve") from e + except Exception: + logger.exception("Unhandled exception in serve") finally: logger.info("Shutting down FastCS") self._stop_scan_tasks() From 60f9c5a2228ac60879ebb66c75c19fbc1edcf1cd Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Wed, 29 Oct 2025 09:30:45 +0000 Subject: [PATCH 2/3] Try to give clearer error for p4p Value creation failure --- src/fastcs/transport/epics/pva/pvi_tree.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/fastcs/transport/epics/pva/pvi_tree.py b/src/fastcs/transport/epics/pva/pvi_tree.py index e4f8726ff..22480478a 100644 --- a/src/fastcs/transport/epics/pva/pvi_tree.py +++ b/src/fastcs/transport/epics/pva/pvi_tree.py @@ -139,15 +139,18 @@ def make_p4p_value(self) -> Value: raw_value = self._make_p4p_raw_value() p4p_type = self._make_type_for_raw_value(raw_value) - return Value( - p4p_type, - { - **p4p_alarm_states(), - **p4p_timestamp_now(), - **display, - "value": raw_value, - }, - ) + try: + return Value( + p4p_type, + { + **p4p_alarm_states(), + **p4p_timestamp_now(), + **display, + "value": raw_value, + }, + ) + except KeyError as e: + raise ValueError(f"Failed to create p4p Value from {raw_value}") from e def make_provider( self, From f6dce44fbd0453ca0693e23e86d2d498ca8aede9 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Thu, 30 Oct 2025 14:14:00 +0000 Subject: [PATCH 3/3] Log exceptions in all attribute callbacks Update still re-raises to stop the update loop flooding logs --- src/fastcs/attributes.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/fastcs/attributes.py b/src/fastcs/attributes.py index 14eba97d0..331299577 100644 --- a/src/fastcs/attributes.py +++ b/src/fastcs/attributes.py @@ -163,7 +163,14 @@ async def update(self, value: T) -> None: self._value = self._datatype.validate(value) if self._on_update_callbacks is not None: - await asyncio.gather(*[cb(self._value) for cb in self._on_update_callbacks]) + try: + await asyncio.gather( + *[cb(self._value) for cb in self._on_update_callbacks] + ) + except Exception as e: + logger.opt(exception=e).error( + "On update callback failed", attribute=self, value=value + ) def add_on_update_callback(self, callback: AttrOnUpdateCallback[T]) -> None: """Add a callback to be called when the value of the attribute is updated @@ -197,8 +204,8 @@ async def update_attribute(): try: self.log_event("Update attribute", topic=self) await update_callback(self) - except Exception: - logger.opt(exception=True).error("Update loop failed", attribute=self) + except Exception as e: + logger.opt(exception=e).error("Update loop failed", attribute=self) raise return update_attribute @@ -246,10 +253,20 @@ async def put(self, setpoint: T, sync_setpoint: bool = False) -> None: """ setpoint = self._datatype.validate(setpoint) if self._on_put_callback is not None: - await self._on_put_callback(self, setpoint) + try: + await self._on_put_callback(self, setpoint) + except Exception as e: + logger.opt(exception=e).error( + "Put failed", attribute=self, setpoint=setpoint + ) if sync_setpoint: - await self._call_sync_setpoint_callbacks(setpoint) + try: + await self._call_sync_setpoint_callbacks(setpoint) + except Exception as e: + logger.opt(exception=e).error( + "Sync setpoint failed", attribute=self, setpoint=setpoint + ) async def _call_sync_setpoint_callbacks(self, setpoint: T) -> None: if self._sync_setpoint_callbacks: