-1

I extended the python logger class given below:

class CommonLogger(Logger):

    def debug(self, *args):
        self.check_and_log(10, args)

    def info(self, *args):
        self.check_and_log(20, args)

    def warn(self, *args):
        self.check_and_log(30, args)

    def error(self, *args):
        self.check_and_log(40, args)

    def check_and_log(self, level, args):
        if self.isEnabledFor(level):
            log_str = self.convert_list_to_string(args)
            super().log(level, log_str)

    @staticmethod
    def convert_list_to_string(args):
        return ''.join(str(msg) for msg in args)

And then create a logger in every class that uses it like so:

self.logger = CommonLogger(logging.getLogger(Constants.LOGGER_NAME))
self.logger.info('starting logger: ' + str(Constants.LOGGER_NAME))

But when I run this, it doesn't throw an error - but it also doesn't print any logs on console. Any idea why?

UPDATE: I compared this with the normal flow of a log getting printed - and it looks like my logging.handlers aren't initialized (empty list) inside Logger Class when its created as an implementation of the Logger Class.

handlers = {list} <class 'list'> []

while in a normal flow - handlers are filled out like so:

handlers = {list} <class 'list'>: [<StreamHandler <stdout> (INFO)>, <RotatingFileHandler D:\y\logs\engine.log (INFO)>]
 0 = {StreamHandler} <StreamHandler <stdout> (INFO)>
 1 = {RotatingFileHandler} <RotatingFileHandler D:\y\logs\engine.log (INFO)>
 __len__ = {int} 2

Any Suggestions?

Ramsha Siddiqui
  • 460
  • 6
  • 20
  • there's usually some ceremony to jump through with the silly built in logger. this usually does the trick for me: `logging.basicConfig(level=logging.DEBUG)`, could this be your problem? – JL Peyret Feb 12 '20 at 06:54
  • No when i use a normal logger - without wrapping it in CommonLogger() it prints logs as normal - on console and in file. I think there's something wrong with the wrapper - but its not throwing errors. I debugged and found out that a LogRecord does get formed - it just doesnt log it on console/file. – Ramsha Siddiqui Feb 12 '20 at 06:57
  • Subclassing `logging.Logger` class is not needed in most cases as the `logging` module provides lots of functionality. There's a related question [How to extend the logging.Logger Class?](https://stackoverflow.com/questions/39492471/how-to-extend-the-logging-logger-class). I hope it would help your issue. – DumTux Feb 12 '20 at 06:59
  • the problem with the normal logger is that it typecasts and concatenates strings before evaluating whether or not the log level was on. Because I/O is such an expensive operation in production environments - we wanted to implement a generic logger that first checks the level of logging - and then concatenates / typecasts strings together. – Ramsha Siddiqui Feb 12 '20 at 07:04
  • And no, i checked this question before - it doesnt help my case.Its more about logging configurations - which are working fine in my case. – Ramsha Siddiqui Feb 12 '20 at 07:06
  • have you tried commenting out your if condition? it would suppress printing if falsey, rather than a config-driven error to chase after. same reasoning: i would just pass in a hardcoded string rather than the calculated `log_str`. maybe it's writing `""` to stderr which has no linefeeds unlike a print. i.e. `super().error("hello!")` – JL Peyret Feb 12 '20 at 07:13
  • just tried it - no logs printed still O.O - i think I've messed up the initialization - i'll try making the logger inside the class. – Ramsha Siddiqui Feb 12 '20 at 07:17
  • you could also do a `pdb.set_trace()` and step into the call into the logger.error, both on a normal logger and your logger. probably starting with your logger. at some point, it wont find something and it will return. thing is, the logger is complicated so you may be there a while ;-) – JL Peyret Feb 12 '20 at 07:24
  • Hi! I updated my problem above. Please review. – Ramsha Siddiqui Feb 12 '20 at 07:34
  • your super `__init__` call doesn't match what your own `__init__` gets. do you even need it at all? it doesnt do anything that i see, aside from changing the param. try commenting it out. – JL Peyret Feb 12 '20 at 07:37
  • yeah I didnt have it before - coz it doesnt do anything, but i added it in case I'm missing something. I'll remove it again. – Ramsha Siddiqui Feb 12 '20 at 07:40
  • @JLPeyret "there's usually some ceremony to jump through with the silly built in logger" => the "silly builtin logger" is __very__ far from being "silly" - it's complex, yes, but there are very good reasons for this complexity, as you'll possibly understand when you'll have to maintain a large system with same packages used in different contexts / applications needing different logging config. – bruno desthuilliers Feb 12 '20 at 08:30
  • "Because I/O is such an expensive operation" => and how does this relates to formatting the message ? string formatting is not doing any I/O. – bruno desthuilliers Feb 12 '20 at 08:55
  • "Any Suggestions?" => yes: 1/ double-check your facts, and 2/ use a profiler before deciding that there's any optimization needed here, and if yes what _really needs to be optimized. So far, your arguments look dangerously like wild guesses to me, and wild guesses are the worst basis for optimization. – bruno desthuilliers Feb 12 '20 at 08:59
  • "i do mean to use non-type casted comma-separated values " => you mean something like `logger.debug("foo %s %s %s", 42, False, [int])` ? . "I did read the logging class definition mentioned here" (nb: "here" = https://docs.python.org/3/howto/logging-cookbook.html) => what about this instead https://github.com/python/cpython/tree/master/Lib/logging ? That's what I was talking about when I suggested you checked your facts. – bruno desthuilliers Feb 12 '20 at 13:28
  • @brunodesthuilliers well, we can certainly agree to disagree. logging shows its Java origins in how complicated it can be. Yes, it covers complex use cases, but even trivial use cases are complicated. If you take the basic Django setup in the doc vs that coming from cookiecutter you end up with something that does essentially the same thing except Django does file and cookiecutter does console. Merging to do console&file is non-obvious as the logger hierarchies are not the same. Simple things shouldn't be that hard. Coincidentally, alternative loggers are often popular pypi packages. – JL Peyret Feb 12 '20 at 23:35
  • @JLPeyret we do have to disagree indeed - I don't find the trivial cases to be complicated (what's complicated in calling `logging.basisConfig()` or `logging.dictConfig()`in your app's entry point ?), and I never had any problem setting up Django's logging either, for both dev and prod setups. – bruno desthuilliers Feb 13 '20 at 09:22

1 Answers1

0

This is what eventually solved my problem:

class Message(object):
    def __init__(self, fmt, args):
        self.fmt = fmt
        self.args = args

    def __str__(self):
        return self.fmt + ''.join(str(arg) for arg in self.args)


class CommonLoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, extra=None):
        super(CommonLoggerAdapter, self).__init__(logger, extra or {})

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, kwargs = self.process(msg, kwargs)
            self.logger._log(level, Message(msg, args), (), **kwargs)

Apparently, using an Adapter Implementation instead of a Logger Implementation works better, as logging.config is shared between both of them. If you want to use a Logger Implementation, then you need to do this at the time of initialization:

def __init__(name):
    for handler in logging._handlers.values():
        self.addhandler(handler)

Both these options work! Hope it helps others too!

Ramsha Siddiqui
  • 460
  • 6
  • 20