diff --git a/docs/configuration.rst b/docs/configuration.rst index a5fe78b3..873c5fe6 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -9425,7 +9425,8 @@ Example "format" : "{asctime} {name}: {message}", "format-date": "%H:%M:%S", "path" : "~/log.txt", - "encoding" : "ascii" + "encoding" : "ascii", + "defer" : true } .. code:: json @@ -9474,9 +9475,15 @@ Description * encoding * File encoding * Default: ``"utf-8"`` + * defer + * Defer file opening/creation until writing the first logging message + * Default: + ``true`` for `errorfile `__, + ``false`` otherwise Note - path, mode, and encoding are only applied when configuring - logging output to a file. + path, mode, encoding, and defer + are only applied when configuring logging output to a file. + (See `logging.FileHandler `__) Postprocessor Configuration diff --git a/gallery_dl/__init__.py b/gallery_dl/__init__.py index fdcb6d02..3e152a98 100644 --- a/gallery_dl/__init__.py +++ b/gallery_dl/__init__.py @@ -291,7 +291,7 @@ def main(): # error file logging handler if handler := output.setup_logging_handler( - "errorfile", fmt="{message}", mode="a"): + "errorfile", fmt="{message}", mode="a", defer=True): elog = input_manager.err = logging.getLogger("errorfile") elog.addHandler(handler) elog.propagate = False diff --git a/gallery_dl/output.py b/gallery_dl/output.py index 3be23e58..fe7235e1 100644 --- a/gallery_dl/output.py +++ b/gallery_dl/output.py @@ -176,6 +176,48 @@ class Formatter(logging.Formatter): return msg +class FileHandler(logging.StreamHandler): + def __init__(self, path, mode, encoding, delay=True): + self.path = path + self.mode = mode + self.errors = None + self.encoding = encoding + + if delay: + logging.Handler.__init__(self) + self.stream = None + self.emit = self.emit_delayed + else: + logging.StreamHandler.__init__(self, self._open()) + + def close(self): + with self.lock: + try: + if self.stream: + try: + self.flush() + self.stream.close() + finally: + self.stream = None + finally: + logging.StreamHandler.close(self) + + def _open(self): + try: + return open(self.path, self.mode, + encoding=self.encoding, errors=self.errors) + except FileNotFoundError: + os.makedirs(os.path.dirname(self.path)) + return open(self.path, self.mode, + encoding=self.encoding, errors=self.errors) + + def emit_delayed(self, record): + if self.mode != "w" or not self._closed: + self.stream = self._open() + self.emit = logging.StreamHandler.emit.__get__(self) + self.emit(record) + + def initialize_logging(loglevel): """Setup basic logging functionality before configfiles have been loaded""" # convert levelnames to lowercase @@ -247,7 +289,8 @@ def configure_logging(loglevel): root.setLevel(minlevel) -def setup_logging_handler(key, fmt=LOG_FORMAT, lvl=LOG_LEVEL, mode="w"): +def setup_logging_handler(key, fmt=LOG_FORMAT, lvl=LOG_LEVEL, mode="w", + defer=False): """Setup a new logging handler""" opts = config.interpolate(("output",), key) if not opts: @@ -258,12 +301,10 @@ def setup_logging_handler(key, fmt=LOG_FORMAT, lvl=LOG_LEVEL, mode="w"): path = opts.get("path") mode = opts.get("mode", mode) encoding = opts.get("encoding", "utf-8") + delay = opts.get("defer", defer) try: path = util.expand_path(path) - handler = logging.FileHandler(path, mode, encoding) - except FileNotFoundError: - os.makedirs(os.path.dirname(path)) - handler = logging.FileHandler(path, mode, encoding) + handler = FileHandler(path, mode, encoding, delay) except (OSError, ValueError) as exc: logging.getLogger("gallery-dl").warning( "%s: %s", key, exc)