[output] add 'defer' option for logging files (#8523)

- defer file creation of error files by default
- implement custom FileHandler class
  to support deferred directory creation
This commit is contained in:
Mike Fährmann
2025-11-27 09:24:35 +01:00
parent 0b05254055
commit 1e7f4ee178
3 changed files with 57 additions and 9 deletions

View File

@@ -9425,7 +9425,8 @@ Example
"format" : "{asctime} {name}: {message}", "format" : "{asctime} {name}: {message}",
"format-date": "%H:%M:%S", "format-date": "%H:%M:%S",
"path" : "~/log.txt", "path" : "~/log.txt",
"encoding" : "ascii" "encoding" : "ascii",
"defer" : true
} }
.. code:: json .. code:: json
@@ -9474,9 +9475,15 @@ Description
* encoding * encoding
* File encoding * File encoding
* Default: ``"utf-8"`` * Default: ``"utf-8"``
* defer
* Defer file opening/creation until writing the first logging message
* Default:
``true`` for `errorfile <output.errorfile_>`__,
``false`` otherwise
Note Note
path, mode, and encoding are only applied when configuring path, mode, encoding, and defer
logging output to a file. are only applied when configuring logging output to a file.
(See `logging.FileHandler <https://docs.python.org/3/library/logging.handlers.html#filehandler>`__)
Postprocessor Configuration Postprocessor Configuration

View File

@@ -291,7 +291,7 @@ def main():
# error file logging handler # error file logging handler
if handler := output.setup_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 = input_manager.err = logging.getLogger("errorfile")
elog.addHandler(handler) elog.addHandler(handler)
elog.propagate = False elog.propagate = False

View File

@@ -176,6 +176,48 @@ class Formatter(logging.Formatter):
return msg 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): def initialize_logging(loglevel):
"""Setup basic logging functionality before configfiles have been loaded""" """Setup basic logging functionality before configfiles have been loaded"""
# convert levelnames to lowercase # convert levelnames to lowercase
@@ -247,7 +289,8 @@ def configure_logging(loglevel):
root.setLevel(minlevel) 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""" """Setup a new logging handler"""
opts = config.interpolate(("output",), key) opts = config.interpolate(("output",), key)
if not opts: if not opts:
@@ -258,12 +301,10 @@ def setup_logging_handler(key, fmt=LOG_FORMAT, lvl=LOG_LEVEL, mode="w"):
path = opts.get("path") path = opts.get("path")
mode = opts.get("mode", mode) mode = opts.get("mode", mode)
encoding = opts.get("encoding", "utf-8") encoding = opts.get("encoding", "utf-8")
delay = opts.get("defer", defer)
try: try:
path = util.expand_path(path) path = util.expand_path(path)
handler = logging.FileHandler(path, mode, encoding) handler = FileHandler(path, mode, encoding, delay)
except FileNotFoundError:
os.makedirs(os.path.dirname(path))
handler = logging.FileHandler(path, mode, encoding)
except (OSError, ValueError) as exc: except (OSError, ValueError) as exc:
logging.getLogger("gallery-dl").warning( logging.getLogger("gallery-dl").warning(
"%s: %s", key, exc) "%s: %s", key, exc)