From 4cdd52059d37067d0e3f0441ba9a40b2151b89cd Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Wed, 6 May 2026 00:38:56 +0200 Subject: [PATCH] Create logging target option and fallbacks --- src/jcloud_deployment_server/api/logging.py | 105 +++++++++++++++++--- src/jcloud_deployment_server/api/main.py | 24 +---- 2 files changed, 92 insertions(+), 37 deletions(-) diff --git a/src/jcloud_deployment_server/api/logging.py b/src/jcloud_deployment_server/api/logging.py index c9c387a..915ea99 100644 --- a/src/jcloud_deployment_server/api/logging.py +++ b/src/jcloud_deployment_server/api/logging.py @@ -17,17 +17,50 @@ import jcloud_config_parser import logging import pathlib from dataclasses import dataclass +from .exceptions import Fail __all__ = [ + 'LoggingInfo', 'setup_logging' ] @dataclass class LoggingInfo: logger: logging.Logger - logfile: pathlib.Path - format: str - level: int + uvicorn_config: dict + +def _logfile_fallback_used(path: pathlib.Path) -> bool: + ''' + Returns whether the fallback value for the logfile should be used. + + :param path: The path to the logfile. + :type path: pathlib.Path + + :return: Whether the fallback value should be used. + :rtype: bool + ''' + + # Path exists and is a directory -> invalid + if path.exists() and path.is_dir(): + return True + + parent = path.parent or pathlib.Path('.') + + # Parent directory missing -> try to create it + if not parent.exists(): + try: + parent.mkdir(parents = True, exist_ok = True) + except OSError: + return True + + # Check whether the path is writable. + try: + with open(path, 'a'): + pass + except (OSError, PermissionError): + return True + + return False def setup_logging(configuration: jcloud_config_parser.ini.INIConfiguration) -> LoggingInfo: ''' @@ -40,8 +73,22 @@ def setup_logging(configuration: jcloud_config_parser.ini.INIConfiguration) -> L :rtype: LoggingInfo ''' - logfile = configuration.logging.logfile - if os.path.isdir(logfile) or not logfile: + fail_strict = configuration.logging.fail_strict.lower() in ('true', 'yes', 't', 'y') + + logtarget = configuration.logging.logtarget + + if logtarget not in ('file', 'stdout'): + if fail_strict: + raise Fail(f'\'{logtarget}\': invalid value for [logging].logtarget. Expected either \'file\' or \'stdout\'.') + logtarget = 'stdout' + + if logtarget == 'file': + logfile = pathlib.Path(configuration.logging.logfile) + if _logfile_fallback_used(logfile): + if fail_strict: + raise Fail(f'\'{logfile}\': Cannot use as a log file.') + logtarget = 'stdout' # fallback to stdout + else: logfile = None loglevel = logging._nameToLevel.get( @@ -54,17 +101,47 @@ def setup_logging(configuration: jcloud_config_parser.ini.INIConfiguration) -> L logging.basicConfig(level=loglevel, format=logging_format) logger = logging.getLogger('api') logger.propagate = False - logger.handlers = list() formatter = logging.Formatter(logging_format) - if logfile is not None: - logger_file_handler = logging.FileHandler(logfile) - logger_file_handler.setFormatter(formatter) - logger.addHandler(logger_file_handler) - + + if logtarget == 'file': + logger.handlers = list() + handler = logging.FileHandler(logfile) + handler.setFormatter(formatter) + else: + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + + uvicorn_config = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'uvicorn_formatter': { + 'format': logging_format + } + }, + 'loggers': { + 'uvicorn': { + 'handlers': ['uvicorn_handler'], + 'level': loglevel, + 'propagate': False + }, + }, + 'handlers': { + 'uvicorn_handler': { + 'class': 'logging.FileHandler', + 'filename': logfile, + 'formatter': 'uvicorn_formatter' + } if logtarget == 'file' + else { + 'class': 'logging.StreamHandler', + 'formatter': 'uvicorn_formatter' + } + } + } + return LoggingInfo( logger, - logfile, - logging_format, - loglevel + uvicorn_config ) \ No newline at end of file diff --git a/src/jcloud_deployment_server/api/main.py b/src/jcloud_deployment_server/api/main.py index ab68247..8f647fb 100644 --- a/src/jcloud_deployment_server/api/main.py +++ b/src/jcloud_deployment_server/api/main.py @@ -50,27 +50,5 @@ def main(): host = host, port = port, server_header = False, - log_config = { - 'version': 1, - 'disable_existing_loggers': True, - 'handlers': { - 'uvicorn_handler': { - 'class': 'logging.FileHandler', - 'filename': str(logger_info.logfile), - 'formatter': 'uvicorn_formatter', - }, - }, - 'formatters': { - 'uvicorn_formatter': { - 'format': logger_info.format - }, - }, - 'loggers': { - 'uvicorn': { - 'handlers': ['uvicorn_handler'], - 'level': logger_info.level, - 'propagate': False - }, - }, - } + log_config = logger_info.uvicorn_config ) \ No newline at end of file