2
\$\begingroup\$

I need to log the activities of instances of a class. Code that uses the class should be able to disable the default class logger (which, by default, is initialized with a StreamHandler that output log messages to console) and add handlers to it (i.e., to support writing log messages out to a log file).

It appears to work fine. What irks me is that I had to rely on a decorator (per this advice) to initialize the class_logger. This may be unavoidable. I'm ok with it but if there's a better way, I'd like to know about it.

Code for Person class is something I might use in multiple modules. Are there are any outstanding issues with this implementation?

person.py

import logging

def __init_static_members(cls):
    cls.class_logger = cls._init_class_logger()
    return cls

@__init_static_members
class Person:
    class_logger = None

    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    @property
    def basic_info(self):
        return '({},{},{})'.format(self.first_name, self.last_name, self.age)

    def jump(self):
        self.__class__.class_logger.info('{} jumping!'.format(self))

    def shout(self, msg):
        self.__class__.class_logger.info('{} shouting "{}"'.format(self, msg))

    @classmethod
    def _init_class_logger(cls):
        logger = logging.getLogger('PersonClass')
        logger.setLevel(logging.DEBUG)
        console_logger = logging.StreamHandler()
        console_logger.setLevel(logging.DEBUG)
        console_logger.setFormatter(logging.Formatter('%(levelname)5s %(asctime)s:%(message)s', '%Y/%m/%d %H:%M:%S'))
        logger.addHandler(console_logger)
        return logger

    def __str__(self): 
        return self.basic_info

if __name__ == '__main__':
    johnny_appleseed = Person('Johnny', 'Appleseed', 17)
    johnny_appleseed.jump() # Uses default class logger
    johnny_appleseed.shout("I'm going to plant apple trees in Pennsylvania, ONtario, Ohio, Indiana, and Illinois!")

    wendy_wonder = Person('Wendy', 'Wonder', 17)
    wendy_wonder.jump() # Uses default class logger

main.py

import os
import logging
from logging.handlers import RotatingFileHandler

import person

def _init_rfh(path):
    rfhandler = RotatingFileHandler(path, mode='a', maxBytes=1000000, backupCount=2)
    rfhandler.setLevel(logging.DEBUG)
    rfhandler.setFormatter(
        logging.Formatter('%(levelname)5s %(asctime)s:%(message)s', '%Y/%m/%d %H:%M:%S')
    )
    return rfhandler

if __name__ == '__main__':

    # Attach a RotatingFileHandler to the Person's class logger 
    logpath = r'C:\Users\mtran\Desktop\Development\Python\Python_Notes\ex_classlogger\logs'
    logfilepath = os.path.join(logpath, 'main.log')
    person.Person.class_logger.addHandler(_init_rfh(logfilepath))

    bz = person.Person('Buzz', 'Lightyear', 32)
    bz.jump() # Uses class Person's default logger with RotatingFileHandler attached

    # Disable the Person class logger
    person.Person.class_logger.disabled = True
    bz.jump() # Log message to console and rotating log file is suppressed
    bz.shout('To infinity ... and beyond!')  # Suppressed

    # Enable Person class logger
    person.Person.class_logger.disabled = False
    bz.jump()
    bz.shout('To infinity ... and beyond ... AGAIN!')  # Suppressed

Console Output (main.py)

INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) jumping!
INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) jumping!
INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) shouting "To infinity ... and beyond ... AGAIN!"

main.log

INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) jumping!
INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) jumping!
INFO 2019/08/09 14:42:41:(Buzz,Lightyear,32) shouting "To infinity ... and beyond ... AGAIN!"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.