Search code examples
pythonpython-3.xloggingpython-logging

Logging Inheritance in Python


I am currently developing a core utils package where I want to set some logging properties (I know that this is not best practice, but it´s for interal purposes and intended to generate logs). When I now import the package nothing gets logged:

# core.__main__.py

class BaseLoggerConfig(BaseModel):

    LOG_FORMAT: str = "%(levelprefix)s %(asctime)s  %(name)s:%(lineno)d: %(message)s"
    DATEFMT: str = "%Y-%m-%d %H:%M:%S"

    LOG_LEVEL: int = logging.INFO

    version: int = 1
    disable_existing_loggers: bool = False
    formatters: dict = {
        "default": {
            # "()": "uvicorn.logging.DefaultFormatter",
            "fmt": LOG_FORMAT,
            "datefmt": DATEFMT,
        },
    }
    filters: dict = {}

    handlers: dict = {
        "default": {
            "formatter": "default",
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stderr",
        }
    }

    loggers: dict = {}

    def __init__(self, name: str, **data):
        super().__init__(**data)

        self.loggers[name] = {
            "handlers": ["default"],
            "level": self.LOG_LEVEL,
            "propagate": False,
        }

LOG_CONFIG = BaseLoggerConfig(__name__)

logging.config.dictConfig(LOG_CONFIG)


- core.__main__
  Level: INFO
  Handlers: ['StreamHandler']

I now have logging in my other files, like:

# core.utils
import logging

logger = logging.getLogger(__name__)

def test():
    logger.info(f"I am a log from {__name__}")

# test.py
import logging
from core.utils import test

logger = logging.getLogger(__name__)

test()


What am I missing?


Solution

  • There are a couple of tweaks that need to be made here.

    First, the file in which you configure your loggers should not be core/__main__.py, it should be core/__init__.py. __main__.py is used when you want to python -m core, which would run __main__.py.

    Second, the fmt key in your formatter config should be called format. You can see the official docs for logging.config.

    Third, in your LOG_FORMAT, levelprefix is not a valid field name. You probably want to use levelname which is "INFO", "DEBUG", etc.

    Putting it all together:

    # core/__init__.py
    
    import logging
    import logging.config
    
    from pydantic import BaseModel
    
    
    class BaseLoggerConfig(BaseModel):
    
        LOG_FORMAT: str = "%(levelname)s %(asctime)s  %(name)s:%(lineno)d: %(message)s"
        DATEFMT: str = "%Y-%m-%d %H:%M:%S"
    
        LOG_LEVEL: int = logging.INFO
    
        version: int = 1
        disable_existing_loggers: bool = False
        formatters: dict = {
            "default": {
                # "()": "uvicorn.logging.DefaultFormatter",
                "format": LOG_FORMAT,
                "datefmt": DATEFMT,
            },
        }
        filters: dict = {}
    
        handlers: dict = {
            "default": {
                "formatter": "default",
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stderr",
            }
        }
    
        loggers: dict = {}
    
        def __init__(self, name: str, **data):
            super().__init__(**data)
    
            self.loggers[name] = {
                "handlers": ["default"],
                "level": self.LOG_LEVEL,
                "propagate": False,
            }
    
    LOG_CONFIG = BaseLoggerConfig(__name__)
    
    logging.config.dictConfig(LOG_CONFIG)
    
    # core/utils.py
    
    import logging
    
    logger = logging.getLogger(__name__)
    
    def test():
        logger.info(f"I am a log from {__name__}")
    
    # test.py
    
    import logging
    from core.utils import test
    
    logger = logging.getLogger(__name__)
    
    test()
    

    Output:

    INFO 2024-11-30 11:06:28  core.utils:8: I am a log from core.utils
    

    EDIT: I realized that your %(levelprefix)s comes from uvicorn's formatter. In that case, you can uncomment your line

                # "()": "uvicorn.logging.DefaultFormatter",
    

    and undo changing the key to format. Which means:

    # core/__init__.py
    
    # ...
    
    class BaseLoggerConfig(BaseModel):
    
        LOG_FORMAT: str = "%(levelprefix)s %(asctime)s  %(name)s:%(lineno)d: %(message)s"
        DATEFMT: str = "%Y-%m-%d %H:%M:%S"
    
        # ...
    
        formatters: dict = {
            "default": {
                # "()": "uvicorn.logging.DefaultFormatter",
                "format": LOG_FORMAT,
                "datefmt": DATEFMT,
            },
        }
    
        # ...
    

    Output: ("INFO" text is green)

    INFO:     2024-12-01 14:13:14  core.utils:6: I am a log from core.utils