From 0bd225b04b0d75c62e0377a87cabad755fcad920 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Thu, 7 May 2026 12:50:10 -0700 Subject: [PATCH 01/14] Replace SLIMS calls with subprocess call to waterlog app --- pyproject.toml | 1 - src/foraging_gui/Foraging.py | 174 ++++++++++++----------------------- 2 files changed, 59 insertions(+), 116 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f034a5e98..f29e8868a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ dependencies = [ "pyOSC3@git+https://github.com/glopesdev/pyosc3.git@master", "newscale@git+https://github.com/AllenNeuralDynamics/python-newscale@axes-on-target", "aind-auto-train@git+https://github.com/AllenNeuralDynamics/aind-foraging-behavior-bonsai-automatic-training.git@main", - "aind-slims-api@git+https://github.com/AllenNeuralDynamics/aind-slims-api@main", "aind-dynamic-foraging-models >= 0.12.2", "aind-dynamic-foraging-basic-analysis >= 0.3.34", "aind-behavior-services >=0.8, <0.9", diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 5379b37f4..3c1652229 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -31,7 +31,6 @@ from aind_auto_train.schema.task import TrainingStage from aind_behavior_services.session import AindBehaviorSessionModel from aind_data_schema.core.session import Session -from aind_slims_api import SlimsClient, models from matplotlib.backends.backend_qt5agg import ( NavigationToolbar2QT as NavigationToolbar, ) @@ -238,9 +237,6 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): # Connect to Bonsai self._InitializeBonsai() - # connect to Slims - self._ConnectSlims() - # Set up threads self.threadpool = QThreadPool() # get animal response self.threadpool2 = QThreadPool() # get animal lick @@ -1966,6 +1962,7 @@ def _GetSettings(self): "clear_figure_after_save": True, "add_default_project_name": True, "check_schedule": False, + "waterlog_exe_path": "C://Program Files/AIBS_MPE/waterlog/waterlog.exe", } # Try to load the ForagingSettings.json file @@ -2097,71 +2094,8 @@ def _GetSettings(self): ] self.rig_name = "{}".format(self.current_box) - def _ConnectSlims(self): - """ - Connect to Slims - """ - try: - logging.info("Attempting to connect to Slims") - self.slims_client = SlimsClient( - username=os.environ["SLIMS_USERNAME"], - password=os.environ["SLIMS_PASSWORD"], - ) - except KeyError as e: - raise KeyError( - "SLIMS_USERNAME and SLIMS_PASSWORD do not exist as " - f"environment variables on machine. Please add. {e}" - ) - - try: - self.slims_client.fetch_model( - models.SlimsMouseContent, barcode="00000000" - ) - except Exception as e: - if "Status 401 – Unauthorized" in str( - e - ): # catch error if username and password are incorrect - raise Exception( - f"Exception trying to read from Slims: {e}.\n" - f" Please check credentials:\n" - f"Username: {os.environ['SLIMS_USERNAME']}\n" - f"Password: {os.environ['SLIMS_PASSWORD']}" - ) - elif "No record found" not in str( - e - ): # bypass if mouse doesn't exist - raise Exception(f"Exception trying to read from Slims: {e}.\n") - logging.info("Successfully connected to Slims") - - def _AddWaterLogResult(self, session: Session): - """ - Add WaterLogResult to slims based on current state of gui - - :param session: Session object to pull water information from - - """ - - try: # try and find mouse - logging.info( - f"Attempting to fetch mouse {session.subject_id} from Slims" - ) - mouse = self.slims_client.fetch_model( - models.SlimsMouseContent, barcode=session.subject_id - ) - except Exception as e: - if "No record found" in str( - e - ): # if no mouse found or validation errors on mouse - logging.warning( - f'"No record found" error while trying to fetch mouse {session.subject_id}. ' - f"Will not log water." - ) - return - else: - logging.error( - f"While fetching mouse {session.subject_id} model, unexpected error occurred: {e}" - ) - raise e + def _AddWaterlogResult(self, session: Session): + """Send weight/water information to databases via waterlog app cli""" # extract water information logging.info("Extracting water information from first stimulus epoch") @@ -2171,53 +2105,63 @@ def _AddWaterLogResult(self, session: Session): for k, v in water_json } - # extract software information + # extract software information - TODO: Add this to waterlog cli logging.info("Extracting software information from first data stream") software = session.stimulus_epochs[0].software[0] - # create model - logging.info( - "Creating SlimsWaterlogResult based on session information." - ) - print(water) - model = models.SlimsWaterlogResult( - mouse_pk=mouse.pk, - date=datetime.now(), - weight_g=session.animal_weight_post, - operator=self.behavior_session_model.experimenter[0], - water_earned_ml=water["water_in_session_total"], - water_supplement_delivered_ml=water["water_after_session"], - water_supplement_recommended_ml=None, - total_water_ml=water["water_in_session_total"]+water["water_after_session"], - comments=session.notes, - workstation=session.rig_id, - sw_source=software.url, - sw_version=software.version, - test_pk=self.slims_client.fetch_pk( - "Test", test_name="test_waterlog" - ), - ) + # TODO: validate user first + # experimenter_name = self.behavior_session_model.experimenter[0] + # try: + # resp = requests.get("http://aind-metadata-service/api/v2/active_directory/{experimenter_name}") + # resp.raise_for_status() + # validated_username = resp.json()['username'] + # except Exception: + # logging.warning( + # "Could not validate experimenter name against aind-metadata-service", + # exc_info=True + # ) + # validated_username = experimenter_name + + # TODO: Should we remove the suggested water calculation from DF? + # That would mean removing the last two arguments below + + waterlog_args = [ + self.Settings['waterlog_exe_path'], + '--username', + self.behavior_session_model.experimenter[0], + '--mouse-id', + session.subject_id, + '--mouse-weight', + session.animal_weight_post, + '--comment', + session.notes, + '--earned-water', + water["water_in_session_total"], + '--water-supplement-ml', + water["water_after_session"], + '--water-supplement-delivered', + ] - # check if mouse already has waterlog for at session time and if, so update model - logging.info( - f"Fetching previous waterlog for mouse {session.subject_id}" - ) - waterlog = self.slims_client.fetch_models( - models.SlimsWaterlogResult, mouse_pk=mouse.pk, start=0, end=1 - ) - if waterlog != [] and waterlog[0].date.strftime( - "%Y-%m-%d %H:%M:%S" - ) == session.session_start_time.astimezone(timezone.utc).strftime( - "%Y-%m-%d %H:%M:%S" - ): - logging.info( - "Waterlog information already exists for this session. Updating waterlog in Slims." - ) - model.pk = waterlog[0].pk - self.slims_client.update_model(model=model) - else: - logging.info("Adding waterlog to Slims.") - self.slims_client.add_model(model) + ### Leftover fields that were previously sent to SLIMS + ### TODO: validate that it's okay to leave them out + # total_water_ml=water["water_in_session_total"]+water["water_after_session"], + # workstation=session.rig_id, + # sw_source=software.url, + # sw_version=software.version, + + logging.info("Sending water info to waterlog") + process = subprocess.run([str(arg) for arg in waterlog_args]) + + # TODO: Add message to user to to over to waterlog and hit submit + + try: + process.check_returncode() + except Exception: + logging.warning( + f"Waterlog data for mouse {self.behavior_session_model.subject} cannot be sent to waterlog exe" + f", message: {process.stdout}, {process.stderr}", + exc_info=True, + ) def _InitializeBonsai(self): """ @@ -4171,7 +4115,7 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): # save random reward parameters Obj['random_reward_par']=self.RandomReward_dialog.random_reward_par - # generate the metadata file and update slims + # generate the metadata file and update waterlog try: # save the metadata collected in the metadata dialogue self.Metadata_dialog._save_metadata_dialog_parameters() @@ -4220,10 +4164,10 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): ): self._AddWaterLogResult(session) elif self.BaseWeight.text() == "" or self.WeightAfter.text() == "": - logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to slims" + logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to database" f" due do unrecorded weight information.") elif session is None: - logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to slims" + logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to database" f" due do metadata generation failure.") except Exception as e: logging.warning( From 2823f446455fbaedf5a580b9de0ef40f6a532581 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Mon, 11 May 2026 10:03:44 -0700 Subject: [PATCH 02/14] Clean up waterlog adapter communication --- src/foraging_gui/Foraging.py | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 3c1652229..e495c047c 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -2105,25 +2105,10 @@ def _AddWaterlogResult(self, session: Session): for k, v in water_json } - # extract software information - TODO: Add this to waterlog cli + # extract software information to send to waterlog once it can accept it logging.info("Extracting software information from first data stream") software = session.stimulus_epochs[0].software[0] - - # TODO: validate user first - # experimenter_name = self.behavior_session_model.experimenter[0] - # try: - # resp = requests.get("http://aind-metadata-service/api/v2/active_directory/{experimenter_name}") - # resp.raise_for_status() - # validated_username = resp.json()['username'] - # except Exception: - # logging.warning( - # "Could not validate experimenter name against aind-metadata-service", - # exc_info=True - # ) - # validated_username = experimenter_name - - # TODO: Should we remove the suggested water calculation from DF? - # That would mean removing the last two arguments below + # Access sw name/version with (software.url, software.version) waterlog_args = [ self.Settings['waterlog_exe_path'], @@ -2142,17 +2127,10 @@ def _AddWaterlogResult(self, session: Session): '--water-supplement-delivered', ] - ### Leftover fields that were previously sent to SLIMS - ### TODO: validate that it's okay to leave them out - # total_water_ml=water["water_in_session_total"]+water["water_after_session"], - # workstation=session.rig_id, - # sw_source=software.url, - # sw_version=software.version, - logging.info("Sending water info to waterlog") process = subprocess.run([str(arg) for arg in waterlog_args]) - - # TODO: Add message to user to to over to waterlog and hit submit + + QMessageBox.information(self, "Waterlog", "Go to waterlog app to submit water information.") try: process.check_returncode() From 0e04b51ff435f007aaff0aac0990fde8ab4ca5d7 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Fri, 22 May 2026 09:34:34 -0700 Subject: [PATCH 03/14] updates function name --- src/foraging_gui/Foraging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index e495c047c..9abfb2167 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -4140,7 +4140,7 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): ] and session is not None ): - self._AddWaterLogResult(session) + self._AddWaterlogResult(session) elif self.BaseWeight.text() == "" or self.WeightAfter.text() == "": logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to database" f" due do unrecorded weight information.") From 3013e4048c89fdb015d49ff5fd57e2e53662aa85 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:03:51 -0700 Subject: [PATCH 04/14] adds lifecycle logger to lof start, complete, and failure logs in aind-log-utils format for grafana alloy to pick up --- pyproject.toml | 1 + src/foraging_gui/Foraging.py | 40 +++++++++++++++++++++++++++++- src/foraging_gui/settings_model.py | 3 ++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f034a5e98..6588d4364 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ dependencies = [ "winkerberos; sys_platform == 'win32'", "ldap3; sys_platform == 'win32'", "msal; sys_platform == 'win32'", + "aind-log-utils @ git+https://github.com/AllenNeuralDynamics/aind-log-utils.git@main#subdirectory=python" ] [project.optional-dependencies] diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index c9baca1bf..e90e0c04f 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -28,6 +28,8 @@ import requests import serial import yaml + +import aind_log_utils from aind_auto_train.schema.task import TrainingStage from aind_behavior_services.session import AindBehaviorSessionModel from aind_data_schema.core.session import Session @@ -351,6 +353,9 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): # load the rig metadata self._load_rig_metadata() + + # setup life-cycle logger + self.lifecycle_logger = self.setup_lifecycle_logger() # Initializes session log handler as None self.session_log_handler = None @@ -365,6 +370,20 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): self._ReconnectBonsai() logging.info("Start up complete") + def setup_lifecycle_logger(self): + + # Ensure the directory exists + os.makedirs(os.path.dirname(self.SettingsBox), exist_ok=True) + + lifecycle_logger = logging.getLogger("lifecycle") + lifecycle_logger.setLevel(logging.INFO) + file_handler = logging.FileHandler(self.Settings["lifecycle_log_dir"], encoding="utf-8") + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(aind_log_utils.DefaultFormatter()) + lifecycle_logger.addHandler(file_handler) + + return lifecycle_logger + def _load_rig_metadata(self): """Load the latest rig metadata""" @@ -1961,6 +1980,11 @@ def _GetSettings(self): "aind_watchdog_service", "manifest", ), + "lifecycle_log_dir": os.path.join( + os.path.expanduser("~"), + "Documents", + "lifecycle_logs", + ), "transfer_service_job_type": "dynamic_foraging_compression", "auto_engage": True, "clear_figure_after_save": True, @@ -4225,6 +4249,12 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): elif session is None: logging.warning(f"Waterlog for mouse {self.behavior_session_model.subject} cannot be added to slims" f" due do metadata generation failure.") + + # add complete log to lifecycle + self.lifecycle_logger.info("Session ended.", extra={"subject_id": self.behavior_session_model.subject, + "acquisition_name": self.behavior_session_model.session_name, + "event_type": "stage_complete"}) + except Exception as e: logging.warning( "Meta data is not saved!", @@ -6124,6 +6154,12 @@ def _Start(self): elif self.behavior_session_model.allow_dirty_repo is None: logging.error("Could not check for untracked local changes") + # log start event + if self.StartANewSession != 0: + self.lifecycle_logger.info("Session started.", extra={"subject_id": self.behavior_session_model.subject, + "acquisition_name": self.behavior_session_model.session_name, + "event_type": "stage_start"}) + # disable sound button self.sound_button.setEnabled(False) @@ -6641,6 +6677,9 @@ def _StartTrialLoop(self, GeneratedTrials, worker1, worker_save): self.ANewTrial = 1 self.Start.setChecked(False) self.Start.setStyleSheet("background-color : none") + self.lifecycle_logger.info("Session failed.", extra={"subject_id": self.behavior_session_model.subject, + "acquisition_name": self.behavior_session_model.session_name, + "event_type": "stage_failure"}) break # receive licks and update figures if self.actionDrawing_after_stopping.isChecked() == False: @@ -7432,7 +7471,6 @@ def setup_loki_logging(box_number): handler.setLevel(logging.INFO) logger.root.addHandler(handler) - def start_gui_log_file(box_number): """ Starts a log file for the gui. diff --git a/src/foraging_gui/settings_model.py b/src/foraging_gui/settings_model.py index a2f2edfc0..77dac0bfd 100644 --- a/src/foraging_gui/settings_model.py +++ b/src/foraging_gui/settings_model.py @@ -1,7 +1,7 @@ from typing import Literal, Optional from pydantic import BaseModel, Field - +from pathlib import Path class BonsaiSettingsModel(BaseModel): """ @@ -182,6 +182,7 @@ class DFTSettingsModel(BaseModel): save_each_trial: bool AutomaticUpload: bool manifest_flag_dir: str + lifecycle_log_dir: Path transfer_service_job_type: str auto_engage: bool clear_figure_after_save: bool From d4f6c892f504cfb98f177f793afce86add730d1e Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:23:03 -0700 Subject: [PATCH 05/14] adds unique filename --- src/foraging_gui/Foraging.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index e90e0c04f..ccae40de2 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -377,7 +377,10 @@ def setup_lifecycle_logger(self): lifecycle_logger = logging.getLogger("lifecycle") lifecycle_logger.setLevel(logging.INFO) - file_handler = logging.FileHandler(self.Settings["lifecycle_log_dir"], encoding="utf-8") + + timestamp = datetime.now().strftime("%Y%m%dT%H%M%SZ") + filename = f"lifecycle_log_{timestamp}.jsonl" + file_handler = logging.FileHandler(os.path.join(self.Settings["lifecycle_log_dir"], filename), encoding="utf-8") file_handler.setLevel(logging.INFO) file_handler.setFormatter(aind_log_utils.DefaultFormatter()) lifecycle_logger.addHandler(file_handler) From c0b310de7487f72b787b139411847acc9653376d Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:28:08 -0700 Subject: [PATCH 06/14] pin pypi version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6588d4364..7690fd47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ dependencies = [ "winkerberos; sys_platform == 'win32'", "ldap3; sys_platform == 'win32'", "msal; sys_platform == 'win32'", - "aind-log-utils @ git+https://github.com/AllenNeuralDynamics/aind-log-utils.git@main#subdirectory=python" + "aind-log-utils==0.2.8" ] [project.optional-dependencies] From 6042321910dff1b5e0a88b2be701d10ef5951d5e Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:30:20 -0700 Subject: [PATCH 07/14] fixes mkdir bug --- src/foraging_gui/Foraging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index ccae40de2..351ac4de1 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -373,7 +373,7 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): def setup_lifecycle_logger(self): # Ensure the directory exists - os.makedirs(os.path.dirname(self.SettingsBox), exist_ok=True) + os.makedirs(os.path.dirname(self.SettingsBox["lifecycle_log_dir"]), exist_ok=True) lifecycle_logger = logging.getLogger("lifecycle") lifecycle_logger.setLevel(logging.INFO) From 4ad31acd3e0fef5cffde93d376966a302d2c1786 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:32:35 -0700 Subject: [PATCH 08/14] fixes settings bug --- src/foraging_gui/Foraging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 351ac4de1..325fd36cd 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -373,7 +373,7 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): def setup_lifecycle_logger(self): # Ensure the directory exists - os.makedirs(os.path.dirname(self.SettingsBox["lifecycle_log_dir"]), exist_ok=True) + os.makedirs(os.path.dirname(self.Settings["lifecycle_log_dir"]), exist_ok=True) lifecycle_logger = logging.getLogger("lifecycle") lifecycle_logger.setLevel(logging.INFO) From 9fa8a1acf73035c8247df13f9025474ab553f1d8 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:34:54 -0700 Subject: [PATCH 09/14] fixes settings bug --- src/foraging_gui/Foraging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 325fd36cd..d11fabf0a 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -373,7 +373,7 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): def setup_lifecycle_logger(self): # Ensure the directory exists - os.makedirs(os.path.dirname(self.Settings["lifecycle_log_dir"]), exist_ok=True) + os.makedirs(os.path(self.Settings["lifecycle_log_dir"]), exist_ok=True) lifecycle_logger = logging.getLogger("lifecycle") lifecycle_logger.setLevel(logging.INFO) From 0493014804dcbdb023723b92aa550b25062975ef Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Tue, 26 May 2026 10:36:26 -0700 Subject: [PATCH 10/14] fixes bug in makeirs --- src/foraging_gui/Foraging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index d11fabf0a..cd6216985 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -373,7 +373,7 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): def setup_lifecycle_logger(self): # Ensure the directory exists - os.makedirs(os.path(self.Settings["lifecycle_log_dir"]), exist_ok=True) + os.makedirs(Path(self.Settings["lifecycle_log_dir"]), exist_ok=True) lifecycle_logger = logging.getLogger("lifecycle") lifecycle_logger.setLevel(logging.INFO) From ccd7c7e50d41bf158cc1f8a66a4f7cda35ea15f4 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 08:00:22 -0700 Subject: [PATCH 11/14] pins log-schema --- pyproject.toml | 2 +- src/foraging_gui/Foraging.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7690fd47a..ad36f2e37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ dependencies = [ "winkerberos; sys_platform == 'win32'", "ldap3; sys_platform == 'win32'", "msal; sys_platform == 'win32'", - "aind-log-utils==0.2.8" + "log-schema==0.2.10.dev1" ] [project.optional-dependencies] diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index cd6216985..3761f1d50 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -29,7 +29,7 @@ import serial import yaml -import aind_log_utils +import log_schema from aind_auto_train.schema.task import TrainingStage from aind_behavior_services.session import AindBehaviorSessionModel from aind_data_schema.core.session import Session @@ -382,7 +382,7 @@ def setup_lifecycle_logger(self): filename = f"lifecycle_log_{timestamp}.jsonl" file_handler = logging.FileHandler(os.path.join(self.Settings["lifecycle_log_dir"], filename), encoding="utf-8") file_handler.setLevel(logging.INFO) - file_handler.setFormatter(aind_log_utils.DefaultFormatter()) + file_handler.setFormatter(log_schema.DefaultFormatter()) lifecycle_logger.addHandler(file_handler) return lifecycle_logger From dd89052e9318a4e875a1096de218dc2fb83f30d9 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 08:08:04 -0700 Subject: [PATCH 12/14] moves start log after session name has been created --- src/foraging_gui/Foraging.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 3761f1d50..64ad5f096 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -3964,7 +3964,11 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): if self.CreateNewFolder == 1: self._GetSaveFolder() self.CreateNewFolder = 0 - + # Need to log start event after session_name has been set in _GetSaveFolder. This will only happen once at start of session. + self.lifecycle_logger.info("Session started.", extra={"subject_id": self.behavior_session_model.subject, + "acquisition_name": self.behavior_session_model.session_name, + "event_type": "stage_start"}) + if not os.path.exists(os.path.dirname(self.SaveFileJson)): os.makedirs(os.path.dirname(self.SaveFileJson)) logging.info( @@ -6157,12 +6161,6 @@ def _Start(self): elif self.behavior_session_model.allow_dirty_repo is None: logging.error("Could not check for untracked local changes") - # log start event - if self.StartANewSession != 0: - self.lifecycle_logger.info("Session started.", extra={"subject_id": self.behavior_session_model.subject, - "acquisition_name": self.behavior_session_model.session_name, - "event_type": "stage_start"}) - # disable sound button self.sound_button.setEnabled(False) From 16a13a0f6736f16fe27b50993d71294b907a4131 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 08:12:11 -0700 Subject: [PATCH 13/14] moves start log after restart_logging --- src/foraging_gui/Foraging.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 64ad5f096..2b4c32a73 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -1616,6 +1616,7 @@ def _restartlogging(self, log_folder=None,start_from_camera=False): self.Save.setStyleSheet( "color: white;background-color : mediumorchid" ) + else: # temporary logging loggingtype = 1 @@ -3964,10 +3965,6 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0): if self.CreateNewFolder == 1: self._GetSaveFolder() self.CreateNewFolder = 0 - # Need to log start event after session_name has been set in _GetSaveFolder. This will only happen once at start of session. - self.lifecycle_logger.info("Session started.", extra={"subject_id": self.behavior_session_model.subject, - "acquisition_name": self.behavior_session_model.session_name, - "event_type": "stage_start"}) if not os.path.exists(os.path.dirname(self.SaveFileJson)): os.makedirs(os.path.dirname(self.SaveFileJson)) @@ -6223,6 +6220,10 @@ def _Start(self): # Start logging if the formal logging is not started if self.logging_type != 0: self.Ot_log_folder = self._restartlogging() + # Need to log start event after session_name has been set in_restartlogging + self.lifecycle_logger.info("Session started.", extra={"subject_id": self.behavior_session_model.subject, + "acquisition_name": self.behavior_session_model.session_name, + "event_type": "stage_start"}) except Exception as e: if "ConnectionAbortedError" in str(e): logging.info("lost bonsai connection: restartlogging()") From 24be3cc62eab2be9bbd6fbdb254958b36b77dd27 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 08:19:35 -0700 Subject: [PATCH 14/14] adds doc string and typing --- src/foraging_gui/Foraging.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py index 2b4c32a73..3940347e0 100644 --- a/src/foraging_gui/Foraging.py +++ b/src/foraging_gui/Foraging.py @@ -370,8 +370,12 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True): self._ReconnectBonsai() logging.info("Start up complete") - def setup_lifecycle_logger(self): + def setup_lifecycle_logger(self) -> logging.Logger: + """ + Creates logger for start, stop, and failure events with formatter adhering to aind log standards. + """ + # Ensure the directory exists os.makedirs(Path(self.Settings["lifecycle_log_dir"]), exist_ok=True)