How to leverage the 'logging' module in a library while providing an easy interface to the library users?

37 Views Asked by At

I am developping a Python library that defines the following classes:

  • a Controller class
  • a BaseManager class and several BaseManagerXxx children classes

The Controller instanciates managers, then run them (make them do things). A user application using this library will typically implement new classes that inherit from BaseManagerXxx classes, instanciates one Controller specifying the managers instances to create, and run this controller. An example of minimal code is as follows:

#---------------------------------------------------
# Library code
#---------------------------------------------------

class Controller:

    def __init__(self,managerinfo):
        self.managers = [mtype(mname) for mtype,mname in managerinfo]

    def run(self):
        # do something with managers, for instance:
        self.managers[0].parseStuff()
        self.managers[1].cleanStuff()

class BaseManager:
    def __init__(self,name):
        self.name = name
    def parseStuff(self):
        pass #something is actually done here
    def cleanStuff(self): # example only
        pass #something is actually done here

class BaseManager1: BaseManager:
    pass # actually redefine some base methods
class BaseManager2: BaseManager:
    pass # actually redefine some base methods

A user app code looks like this:

#---------------------------------------------------
# User app code
#---------------------------------------------------
from the_library import *
from another_user_module import ARandomClass

class MyManager1: BaseManager1
    def parseStuff(self):
        c = ARandomClass()  #might also log messages or raise exceptions
        print("WARNING: something might be wrong")

class MyManager2: BaseManager2
    def cleanStuff(self):
        raise RuntimeError("Something is seriously wrong here")

c = Controller([
    (MyManager1,"foo"),
    (MyManager2,"bar")])
c.run()

For now, logging is done using print statements, with strongly heterogenous styles due to custom managers being developped by different users for a same app. I want to leverage the 'logging' module, for consistency, and to apply a common style to all logs done by every part of the app. Moreover, I want all the exceptions to be catched and also result into a log message with the same style.

More precisely:

  • Each log must contain the name of the manager the execution is currently in (or an empty string if the we are in Controller code); specifically, in the example above, this means that if a log message is for instance emitted in the ARandomClass's constructor, this is still the name of the manager (foo in this case) that should be shown.
  • All exceptions must be catched and result in a 'CRITICAL' log message (also containing a name as described above)
  • In the user app, there should be no more to do for the user than one import statement and one simple statement for each log. Standard exception should be supported (i.e. the users cannot be asked to use only a custom exception).

The only way I found to satisfy those requirements is to use a single logger and to encapsulate, in controller code, all calls to manager methods. Something like this:

import logging

class MyLogger:
    pass #some basic logic here using **a single logger** from logging module

class Controller:

    def __init__(self,managerinfo):
        self.logger = MyLogger()
        self.managers = []
        for mtyme,mname in managerinfo:
            self.logger.setName(mname)
            m = mtype(mname)
            self.logger.setName(None)
            self.managers.append(m)

    def run(self):
        # do something with managers, for instance:
        self.logger.setName(self.managers[0].name)
        self.managers[0].parseStuff()
        self.logger.setName(self.managers[1].name)
        self.managers[1].cleanStuff()
        self.logger.setName(None)

The exception management would be done redefining sys.excepthook and using yet the same logger.

This works, but does not look like the standard way of using the logging module (one logger per module) and I am not very satisfied with the architecture. Actually, there is almost no need to use the logging module with such a pattern (a single class encapsulating print would be enough). Would there be a more standard/better way of achieving what I described here?

0

There are 0 best solutions below