From 9ce33b2179998707513876a665b217d396a85e5d Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 17 Oct 2024 09:54:50 -0700 Subject: [PATCH 1/7] octopi init --- software/octopi/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 software/octopi/__init__.py diff --git a/software/octopi/__init__.py b/software/octopi/__init__.py new file mode 100644 index 000000000..e69de29bb From 8ca0f74bddb656c48128624c9918865e23a0f7b5 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 17 Oct 2024 09:56:05 -0700 Subject: [PATCH 2/7] logging: add octopi logging hierarchy, and helper for top level exception logging --- software/octopi/logging.py | 85 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 software/octopi/logging.py diff --git a/software/octopi/logging.py b/software/octopi/logging.py new file mode 100644 index 000000000..9bfd79f53 --- /dev/null +++ b/software/octopi/logging.py @@ -0,0 +1,85 @@ +import logging +import threading +from typing import Optional, Type +from types import TracebackType +import sys + +_octopi_root_logger_name="octopi" + + +def get_logger(name: Optional[str] = None) -> logging.Logger: + """ + Returns the top level octopi logger instance by default, or a logger in the octopi + logging hierarchy if a non-None name is given. + """ + if name is None: + return logging.getLogger(_octopi_root_logger_name) + else: + return logging.getLogger(_octopi_root_logger_name).getChild(name) + + +def set_log_level(level): + """ + All octopi-research code should use this set_log_level method, and the corresponding octopi.logging.get_logger, + to control octopi-research-only logging. + + This does not modify the log level of loggers outside the octopi logger hierarchy! If global logging control + is needed the normal logging package tools can be used instead. + """ + octopi_root_logger = get_logger() + octopi_root_logger.setLevel(level) + + # There's no `getAllChildren` method on the logger or its manager, so we just grab the manager + # for our root logger and then check all other loggers to see if they start with our root logger prefix + # to find all the octopi specific logger. + for (name, logger) in octopi_root_logger.manager.loggerDict.items(): + if name.startswith(_octopi_root_logger_name): + logger.setLevel(level) + +def register_crash_handler(handler): + """ + We want to make sure any uncaught exceptions are logged, so we have this mechanism for putting a hook into + the python system that does custom logging when an exception bubbles all the way to the top. + + NOTE: We do our best below, but it is a really bad idea for your handler to raise an exception. + """ + # The sys.excepthook docs are a good entry point for all of this + # (here: https://docs.python.org/3/library/sys.html#sys.excepthook), but essentially there are 3 different ways + # threads of execution can blow up. We want to catch and log all 3 of them. + old_excepthook = sys.excepthook + old_thread_excepthook = threading.excepthook + # The unraisable hook doesn't have the same signature as the excepthooks, but we can sort of shoehorn the arguments + # into the same signature. Also, this is an extremely rare (I'm not sure I've ever seen it?) failure mode, so + # it should be okay. + old_unraisable_hook = sys.unraisablehook + + logger = get_logger() + + def new_excepthook(exception_type: Type[BaseException], value: BaseException, tb: TracebackType): + try: + handler(exception_type, value, tb) + except BaseException as e: + logger.critical("Custom excepthook handler raised exception", e) + old_excepthook(exception_type, value, tb) + + def new_thread_excepthook(exception_type: Type[BaseException], value: BaseException, tb: TracebackType): + try: + handler(exception_type, value, tb) + except BaseException as e: + logger.critical("Custom thread excepthook handler raised exception", e) + old_thread_excepthook(exception_type, value, tb) + + def new_unraisable_hook(info): + exception_type = info["exception_type"] + tb = info["exception_traceback"] + value = info["exception_value"] + try: + handler(exception_type, value, tb) + except BaseException as e: + logger.critical("Custom unraisable hook handler raised exception", e) + old_unraisable_hook(info) + + logger.info(f"Registering custom excepthook, threading excepthook, and unraisable hook using handler={handler.__name__}") + sys.excepthook = new_excepthook + threading.excepthook = new_thread_excepthook + sys.unraisablehook = new_unraisable_hook \ No newline at end of file From b288f10e1ae4b09891d1651ef9459029d4edeff9 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 17 Oct 2024 10:04:53 -0700 Subject: [PATCH 3/7] main_hcs: remove unused imports, sort imports, move arg parsing inside top level check --- software/main_hcs.py | 28 +++++++++++++--------------- software/octopi/logging.py | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/software/main_hcs.py b/software/main_hcs.py index 2264803b8..5b0f9f25c 100644 --- a/software/main_hcs.py +++ b/software/main_hcs.py @@ -1,48 +1,46 @@ # set QT_API environment variable -import os -import glob import argparse +import glob +import os os.environ["QT_API"] = "pyqt5" -import qtpy - import sys # qt libraries -from qtpy.QtCore import * from qtpy.QtWidgets import * from qtpy.QtGui import * # app specific libraries import control.gui_hcs as gui - from configparser import ConfigParser from control.widgets import ConfigEditorBackwardsCompatible, ConfigEditorForAcquisitions - from control._def import CACHED_CONFIG_FILE_PATH - -import glob - -parser = argparse.ArgumentParser() -parser.add_argument("--simulation", help="Run the GUI with simulated hardware.", action = 'store_true') -parser.add_argument("--performance", help="Run the GUI with minimal viewers.", action = 'store_true') -args = parser.parse_args() +import octopi.logging def show_config(cfp, configpath, main_gui): config_widget = ConfigEditorBackwardsCompatible(cfp, configpath, main_gui) config_widget.exec_() + def show_acq_config(cfm): acq_config_widget = ConfigEditorForAcquisitions(cfm) acq_config_widget.exec_() + if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--simulation", help="Run the GUI with simulated hardware.", action='store_true') + parser.add_argument("--performance", help="Run the GUI with minimal viewers.", action='store_true') + args = parser.parse_args() + + logger = octopi.logging.get_logger("main_hcs") + legacy_config = False cf_editor_parser = ConfigParser() config_files = glob.glob('.' + '/' + 'configuration*.ini') if config_files: cf_editor_parser.read(CACHED_CONFIG_FILE_PATH) else: - print('configuration*.ini file not found, defaulting to legacy configuration') + logger.error('configuration*.ini file not found, defaulting to legacy configuration') legacy_config = True app = QApplication([]) app.setStyle('Fusion') diff --git a/software/octopi/logging.py b/software/octopi/logging.py index 9bfd79f53..518df1822 100644 --- a/software/octopi/logging.py +++ b/software/octopi/logging.py @@ -1,4 +1,4 @@ -import logging +import logging as py_logging import threading from typing import Optional, Type from types import TracebackType @@ -6,17 +6,44 @@ _octopi_root_logger_name="octopi" +# The idea for this CustomFormatter is cribbed from https://stackoverflow.com/a/56944256 +class _CustomFormatter(py_logging.Formatter): + GRAY = "\x1b[38;20m" + YELLOW = "\x1b[33;20m" + RED = "\x1b[31;20m" + BOLD_RED = "\x1b[31;1m" + RESET = "\x1b[0m" + FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" -def get_logger(name: Optional[str] = None) -> logging.Logger: + FORMATS = { + py_logging.DEBUG: GRAY + FORMAT + RESET, + py_logging.INFO: GRAY + FORMAT + RESET, + py_logging.WARNING: YELLOW + FORMAT + RESET, + py_logging.ERROR: RED + FORMAT + RESET, + py_logging.CRITICAL: BOLD_RED + FORMAT + RESET + } + + FORMATTERS = {level: py_logging.Formatter(fmt) for (level, fmt) in FORMATS.items()} + + def format(self, record): + return self.FORMATTERS[record.levelno].format(record) + +_COLOR_STREAM_HANDLER = py_logging.StreamHandler() +_COLOR_STREAM_HANDLER.setFormatter(_CustomFormatter()) + +def get_logger(name: Optional[str] = None) -> py_logging.Logger: """ Returns the top level octopi logger instance by default, or a logger in the octopi logging hierarchy if a non-None name is given. """ if name is None: - return logging.getLogger(_octopi_root_logger_name) + logger = py_logging.getLogger(_octopi_root_logger_name) else: - return logging.getLogger(_octopi_root_logger_name).getChild(name) + logger = py_logging.getLogger(_octopi_root_logger_name).getChild(name) + + logger.addHandler(_COLOR_STREAM_HANDLER) + return logger def set_log_level(level): """ From 792d59b99c82f052e9b4b9e5bdd688f72e71dbf1 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 17 Oct 2024 10:37:06 -0700 Subject: [PATCH 4/7] use _def.py as an example to convert to new logging style --- software/control/_def.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/software/control/_def.py b/software/control/_def.py index 5f2a4f4b0..68dbb8969 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -1,12 +1,16 @@ import os import sys import glob -import numpy as np from pathlib import Path from configparser import ConfigParser import json import csv +import octopi.logging + +log = octopi.logging.get_logger("_def") + + def conf_attribute_reader(string_value): """ :brief: standardized way for reading config entries @@ -180,13 +184,13 @@ class LIMIT_SWITCH_POLARITY: class ILLUMINATION_CODE: - ILLUMINATION_SOURCE_LED_ARRAY_FULL = 0; + ILLUMINATION_SOURCE_LED_ARRAY_FULL = 0 ILLUMINATION_SOURCE_LED_ARRAY_LEFT_HALF = 1 ILLUMINATION_SOURCE_LED_ARRAY_RIGHT_HALF = 2 ILLUMINATION_SOURCE_LED_ARRAY_LEFTB_RIGHTR = 3 - ILLUMINATION_SOURCE_LED_ARRAY_LOW_NA = 4; - ILLUMINATION_SOURCE_LED_ARRAY_LEFT_DOT = 5; - ILLUMINATION_SOURCE_LED_ARRAY_RIGHT_DOT = 6; + ILLUMINATION_SOURCE_LED_ARRAY_LOW_NA = 4 + ILLUMINATION_SOURCE_LED_ARRAY_LEFT_DOT = 5 + ILLUMINATION_SOURCE_LED_ARRAY_RIGHT_DOT = 6 ILLUMINATION_SOURCE_LED_EXTERNAL_FET = 20 ILLUMINATION_SOURCE_405NM = 11 ILLUMINATION_SOURCE_488NM = 12 @@ -493,8 +497,6 @@ class SOFTWARE_POS_LIMIT: DO_FLUORESCENCE_RTP = False -ENABLE_SPINNING_DISK_CONFOCAL = False - INVERTED_OBJECTIVE = False ILLUMINATION_INTENSITY_FACTOR = 0.6 @@ -598,7 +600,6 @@ def read_objectives_csv(file_path): 'NA': float(row['NA']), 'tube_lens_f_mm': float(row['tube_lens_f_mm']) } - #print(f"{row['name']}: {objectives[row['name']]}") return objectives def read_sample_formats_csv(file_path): @@ -618,7 +619,6 @@ def read_sample_formats_csv(file_path): 'rows': int(row['rows']), 'cols': int(row['cols']) } - #print(format_key, "well plate settings:", sample_formats[format_key]) return sample_formats OBJECTIVES_CSV_PATH = 'objectives.csv' @@ -668,12 +668,12 @@ def read_sample_formats_csv(file_path): if config_files: if len(config_files) > 1: if CACHED_CONFIG_FILE_PATH in config_files: - print('defaulting to last cached config file at '+CACHED_CONFIG_FILE_PATH) + log.info(f'defaulting to last cached config file at \'{CACHED_CONFIG_FILE_PATH}\'') config_files = [CACHED_CONFIG_FILE_PATH] else: - print('multiple machine configuration files found, the program will exit') + log.error('multiple machine configuration files found, the program will exit') sys.exit(1) - print('load machine-specific configuration') + log.info('load machine-specific configuration') #exec(open(config_files[0]).read()) cfp = ConfigParser() cfp.read(config_files[0]) @@ -704,16 +704,16 @@ def read_sample_formats_csv(file_path): file.write(config_files[0]) CACHED_CONFIG_FILE_PATH = config_files[0] else: - print('configuration*.ini file not found, defaulting to legacy configuration') + log.warning('configuration*.ini file not found, defaulting to legacy configuration') config_files = glob.glob('.' + '/' + 'configuration*.txt') if config_files: if len(config_files) > 1: - print('multiple machine configuration files found, the program will exit') + log.error('multiple machine configuration files found, the program will exit') sys.exit(1) - print('load machine-specific configuration') + log.info('load machine-specific configuration') exec(open(config_files[0]).read()) else: - print('machine-specific configuration not present, the program will exit') + log.error('machine-specific configuration not present, the program will exit') sys.exit(1) ########################################################## ##### end of loading machine specific configurations ##### From 0457951c828060a72cd312f688c201c9e81da675 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 17 Oct 2024 11:02:14 -0700 Subject: [PATCH 5/7] use excepthook handler, double log handler fix --- software/control/_def.py | 2 +- software/main_hcs.py | 10 ++++--- software/octopi/logging.py | 61 ++++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/software/control/_def.py b/software/control/_def.py index 68dbb8969..676f72b70 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -8,7 +8,7 @@ import octopi.logging -log = octopi.logging.get_logger("_def") +log = octopi.logging.get_logger(__name__) def conf_attribute_reader(string_value): diff --git a/software/main_hcs.py b/software/main_hcs.py index 5b0f9f25c..007f24d0a 100644 --- a/software/main_hcs.py +++ b/software/main_hcs.py @@ -9,12 +9,15 @@ from qtpy.QtWidgets import * from qtpy.QtGui import * +import octopi.logging +octopi.logging.setup_uncaught_exception_logging() + # app specific libraries import control.gui_hcs as gui from configparser import ConfigParser from control.widgets import ConfigEditorBackwardsCompatible, ConfigEditorForAcquisitions from control._def import CACHED_CONFIG_FILE_PATH -import octopi.logging + def show_config(cfp, configpath, main_gui): config_widget = ConfigEditorBackwardsCompatible(cfp, configpath, main_gui) @@ -25,14 +28,13 @@ def show_acq_config(cfm): acq_config_widget = ConfigEditorForAcquisitions(cfm) acq_config_widget.exec_() - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--simulation", help="Run the GUI with simulated hardware.", action='store_true') parser.add_argument("--performance", help="Run the GUI with minimal viewers.", action='store_true') args = parser.parse_args() - logger = octopi.logging.get_logger("main_hcs") + log = octopi.logging.get_logger("main_hcs") legacy_config = False cf_editor_parser = ConfigParser() @@ -40,7 +42,7 @@ def show_acq_config(cfm): if config_files: cf_editor_parser.read(CACHED_CONFIG_FILE_PATH) else: - logger.error('configuration*.ini file not found, defaulting to legacy configuration') + log.error('configuration*.ini file not found, defaulting to legacy configuration') legacy_config = True app = QApplication([]) app.setStyle('Fusion') diff --git a/software/octopi/logging.py b/software/octopi/logging.py index 518df1822..20e559a77 100644 --- a/software/octopi/logging.py +++ b/software/octopi/logging.py @@ -6,6 +6,7 @@ _octopi_root_logger_name="octopi" + # The idea for this CustomFormatter is cribbed from https://stackoverflow.com/a/56944256 class _CustomFormatter(py_logging.Formatter): GRAY = "\x1b[38;20m" @@ -13,7 +14,7 @@ class _CustomFormatter(py_logging.Formatter): RED = "\x1b[31;20m" BOLD_RED = "\x1b[31;1m" RESET = "\x1b[0m" - FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" + FORMAT = "%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" FORMATS = { py_logging.DEBUG: GRAY + FORMAT + RESET, @@ -23,7 +24,9 @@ class _CustomFormatter(py_logging.Formatter): py_logging.CRITICAL: BOLD_RED + FORMAT + RESET } - FORMATTERS = {level: py_logging.Formatter(fmt) for (level, fmt) in FORMATS.items()} + # NOTE(imo): The datetime hackery is so that we can have millisecond timestamps using a period instead + # of comma. The default asctime + datefmt uses a comma. + FORMATTERS = {level: py_logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S") for (level, fmt) in FORMATS.items()} def format(self, record): return self.FORMATTERS[record.levelno].format(record) @@ -31,6 +34,11 @@ def format(self, record): _COLOR_STREAM_HANDLER = py_logging.StreamHandler() _COLOR_STREAM_HANDLER.setFormatter(_CustomFormatter()) +# Make sure the octopi root logger has all the handlers we want setup. We could move this into a helper so it +# isn't done at the module level, but not needing to remember to call some helper to setup formatting is nice. +py_logging.getLogger(_octopi_root_logger_name).addHandler(_COLOR_STREAM_HANDLER) + + def get_logger(name: Optional[str] = None) -> py_logging.Logger: """ Returns the top level octopi logger instance by default, or a logger in the octopi @@ -41,10 +49,9 @@ def get_logger(name: Optional[str] = None) -> py_logging.Logger: else: logger = py_logging.getLogger(_octopi_root_logger_name).getChild(name) - logger.addHandler(_COLOR_STREAM_HANDLER) - return logger + def set_log_level(level): """ All octopi-research code should use this set_log_level method, and the corresponding octopi.logging.get_logger, @@ -60,10 +67,15 @@ def set_log_level(level): # for our root logger and then check all other loggers to see if they start with our root logger prefix # to find all the octopi specific logger. for (name, logger) in octopi_root_logger.manager.loggerDict.items(): - if name.startswith(_octopi_root_logger_name): + # The logging module uses the PlaceHolder object for nodes in the hierarchy that + # have children, but no associated loggers. EG if we create a logger at + # octopi.control.gui_hcs but not at octopi.control, then the logger for octopi.control + # exists but is a PlaceHolder (until someone explicitly requests it). + if name.startswith(_octopi_root_logger_name) and isinstance(logger, py_logging.Logger): logger.setLevel(level) -def register_crash_handler(handler): + +def register_crash_handler(handler, call_existing_too=True): """ We want to make sure any uncaught exceptions are logged, so we have this mechanism for putting a hook into the python system that does custom logging when an exception bubbles all the way to the top. @@ -87,26 +99,43 @@ def new_excepthook(exception_type: Type[BaseException], value: BaseException, tb handler(exception_type, value, tb) except BaseException as e: logger.critical("Custom excepthook handler raised exception", e) - old_excepthook(exception_type, value, tb) + if call_existing_too: + old_excepthook(exception_type, value, tb) - def new_thread_excepthook(exception_type: Type[BaseException], value: BaseException, tb: TracebackType): + def new_thread_excepthook(hook_args: threading.ExceptHookArgs): + exception_type = hook_args.exc_type + value = hook_args.exc_type + tb = hook_args.exc_traceback try: - handler(exception_type, value, tb) + handler(exception_type, value, type(tb)) except BaseException as e: logger.critical("Custom thread excepthook handler raised exception", e) - old_thread_excepthook(exception_type, value, tb) + if call_existing_too: + old_thread_excepthook(exception_type, value, type(tb)) def new_unraisable_hook(info): - exception_type = info["exception_type"] - tb = info["exception_traceback"] - value = info["exception_value"] + exception_type = info.exc_type + tb = info.exc_traceback + value = info.exc_value try: - handler(exception_type, value, tb) + handler(exception_type, value, type(tb)) except BaseException as e: logger.critical("Custom unraisable hook handler raised exception", e) - old_unraisable_hook(info) + if call_existing_too: + old_unraisable_hook(info) logger.info(f"Registering custom excepthook, threading excepthook, and unraisable hook using handler={handler.__name__}") sys.excepthook = new_excepthook threading.excepthook = new_thread_excepthook - sys.unraisablehook = new_unraisable_hook \ No newline at end of file + sys.unraisablehook = new_unraisable_hook + + +def setup_uncaught_exception_logging(): + """ + This will make sure uncaught exceptions are sent to the root octopi logger as error messages. + """ + logger = get_logger() + def uncaught_exception_logger(exception_type: Type[BaseException], value: BaseException, tb: TracebackType): + logger.exception("Uncaught Exception!", exc_info=value) + + register_crash_handler(uncaught_exception_logger, call_existing_too=False) \ No newline at end of file From 556417504f6b788414dac2b825273c43fe7e15c1 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 24 Oct 2024 08:42:03 -0700 Subject: [PATCH 6/7] logging: rename octopi to squid --- software/control/_def.py | 4 ++-- software/main_hcs.py | 6 ++--- software/{octopi => squid}/__init__.py | 0 software/{octopi => squid}/logging.py | 32 +++++++++++++------------- 4 files changed, 21 insertions(+), 21 deletions(-) rename software/{octopi => squid}/__init__.py (100%) rename software/{octopi => squid}/logging.py (81%) diff --git a/software/control/_def.py b/software/control/_def.py index 676f72b70..f4ceab329 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -6,9 +6,9 @@ import json import csv -import octopi.logging +import squid.logging -log = octopi.logging.get_logger(__name__) +log = squid.logging.get_logger(__name__) def conf_attribute_reader(string_value): diff --git a/software/main_hcs.py b/software/main_hcs.py index 007f24d0a..3a09aaf92 100644 --- a/software/main_hcs.py +++ b/software/main_hcs.py @@ -9,8 +9,8 @@ from qtpy.QtWidgets import * from qtpy.QtGui import * -import octopi.logging -octopi.logging.setup_uncaught_exception_logging() +import squid.logging +squid.logging.setup_uncaught_exception_logging() # app specific libraries import control.gui_hcs as gui @@ -34,7 +34,7 @@ def show_acq_config(cfm): parser.add_argument("--performance", help="Run the GUI with minimal viewers.", action='store_true') args = parser.parse_args() - log = octopi.logging.get_logger("main_hcs") + log = squid.logging.get_logger("main_hcs") legacy_config = False cf_editor_parser = ConfigParser() diff --git a/software/octopi/__init__.py b/software/squid/__init__.py similarity index 100% rename from software/octopi/__init__.py rename to software/squid/__init__.py diff --git a/software/octopi/logging.py b/software/squid/logging.py similarity index 81% rename from software/octopi/logging.py rename to software/squid/logging.py index 20e559a77..55a8d8c98 100644 --- a/software/octopi/logging.py +++ b/software/squid/logging.py @@ -4,7 +4,7 @@ from types import TracebackType import sys -_octopi_root_logger_name="octopi" +_squid_root_logger_name= "squid" # The idea for this CustomFormatter is cribbed from https://stackoverflow.com/a/56944256 @@ -34,44 +34,44 @@ def format(self, record): _COLOR_STREAM_HANDLER = py_logging.StreamHandler() _COLOR_STREAM_HANDLER.setFormatter(_CustomFormatter()) -# Make sure the octopi root logger has all the handlers we want setup. We could move this into a helper so it +# Make sure the squid root logger has all the handlers we want setup. We could move this into a helper so it # isn't done at the module level, but not needing to remember to call some helper to setup formatting is nice. -py_logging.getLogger(_octopi_root_logger_name).addHandler(_COLOR_STREAM_HANDLER) +py_logging.getLogger(_squid_root_logger_name).addHandler(_COLOR_STREAM_HANDLER) def get_logger(name: Optional[str] = None) -> py_logging.Logger: """ - Returns the top level octopi logger instance by default, or a logger in the octopi + Returns the top level squid logger instance by default, or a logger in the squid logging hierarchy if a non-None name is given. """ if name is None: - logger = py_logging.getLogger(_octopi_root_logger_name) + logger = py_logging.getLogger(_squid_root_logger_name) else: - logger = py_logging.getLogger(_octopi_root_logger_name).getChild(name) + logger = py_logging.getLogger(_squid_root_logger_name).getChild(name) return logger def set_log_level(level): """ - All octopi-research code should use this set_log_level method, and the corresponding octopi.logging.get_logger, - to control octopi-research-only logging. + All squid code should use this set_log_level method, and the corresponding squid.logging.get_logger, + to control squid-only logging. - This does not modify the log level of loggers outside the octopi logger hierarchy! If global logging control + This does not modify the log level of loggers outside the squid logger hierarchy! If global logging control is needed the normal logging package tools can be used instead. """ - octopi_root_logger = get_logger() - octopi_root_logger.setLevel(level) + squid_root_logger = get_logger() + squid_root_logger.setLevel(level) # There's no `getAllChildren` method on the logger or its manager, so we just grab the manager # for our root logger and then check all other loggers to see if they start with our root logger prefix - # to find all the octopi specific logger. - for (name, logger) in octopi_root_logger.manager.loggerDict.items(): + # to find all the squid specific logger. + for (name, logger) in squid_root_logger.manager.loggerDict.items(): # The logging module uses the PlaceHolder object for nodes in the hierarchy that # have children, but no associated loggers. EG if we create a logger at - # octopi.control.gui_hcs but not at octopi.control, then the logger for octopi.control + # squid.control.gui_hcs but not at squid.control, then the logger for squid.control # exists but is a PlaceHolder (until someone explicitly requests it). - if name.startswith(_octopi_root_logger_name) and isinstance(logger, py_logging.Logger): + if name.startswith(_squid_root_logger_name) and isinstance(logger, py_logging.Logger): logger.setLevel(level) @@ -132,7 +132,7 @@ def new_unraisable_hook(info): def setup_uncaught_exception_logging(): """ - This will make sure uncaught exceptions are sent to the root octopi logger as error messages. + This will make sure uncaught exceptions are sent to the root squid logger as error messages. """ logger = get_logger() def uncaught_exception_logger(exception_type: Type[BaseException], value: BaseException, tb: TracebackType): From b72b9845d6d780d1bd8452d82f4e58b14f7cbc10 Mon Sep 17 00:00:00 2001 From: Ian OHara Date: Thu, 24 Oct 2024 08:58:07 -0700 Subject: [PATCH 7/7] tests: add basic import + sanity check tests for logging module --- software/tests/__init__.py | 0 software/tests/squid/__init__.py | 0 software/tests/squid/test_logging.py | 15 +++++++++++++++ 3 files changed, 15 insertions(+) create mode 100644 software/tests/__init__.py create mode 100644 software/tests/squid/__init__.py create mode 100644 software/tests/squid/test_logging.py diff --git a/software/tests/__init__.py b/software/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/software/tests/squid/__init__.py b/software/tests/squid/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/software/tests/squid/test_logging.py b/software/tests/squid/test_logging.py new file mode 100644 index 000000000..ad6ec5fb4 --- /dev/null +++ b/software/tests/squid/test_logging.py @@ -0,0 +1,15 @@ +import squid.logging + +def test_root_logger(): + root_logger = squid.logging.get_logger() + assert root_logger.name == squid.logging._squid_root_logger_name + +def test_children_loggers(): + child_a = "a" + child_b = "b" + + child_a_logger = squid.logging.get_logger(child_a) + child_b_logger = child_a_logger.getChild(child_b) + + assert child_a_logger.name == f"{squid.logging._squid_root_logger_name}.{child_a}" + assert child_b_logger.name == f"{squid.logging._squid_root_logger_name}.{child_a}.{child_b}" \ No newline at end of file