5

I just implemented logging in my app, and I'd like to know if there's a method to check if a logger is empty or not.

What I have in mind is to set two handlers in my script:

  • One for console with level WARNING
  • One for file with level DEBUG

At the end of the script, I need to check if CONSOLE logger is not empty. This means that during the run, some messages with level >= WARNING were logged, and in this case I'd like to send the log file with debug level thru smtpto my mailbox.

Is this check possible inside the python script itself, without shell redirection to file?

ptierno
  • 9,534
  • 2
  • 23
  • 35
Enrico Gherardo
  • 116
  • 1
  • 6
  • 1
    One thought is to write a custom hander for the console that, if it is ever called, uses `atexit.register` to schedule code that e-mails the log file. – chepner May 05 '15 at 17:38

2 Answers2

4

There's a general purpose way to count the number of times a function was called using decorators you can check out here

If you wrap this decorator around class calls you are interested in you could find out how many times each member function was called, something like this:

def counted(fn):
    def wrapper(*args, **kwargs):
        wrapper.called += 1
        return fn(*args, **kwargs)
    wrapper.called = 0
    wrapper.__name__ = fn.__name__
    return wrapper

class MyLogger(logging.Logger, object):

    @counted
    def info(self, *args, **kwargs):
        super(MyLogger, self).info(*args, **kwargs)

    @counted
    def warning(self, *args, **kwargs):
        super(MyLogger, self).warning(*args, **kwargs)

Next just set up your logger like any other and use it as you normally would:

log = my_logger.MyLogger('test_logger')
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
log.addHandler(ch)
log.setLevel(logging.DEBUG)

>>> log.info('no big deal')
no big deal
>>> log.info('harmless message')
harmless message
>>> log.warning('oh no')
oh no

>>> log.info.called
2
>>> log.warning.called
1

You would need to decorate all of the classes you wanted counted, so exception, debug etc.

Community
  • 1
  • 1
Mike
  • 6,813
  • 4
  • 29
  • 50
  • Thank you Mike, I'll try it. This seems to be an elegant solution too. – Enrico Gherardo May 05 '15 at 21:05
  • I tried that def init_logger(lid: str): log = MyLogger(str(lid)) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) log.addHandler(ch) log.setLevel(logging.DEBUG) return log log1 = init_logger(1) log2 = init_logger(2) log1.info("INFO 1") print(log2.info.called) --> 1 Why? – Christian Sicari Apr 14 '21 at 20:05
0

I'm sorry, I forgot to complete this thread.
Following Mike's idea, I came up with something like this :

in a module called logset.py :

def counted(fn):

    def wrapper(*args, **kwargs):
        wrapper.count += 1
        return fn(*args, **kwargs)
    wrapper.count = 0
    wrapper.__name__ = fn.__name__
    return wrapper

class MyLogger(logging.Logger):

    def __init__(self, name=None, level=logging.NOTSET):
        super(MyLogger, self).__init__(name, level)

    @counted
    def info(self, *args, **kwargs):
        super(MyLogger, self).info(*args, **kwargs)

    @counted
    def warning(self, *args, **kwargs):
        super(MyLogger, self).warning(*args, **kwargs)

    @counted
    def critical(self, msg, *args, **kwargs):
        super(MyLogger, self).warning(*args, **kwargs)

    @counted
    def error(self, *args, **kwargs):
        super(MyLogger, self).warning(*args, **kwargs)

    def logfile(self):
        for h in self.handlers:
            if hasattr(h, 'baseFilename'):
                return h.baseFilename

    def empty(self):
        if self.warning.count or self.critical.count or self.error.count:
            return False
        else:
            return True

    def status(self):
        msg = "WARNINGS:%s ERRORS:%s CRITICAL:%s" % (self.warning.count, self.error.count, self.critical.count)
        return msg

def addLogFile(logger, filepath):

    handler = logging.FileHandler(filepath, "w", encoding=None, delay="true")
    handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter("%(levelname)s\t: %(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)

def addLogConsole(logger):

    handler = logging.StreamHandler()
    handler.setLevel(logging.WARNING)
    formatter = logging.Formatter("%(levelname)s\t: %(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)

def myLog(level=None):

    if not LOGGER.handlers:
        # "Adding Handlers..."
        addLogConsole(LOGGER)
        addLogFile(LOGGER, '#YOUR LOG FILE#')

    return LOGGER

LOGGER = MyLogger("root")

In my app's start file :

from logset import myLog
.
.
. # Initialize the log
root = myLog()
.
.
. # Do something in your app and at end check :

if not root.empty():
    print "\nProgram has logged some errors, check log : %s" % root.logfile()
    print root.status()
    # now I can send root.logfile() via smtp for further examination

You can repeat the import in every module that needs logging, it always refers to the same logger object:

from logset import myLog
log = myLog()
.
.
.
log.info("Adding something")
log.debug("this var has value : %s" % value)

Thank you for your help and attention.
Enrico

Enrico Gherardo
  • 116
  • 1
  • 6