Source code for swh.core.logging
# Copyright (C) 2023  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
"""Logging module providing common swh logging configuration. This only depends on
python core library.
"""
import logging
from logging.config import dictConfig
from pathlib import Path
from typing import List, Optional, Tuple
from yaml import safe_load
[docs]
def validate_loglevel(value: str) -> Tuple[Optional[str], int]:
    """Validate a single loglevel specification, of the form LOGLEVEL or
    module:LOGLEVEL."""
    LOG_LEVEL_NAMES = ["notset", "debug", "info", "warning", "error", "critical"]
    if ":" in value:
        try:
            module, log_level = value.split(":")
        except ValueError:
            raise BadLogLevel(
                "Invalid log level specification `%s`, "
                "needs to be in format `module:LOGLEVEL`" % value
            )
    else:
        module = None
        log_level = value
    if log_level.lower() not in LOG_LEVEL_NAMES:
        raise BadLogLevel(
            f"Log level {log_level} unknown (in `{value}`) needs to be one "
            f"of {', '.join(LOG_LEVEL_NAMES)}"
        )
    return (module, logging.getLevelName(log_level.upper()))
[docs]
def logging_configure(
    log_levels: List[Tuple[str, int]] = [], log_config: Optional[Path] = None
) -> str:
    """A default configuration function to unify swh module logger configuration.
    The log_config YAML file must conform to the logging.config.dictConfig schema
    documented at https://docs.python.org/3/library/logging.config.html.
    Returns:
        The actual root logger log level name defined.
    """
    set_default_loglevel: Optional[str] = None
    if log_config:
        with open(log_config, "r") as f:
            config_dict = safe_load(f.read())
        # Configure logging using a dictionary config
        dictConfig(config_dict)
        effective_level = logging.root.getEffectiveLevel()
        set_default_loglevel = logging.getLevelName(effective_level)
    if not log_levels:
        log_levels = []
    for module, log_level in log_levels:
        logger = logging.getLogger(module)
        logger.setLevel(log_level)
        if module is None:
            set_default_loglevel = log_level
    if not set_default_loglevel:
        logging.root.setLevel("INFO")
        set_default_loglevel = "INFO"
    return set_default_loglevel