2828import requests
2929import serial
3030import yaml
31+
32+ import log_schema
3133from aind_auto_train .schema .task import TrainingStage
3234from aind_behavior_services .session import AindBehaviorSessionModel
3335from aind_data_schema .core .session import Session
34- from aind_slims_api import SlimsClient , models
3536from matplotlib .backends .backend_qt5agg import (
3637 NavigationToolbar2QT as NavigationToolbar ,
3738)
@@ -238,9 +239,6 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True):
238239 # Connect to Bonsai
239240 self ._InitializeBonsai ()
240241
241- # connect to Slims
242- self ._ConnectSlims ()
243-
244242 # Set up threads
245243 self .threadpool = QThreadPool () # get animal response
246244 self .threadpool2 = QThreadPool () # get animal lick
@@ -351,6 +349,9 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True):
351349
352350 # load the rig metadata
353351 self ._load_rig_metadata ()
352+
353+ # setup life-cycle logger
354+ self .lifecycle_logger = self .setup_lifecycle_logger ()
354355
355356 # Initializes session log handler as None
356357 self .session_log_handler = None
@@ -365,6 +366,27 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True):
365366 self ._ReconnectBonsai ()
366367 logging .info ("Start up complete" )
367368
369+ def setup_lifecycle_logger (self ) -> logging .Logger :
370+
371+ """
372+ Creates logger for start, stop, and failure events with formatter adhering to aind log standards.
373+ """
374+
375+ # Ensure the directory exists
376+ os .makedirs (Path (self .Settings ["lifecycle_log_dir" ]), exist_ok = True )
377+
378+ lifecycle_logger = logging .getLogger ("lifecycle" )
379+ lifecycle_logger .setLevel (logging .INFO )
380+
381+ timestamp = datetime .now ().strftime ("%Y%m%dT%H%M%SZ" )
382+ filename = f"lifecycle_log_{ timestamp } .jsonl"
383+ file_handler = logging .FileHandler (os .path .join (self .Settings ["lifecycle_log_dir" ], filename ), encoding = "utf-8" )
384+ file_handler .setLevel (logging .INFO )
385+ file_handler .setFormatter (log_schema .DefaultFormatter ())
386+ lifecycle_logger .addHandler (file_handler )
387+
388+ return lifecycle_logger
389+
368390 def _load_rig_metadata (self ):
369391 """Load the latest rig metadata"""
370392
@@ -1594,6 +1616,7 @@ def _restartlogging(self, log_folder=None,start_from_camera=False):
15941616 self .Save .setStyleSheet (
15951617 "color: white;background-color : mediumorchid"
15961618 )
1619+
15971620 else :
15981621 # temporary logging
15991622 loggingtype = 1
@@ -1961,11 +1984,17 @@ def _GetSettings(self):
19611984 "aind_watchdog_service" ,
19621985 "manifest" ,
19631986 ),
1987+ "lifecycle_log_dir" : os .path .join (
1988+ os .path .expanduser ("~" ),
1989+ "Documents" ,
1990+ "lifecycle_logs" ,
1991+ ),
19641992 "transfer_service_job_type" : "dynamic_foraging_compression" ,
19651993 "auto_engage" : True ,
19661994 "clear_figure_after_save" : True ,
19671995 "add_default_project_name" : True ,
19681996 "check_schedule" : False ,
1997+ "waterlog_exe_path" : "C://Program Files/AIBS_MPE/waterlog/waterlog.exe" ,
19691998 }
19701999
19712000 # Try to load the ForagingSettings.json file
@@ -2097,71 +2126,8 @@ def _GetSettings(self):
20972126 ]
20982127 self .rig_name = "{}" .format (self .current_box )
20992128
2100- def _ConnectSlims (self ):
2101- """
2102- Connect to Slims
2103- """
2104- try :
2105- logging .info ("Attempting to connect to Slims" )
2106- self .slims_client = SlimsClient (
2107- username = os .environ ["SLIMS_USERNAME" ],
2108- password = os .environ ["SLIMS_PASSWORD" ],
2109- )
2110- except KeyError as e :
2111- raise KeyError (
2112- "SLIMS_USERNAME and SLIMS_PASSWORD do not exist as "
2113- f"environment variables on machine. Please add. { e } "
2114- )
2115-
2116- try :
2117- self .slims_client .fetch_model (
2118- models .SlimsMouseContent , barcode = "00000000"
2119- )
2120- except Exception as e :
2121- if "Status 401 – Unauthorized" in str (
2122- e
2123- ): # catch error if username and password are incorrect
2124- raise Exception (
2125- f"Exception trying to read from Slims: { e } .\n "
2126- f" Please check credentials:\n "
2127- f"Username: { os .environ ['SLIMS_USERNAME' ]} \n "
2128- f"Password: { os .environ ['SLIMS_PASSWORD' ]} "
2129- )
2130- elif "No record found" not in str (
2131- e
2132- ): # bypass if mouse doesn't exist
2133- raise Exception (f"Exception trying to read from Slims: { e } .\n " )
2134- logging .info ("Successfully connected to Slims" )
2135-
2136- def _AddWaterLogResult (self , session : Session ):
2137- """
2138- Add WaterLogResult to slims based on current state of gui
2139-
2140- :param session: Session object to pull water information from
2141-
2142- """
2143-
2144- try : # try and find mouse
2145- logging .info (
2146- f"Attempting to fetch mouse { session .subject_id } from Slims"
2147- )
2148- mouse = self .slims_client .fetch_model (
2149- models .SlimsMouseContent , barcode = session .subject_id
2150- )
2151- except Exception as e :
2152- if "No record found" in str (
2153- e
2154- ): # if no mouse found or validation errors on mouse
2155- logging .warning (
2156- f'"No record found" error while trying to fetch mouse { session .subject_id } . '
2157- f"Will not log water."
2158- )
2159- return
2160- else :
2161- logging .error (
2162- f"While fetching mouse { session .subject_id } model, unexpected error occurred: { e } "
2163- )
2164- raise e
2129+ def _AddWaterlogResult (self , session : Session ):
2130+ """Send weight/water information to databases via waterlog app cli"""
21652131
21662132 # extract water information
21672133 logging .info ("Extracting water information from first stimulus epoch" )
@@ -2171,53 +2137,41 @@ def _AddWaterLogResult(self, session: Session):
21712137 for k , v in water_json
21722138 }
21732139
2174- # extract software information
2140+ # extract software information to send to waterlog once it can accept it
21752141 logging .info ("Extracting software information from first data stream" )
21762142 software = session .stimulus_epochs [0 ].software [0 ]
2143+ # Access sw name/version with (software.url, software.version)
2144+
2145+ waterlog_args = [
2146+ self .Settings ['waterlog_exe_path' ],
2147+ '--username' ,
2148+ self .behavior_session_model .experimenter [0 ],
2149+ '--mouse-id' ,
2150+ session .subject_id ,
2151+ '--mouse-weight' ,
2152+ session .animal_weight_post ,
2153+ '--comment' ,
2154+ session .notes ,
2155+ '--earned-water' ,
2156+ water ["water_in_session_total" ],
2157+ '--water-supplement-ml' ,
2158+ water ["water_after_session" ],
2159+ '--water-supplement-delivered' ,
2160+ ]
21772161
2178- # create model
2179- logging .info (
2180- "Creating SlimsWaterlogResult based on session information."
2181- )
2182- print (water )
2183- model = models .SlimsWaterlogResult (
2184- mouse_pk = mouse .pk ,
2185- date = datetime .now (),
2186- weight_g = session .animal_weight_post ,
2187- operator = self .behavior_session_model .experimenter [0 ],
2188- water_earned_ml = water ["water_in_session_total" ],
2189- water_supplement_delivered_ml = water ["water_after_session" ],
2190- water_supplement_recommended_ml = None ,
2191- total_water_ml = water ["water_in_session_total" ]+ water ["water_after_session" ],
2192- comments = session .notes ,
2193- workstation = session .rig_id ,
2194- sw_source = software .url ,
2195- sw_version = software .version ,
2196- test_pk = self .slims_client .fetch_pk (
2197- "Test" , test_name = "test_waterlog"
2198- ),
2199- )
2162+ logging .info ("Sending water info to waterlog" )
2163+ process = subprocess .run ([str (arg ) for arg in waterlog_args ])
22002164
2201- # check if mouse already has waterlog for at session time and if, so update model
2202- logging .info (
2203- f"Fetching previous waterlog for mouse { session .subject_id } "
2204- )
2205- waterlog = self .slims_client .fetch_models (
2206- models .SlimsWaterlogResult , mouse_pk = mouse .pk , start = 0 , end = 1
2207- )
2208- if waterlog != [] and waterlog [0 ].date .strftime (
2209- "%Y-%m-%d %H:%M:%S"
2210- ) == session .session_start_time .astimezone (timezone .utc ).strftime (
2211- "%Y-%m-%d %H:%M:%S"
2212- ):
2213- logging .info (
2214- "Waterlog information already exists for this session. Updating waterlog in Slims."
2215- )
2216- model .pk = waterlog [0 ].pk
2217- self .slims_client .update_model (model = model )
2218- else :
2219- logging .info ("Adding waterlog to Slims." )
2220- self .slims_client .add_model (model )
2165+ QMessageBox .information (self , "Waterlog" , "Go to waterlog app to submit water information." )
2166+
2167+ try :
2168+ process .check_returncode ()
2169+ except Exception :
2170+ logging .warning (
2171+ f"Waterlog data for mouse { self .behavior_session_model .subject } cannot be sent to waterlog exe"
2172+ f", message: { process .stdout } , { process .stderr } " ,
2173+ exc_info = True ,
2174+ )
22212175
22222176 def _InitializeBonsai (self ):
22232177 """
@@ -3937,7 +3891,7 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
39373891 if self .CreateNewFolder == 1 :
39383892 self ._GetSaveFolder ()
39393893 self .CreateNewFolder = 0
3940-
3894+
39413895 if not os .path .exists (os .path .dirname (self .SaveFileJson )):
39423896 os .makedirs (os .path .dirname (self .SaveFileJson ))
39433897 logging .info (
@@ -4171,7 +4125,7 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
41714125 # save random reward parameters
41724126 Obj ['random_reward_par' ]= self .RandomReward_dialog .random_reward_par
41734127
4174- # generate the metadata file and update slims
4128+ # generate the metadata file and update waterlog
41754129 try :
41764130 # save the metadata collected in the metadata dialogue
41774131 self .Metadata_dialog ._save_metadata_dialog_parameters ()
@@ -4218,13 +4172,19 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
42184172 ]
42194173 and session is not None
42204174 ):
4221- self ._AddWaterLogResult (session )
4175+ self ._AddWaterlogResult (session )
42224176 elif self .BaseWeight .text () == "" or self .WeightAfter .text () == "" :
4223- logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims "
4177+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to database "
42244178 f" due do unrecorded weight information." )
42254179 elif session is None :
4226- logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims "
4180+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to database "
42274181 f" due do metadata generation failure." )
4182+
4183+ # add complete log to lifecycle
4184+ self .lifecycle_logger .info ("Session ended." , extra = {"subject_id" : self .behavior_session_model .subject ,
4185+ "acquisition_name" : self .behavior_session_model .session_name ,
4186+ "event_type" : "stage_complete" })
4187+
42284188 except Exception as e :
42294189 logging .warning (
42304190 "Meta data is not saved!" ,
@@ -6186,6 +6146,10 @@ def _Start(self):
61866146 # Start logging if the formal logging is not started
61876147 if self .logging_type != 0 :
61886148 self .Ot_log_folder = self ._restartlogging ()
6149+ # Need to log start event after session_name has been set in_restartlogging
6150+ self .lifecycle_logger .info ("Session started." , extra = {"subject_id" : self .behavior_session_model .subject ,
6151+ "acquisition_name" : self .behavior_session_model .session_name ,
6152+ "event_type" : "stage_start" })
61896153 except Exception as e :
61906154 if "ConnectionAbortedError" in str (e ):
61916155 logging .info ("lost bonsai connection: restartlogging()" )
@@ -6641,6 +6605,9 @@ def _StartTrialLoop(self, GeneratedTrials, worker1, worker_save):
66416605 self .ANewTrial = 1
66426606 self .Start .setChecked (False )
66436607 self .Start .setStyleSheet ("background-color : none" )
6608+ self .lifecycle_logger .info ("Session failed." , extra = {"subject_id" : self .behavior_session_model .subject ,
6609+ "acquisition_name" : self .behavior_session_model .session_name ,
6610+ "event_type" : "stage_failure" })
66446611 break
66456612 # receive licks and update figures
66466613 if self .actionDrawing_after_stopping .isChecked () == False :
@@ -7432,7 +7399,6 @@ def setup_loki_logging(box_number):
74327399 handler .setLevel (logging .INFO )
74337400 logger .root .addHandler (handler )
74347401
7435-
74367402def start_gui_log_file (box_number ):
74377403 """
74387404 Starts a log file for the gui.
0 commit comments