'Unable to Mock the logging variables

I have created a custom logging class which is used to log in the elastic search. But to run the unit-test, I want to mock those variable but I am unable to figure out a way to do so.

Below is the sample to recreate the error:

main.py:

from app.log_utils import printlog, timing
from time import sleep

def func():
    timing.start("abc")
    print("Running...")
    def func2():
        name="Root logger"
        greeting="Nice to meet you"
        printlog.info(f"Hi, {name}. {greeting}")
    
    name="world"
    greeting="Nice to meet you"
    printlog.warn(f"Hi, {name}! {greeting}")
    printlog.info("Logging info")
    func2()

    timing.end("abc")
    print("Sleeping...")
    sleep(0.5)
    print("Done.")


if __name__=="__main__":
    func()

log_utils.py:

import ...

open("./logs/file.log", "a").close()

logfile_format = " - ".join(
    [
        "%(asctime)s",
        "%(process)-6d",
        "%(levelname)-8s",
        "%(filename)s",
        "%(funcName)s",
        "%(message)s",
    ]
)
stdout_format = " - ".join(["%(asctime)s", "%(levelname)s", "%(message)s"])

class Timing: ...

class CustomLog:
    def __init__(self, host, port, index, level) -> None:
        try:
            self.log_level = int(logging.getLevelName(level)) if level.upper()!="ALL" else 0
        except ValueError:
            raise f"Invalid level input {level}"
        self.es = elasticsearch.Elasticsearch([{"scheme": "http", 'host': host, 'port': port}])
        self.index = index
        self.logger = self.get_logger(log_filename="./logs/file.log",
                                        maxsize_MB=100,
                                        stdout_level=level,
                                        logfile_level=level)
        self.logger.propagate = False
        self.es_create_index_if_not_exists()
    
    def es_create_index_if_not_exists(self):
        """Create the given ElasticSearch index and ignore error if it already exists"""
        try:
            self.es.indices.create(index = f"{self.index}")
            self.es.indices.refresh(index=f"{self.index}")
        except elasticsearch.exceptions.RequestError as ex:
            if ex.error == 'resource_already_exists_exception':
                pass # Index already exists. Ignore.
            else: # Other exception - raise it
                pass

    def _check_level(self, level):
        if logging.getLevelName(level) >= self.log_level:
            return True
        return False

    def log(self, msg, log_lvl, caller=None):
        caller = getframeinfo(stack()[1][0]) if caller is None else caller
        _index = f"{self.index}_{datetime.now().date().strftime('%y.%m.%d')}"
        doc = {
            "@timestamp": datetime.now(),
            "log.level": log_lvl,
            "message": msg,
            "host": socket.gethostname(),
            "file": {
                "line": caller.lineno,
                "name": caller.filename,
                "function": caller.function
            }
        }

        self.logger.log(logging.getLevelName(log_lvl), msg)
        try:
            self.es.index(index=_index, document=doc)
        except Exception as ex:
            self.logger.error(f"Unable to write to the elastic {repr(ex)}")
        # self.es.indices.refresh(index=self.index)

    def info(self, msg):
        lvl = "INFO"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)
            
    def error(self, msg):
        lvl = "ERROR"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)

    def debug(self, msg):
        lvl = "DEBUG"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)

    def fatal(self, msg):
        lvl = "FATAL"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)
    
    def warning(self, msg):
        lvl = "WARNING"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)
    
    def warn(self,msg):
        lvl = "WARN"
        caller = getframeinfo(stack()[1][0])
        if self._check_level(lvl):
            self.log(msg, lvl, caller)


    def get_logger(self,log_filename, maxsize_MB=0, backupCount=3, stdout_level='INFO', logfile_level='DEBUG'):
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.getLevelName(logfile_level.upper()))
        screen_formatter = logging.Formatter(stdout_format,
                                            datefmt="%Y-%m-%d %H:%M:%S")
        file_formatter = logging.Formatter(logfile_format,
                                        datefmt="%Y-%m-%d %H:%M:%S")

        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.getLevelName(stdout_level.upper()))
        ch.setFormatter(screen_formatter)
        logger.addHandler(ch)

        handler = RotatingFileHandler(log_filename, maxBytes=maxsize_MB * 1e6, backupCount=backupCount)
        handler.setLevel(logging.getLevelName(logfile_level.upper()))
        handler.setFormatter(file_formatter)
        logger.addHandler(handler)
        return logger

printlog = CustomLog( host = os.environ.get("IP"), port = os.environ.get("port"), index = os.environ.get("_index"), level = "INFO")

timing = Timing(logger=printlog)

I have tried to write the unit-test for above implementation but getting error while importing the 'func' method due to this line in the log_utils.py self.es = elasticsearch.Elasticsearch([{"scheme": "http", 'host': host, 'port': port}]).

test_func.py:

import mock
import unittest

from app.main import func

@mock.patch('app.seqlogtest.printlog')
@mock.patch('app.seqlogtest.timing')
class RmTestCase(unittest.TestCase):
    
    def test_func(self): # , mock_log, mock_timing
        self.assertEqual(func(),None)

Any assistance is greatly appreciated.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source