From ccd4591b5f29889a6877ee7ede1d7006603b4587 Mon Sep 17 00:00:00 2001 From: Kevin Deldycke Date: Sun, 19 Jan 2025 17:59:59 -0800 Subject: [PATCH] Wrap logger manipulation around a lock Just doing it as the standard library does, even if I do not have the proof it is the right thing to do --- click_extra/logging.py | 77 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/click_extra/logging.py b/click_extra/logging.py index a77750841..0e75c81f2 100644 --- a/click_extra/logging.py +++ b/click_extra/logging.py @@ -146,37 +146,39 @@ def extra_basic_config( Add more parameters for even greater configurability of the logger, by re-implementing those supported by ``logging.basicConfig``. """ - # Fetch the logger or create a new one. - logger = logging.getLogger(logger_name) - - # Remove and close any existing handlers. Copy of: - # https://github.com/python/cpython/blob/2b5dbd1/Lib/logging/__init__.py#L2028-L2031 - if force: - for h in logger.handlers[:]: - logger.removeHandler(h) - h.close() - - # If no handlers provided, create a new one with the default handler class. - if not handlers: - handlers = (handler_class(),) - - # Set up the formatter with a default message format. - formatter = formatter_class( - fmt=format, - datefmt=datefmt, - style=style, - ) + with logging._lock: + # Fetch the logger or create a new one. + logger = logging.getLogger(logger_name) + + # Remove and close any existing handlers. Copy of: + # https://github.com/python/cpython/blob/2b5dbd1/Lib/logging/__init__.py#L2028-L2031 + # https://loguru.readthedocs.io/en/stable/resources/recipes.html#avoiding-logs-to-be-printed-twice-on-the-terminal + if force: + for h in logger.handlers[:]: + logger.removeHandler(h) + h.close() + + # If no handlers provided, create a new one with the default handler class. + if not handlers: + handlers = (handler_class(),) + + # Set up the formatter with a default message format. + formatter = formatter_class( + fmt=format, + datefmt=datefmt, + style=style, + ) - # Attach handlers to the loggers. - for h in handlers: - if h.formatter is None: - h.setFormatter(formatter) - logger.addHandler(h) + # Attach handlers to the loggers. + for h in handlers: + if h.formatter is None: + h.setFormatter(formatter) + logger.addHandler(h) - if level is not None: - logger.setLevel(level) + if level is not None: + logger.setLevel(level) - return logger + return logger class VerbosityOption(ExtraOption): @@ -273,15 +275,16 @@ def __init__( if not param_decls: param_decls = ("--verbosity", "-v") - # Use the provided logger instance as-is. - if isinstance(default_logger, Logger): - logger = default_logger - # If a string is provided, use it as the logger name. - elif isinstance(default_logger, str): - logger = logging.getLogger(default_logger) - # ``None`` will produce a default root logger. - else: - logger = extra_basic_config(default_logger) + with logging._lock: + # A logger object has been provided, use it as-is. + if isinstance(default_logger, Logger): + logger = default_logger + # Retrieves the logger object as it is from the registry, if it exists. + elif default_logger in logging.Logger.manager.loggerDict: + logger = logging.getLogger(default_logger) + # Create a new logger with Click Extra's default configuration. + else: + logger = extra_basic_config(default_logger) # Store the logger name for later use. self.logger_name = logger.name