I would like to wrap the creation of a logger and all its associated configuration, formatters and handlers in a separate module, so that from my application I would just write:
import my_logger
test_logger = my_logger.getlogger("logger_name")
This logger will be used in a unit testing framework using unittest.TestCase
for logging purposes.
I use a QueueHandler
and a QueueListener
on one handler (as per https://docs.python.org/3/howto/logging-cookbook.html#dealing-with-handlers-that-block), as the handler is using the Telegram API and I would like to avoid hanging the whole application while waiting for the Telegram server to be available (for whatever reason).
The (perhaps silly) question is:
how can I automatically handle the start and stop of the QueueListener
when I start and stop the execution of the tests? Do I have to subclass a class of the logging module (which one?) to add, say, a start/stop method?
Thank you very much.
UPDATED (01)
I think I was not complete in my question. I already have the wrapper for the logger, you will find it below:
import logging
from logging.handlers import QueueHandler
from logging.handlers import QueueListener
import queue
from time import strftime
from notifiers.logging import NotificationHandler
def getLogger(name):
LOG_DIR = "logs"
_test_logger = logging.getLogger(name)
_test_logger.setLevel(logging.INFO)
# Create a file handler for the test logger.
# This handler will create the log file of the tests.
# TODO: create the LOG_DIR if it does not exist
now = strftime("%Y%m%d_%H%M%S")
# TODO: use os.path methods instead of directly writing path into filenames
_tl_fh = logging.FileHandler("..\{}\{}_{}.txt".format(LOG_DIR, now, name))
_tl_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
_tl_fh.setFormatter(_tl_formatter)
_test_logger.addHandler(_tl_fh)
# Create a console handler for the test logger.
# This is the logger to the console
_tl_console = logging.StreamHandler()
_test_logger.addHandler(_tl_console)
# Create a notifier logger. This will notify via Telegram:
# * start of a test suite
# * Stop of a test suite
# * Test suite and test case errors
# Create an unlimited size queue and attach it to a queue handler
que = queue.Queue(-1)
queue_handler = QueueHandler(que)
# Create a Telegram notification handler.
hdlr = NotificationHandler("telegram", defaults=defaults)
# On the other side of the queue attach a queue listener
_listener = QueueListener(que, hdlr)
queue_handler.setLevel(logging.ERROR)
_test_logger.addHandler(queue_handler)
formatter = logging.Formatter("%(asctime)s - %(levelno)s - %(levelname)s - %(message)s")
hdlr.setFormatter(formatter)
#=======================================================================
# Start the queue listener
#=======================================================================
_listener.start()
return _test_logger
The "problem" is the _listener.start()
line. How can I stop the listener at the end of the test?
Is it possible to add a start/stop methods to the loggers, so that I can write something like?
import my_logger
test_logger = my_logger.getlogger("logger_name")
[...] execute some test here
test_logger.stop()
4
Answers
I think I have found a solution, maybe not the most pythonic but it fit my need. I found the following answer (python logging - With JSON logs can I add an "extra" value to every single log?)
Following the proposed answer, I wrapped the creation and configuration of the logger into a class named
TestLogger
and I added the logic I needed, i.e. thestart_logger()
andstop_logger()
.What I do not like is that I have to redefine the various methods
debug()
,info()
,warning()
,error()
, just to make them available. If someone found a better solution please let me know.Below the complete code.
You can use something like this:
Then you have the following functions at your disposal (to be used instead of
print
):debug
info
warning
error
critical
periodic
something I found elsewhere, and it works great!
Auto-Start and Stop
I’m not sure if this addresses your updated question directly, but for those coming here for the question title here is a way you can wrap the logging.Logger method and retain the full feature set of the logging module via wrapping and overwriting the root logger.
After you have configured your logger and are able to get your logger using the following snippet we can start wrapping the logger.
The actual wrapping of the logger object can be done as follows. If the class that you’re object is instantiated from contains
__slots__
this methodology will not work as expected. (see: https://stackoverflow.com/a/1445289/11770393)After we have created our wrapper class we can create an instance of this class doing the following.
Now if you call logging methods directly against this object, they will perform based on the overwritten methods. However, we can take this a step further to overwrite the root logger in the logging module. By overwriting the root logger we can call
logging.getLogger(LOGGER_NAME)
in other files in our project. The following snippet will perform the overwrite.