diff --git a/src/openflexure_microscope_server/logging.py b/src/openflexure_microscope_server/logging.py index 84c253c5..6afe04ef 100644 --- a/src/openflexure_microscope_server/logging.py +++ b/src/openflexure_microscope_server/logging.py @@ -11,6 +11,7 @@ OFM_LOG_FILE = os.path.join(OFM_LOG_FOLDER, "openflexure_microscope.log") def configure_logging(): root_logger = logging.getLogger() root_logger.setLevel(logging.INFO) + try: if not os.path.exists(OFM_LOG_FOLDER): os.makedirs(OFM_LOG_FOLDER) @@ -20,10 +21,13 @@ def configure_logging(): maxBytes=1000000, backupCount=10, ) - handler.setFormatter( - logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s") - ) + format_str = "[%(asctime)s] [%(levelname)s] <%(name)s> %(message)s" + handler.setFormatter(logging.Formatter(format_str)) root_logger.addHandler(handler) + ofm_format_str = "[%(asctime)s] [%(levelname)s] %(message)s" + OFM_HANDLER.setFormatter(logging.Formatter(ofm_format_str)) + root_logger.addHandler(OFM_HANDLER) + except PermissionError as e: logging.warning(f"Cannot create log file at {OFM_LOG_FILE}: {e}") logging.info("") @@ -33,5 +37,65 @@ def configure_logging(): def retrieve_log() -> PlainTextResponse: - """The most recent 1Mb of logs from the server""" - return FileResponse(OFM_LOG_FILE, media_type=PlainTextResponse.media_type) + """ + Returns logs since we started running the server, up to a maxiumum of + 250 messages. This log is the one shown in the UI and on the logging page. + + It does not include any of the `uvicorn.access` logs as these are emmitted + every time any API route is called. + + All logs, including `uvicorn.access` are logged to the OFM_LOG_FILE (see above) + ths is the best place to get logs about crashes. + """ + return PlainTextResponse(OFM_HANDLER.log_history) + + +class OFMHandler(logging.Handler): + """ + A child class of logging.Handler. This class handles storing the most recent + logs for access by the server. + """ + + def __init__(self, level=logging.INFO, max_logs=250): + super().__init__(level=level) + self._log = [] + self._max_logs = max_logs + + def append_record(self, record): + """ + Use the built in formatter to format the record, then save + it to an array. Pop any in excess of the mamimum number of logs + """ + self._log.append(self.format(record)) + while len(self._log) > self._max_logs: + self._log.pop(0) + + def emit(self, record): + """ + Emit will save the logged record to the log + """ + + # Basic filter for now that simply stops uvicorn.access logs + # These are the logs each time an API endpoint is accessed + # This is only the log for the UI. + if record.name.startswith("uvicorn.access"): + return + try: + if record.levelno >= self.level: + self.append_record(record) + except RecursionError as e: + raise e + except BaseException: + # Never let the logger crash unless there is somehow recursion. + # Use built in logging error handler + self.handleError(record) + + @property + def log_history(self): + """ + Return the log history up to the maximum number of logs + """ + return "\n".join(self._log) + + +OFM_HANDLER = OFMHandler() diff --git a/webapp/src/components/tabContentComponents/loggingContent.vue b/webapp/src/components/tabContentComponents/loggingContent.vue index 85547734..d74a27f5 100644 --- a/webapp/src/components/tabContentComponents/loggingContent.vue +++ b/webapp/src/components/tabContentComponents/loggingContent.vue @@ -30,7 +30,10 @@
- Download Log File