11

I was wondering what the standard set up is for performing logging from within a Python app.

I am using the Logging class, and I've written my own logger class that instantiates the Logging class. My main then instantiates my logger wrapper class. However, my main instantiates other classes and I want those other classes to also be able to write to he log file via the logger object in the main.

How do I make that logger object such that it can be called by other classes? It's almost like we need some sort of static logger object to get this to work.

I guess the long and short of the question is: how do you implement logging within your code structure such that all classes instantiated from within main can write to the same log file? Do I just have to create a new logging object in each of the classes that points to the same file?

gonz
  • 5,226
  • 5
  • 39
  • 54
GregH
  • 12,278
  • 23
  • 73
  • 109

2 Answers2

34

I don't know what you mean by the Logging class - there's no such class in Python's built-in logging. You don't really need wrappers: here's an example of how to do logging from arbitrary classes that you write:

import logging

# This class could be imported from a utility module
class LogMixin(object):
    @property
    def logger(self):
        name = '.'.join([__name__, self.__class__.__name__])
        return logging.getLogger(name)


# This class is just there to show that you can use a mixin like LogMixin
class Base(object):
    pass

# This could be in a module separate from B
class A(Base, LogMixin):
    def __init__(self):
        # Example of logging from a method in one of your classes
        self.logger.debug('Hello from A')

# This could be in a module separate from A
class B(Base, LogMixin):
    def __init__(self):
        # Another example of logging from a method in one of your classes
        self.logger.debug('Hello from B')

def main():
    # Do some work to exercise logging
    a = A()
    b = B()
    with open('myapp.log') as f:
        print('Log file contents:')
        print(f.read())

if __name__ == '__main__':
    # Configure only in your main program clause
    logging.basicConfig(level=logging.DEBUG,
                        filename='myapp.log', filemode='w',
                        format='%(name)s %(levelname)s %(message)s')
    main()

Generally it's not necessary to have loggers at class level: in Python, unlike say Java, the unit of program (de)composition is the module. However, nothing stops you from doing it, as I've shown above. The script, when run, displays:

Log file contents:
__main__.A DEBUG Hello from A
__main__.B DEBUG Hello from B

Note that code from both classes logged to the same file, myapp.log. This would have worked even with A and B in different modules.

Vinay Sajip
  • 95,872
  • 14
  • 179
  • 191
  • 1
    This is the most elegant solution I've seen for logging in Python. Thank you for sharing :-) – Daniel Quinn Sep 05 '16 at 10:44
  • 2
    `__name__` is not correct if using the mixin class in other module. – Saddle Point Feb 26 '19 at 06:04
  • What is reason of calling it `LogMixin` ? – Utsav Chokshi Mar 30 '20 at 10:19
  • @doer_uvc because it's a Python mixin related to logging. See https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful if you need info on what a mixin is. – Vinay Sajip Mar 30 '20 at 15:52
  • @VinaySajip: do you have an idea, how to adapt the code to log also from classmethods? – maxstrobel Jun 26 '20 at 12:38
  • @mx_muc just make the logger a class attribute, with changes as needed to make that work? – Vinay Sajip Jun 27 '20 at 18:11
  • @VinaySajip I tried to make it work as you proposed. However, I need always an instance of the object, which I might not have, when using a classmethod, to get its class name. `logger = logging.getLogger('.'.join([__name__, self.__class__.__name__]))` Do you have an idea, how to get the class name without an instance, and it should also work with inheritance? – maxstrobel Jun 29 '20 at 07:17
  • 1
    @mx_muc That would be best handled as another question, as it has nothing to do with logging. Comments aren't for supplemental questions and answers. – Vinay Sajip Jun 29 '20 at 19:06
7

Try using logging.getLogger() to get your logging object instance:

http://docs.python.org/3/library/logging.html#logging.getLogger

All calls to this function with a given name return the same logger instance. This means that logger instances never need to be passed between different parts of an application.

UPDATE:

The recommended way to do this is to use the getLogger() function and configure it (setting a handler, formatter, etc...):

# main.py
import logging
import lib


def main():
    logger = logging.getLogger('custom_logger')
    logger.setLevel(logging.INFO)
    logger.addHandler(logging.FileHandler('test.log'))
    logger.info('logged from main module')
    lib.log()

if __name__ == '__main__':
    main()

# lib.py
import logging


def log():
    logger = logging.getLogger('custom_logger')
    logger.info('logged from lib module')

If you really need to extend the logger class take a look at logging.setLoggerClass(klass)

UPDATE 2:

Example on how to add a custom logging level without changing the Logging class:

# main.py
import logging
import lib


# Extend Logger class
CUSTOM_LEVEL_NUM = 9
logging.addLevelName(CUSTOM_LEVEL_NUM, 'CUSTOM')
def custom(self, msg, *args, **kwargs):
    self._log(CUSTOM_LEVEL_NUM, msg, args, **kwargs)
logging.Logger.custom = custom

# Do global logger instance setup
logger = logging.getLogger('custom_logger')
logger.setLevel(logging.INFO)
logger.addHandler(logging.FileHandler('test.log'))


def main():
    logger = logging.getLogger('custom_logger')
    logger.custom('logged from main module')
    lib.log()

if __name__ == '__main__':
    main()

Note that adding custom level is not recommended: http://docs.python.org/2/howto/logging.html#custom-levels

Defining a custom handler and maybe using more than one logger may do the trick for your other requirement: optional output to stderr.

gonz
  • 5,226
  • 5
  • 39
  • 54
  • Sorry for being a little dense here....so then If I have a main module that instantiates my own Log class (which contains the logging class) then it is certainly easy to log things by simply calling my Log object. However, if my main imports other modules, how do a make a call to my Log class methods to log without passing a Log handle to every method? Can you provide an example using multiple modules? – GregH Apr 03 '13 at 14:36
  • I have provided an example using multiple modules and the getLogger() function. It's rare to have to extend the Logger (logging.getLoggerClass()) class, if you really think creating a logger with getLogger and configuring a custom Handler, Formatter it's not enough for your needs please explain what's the rationale behind your custom Logger class. – gonz Apr 03 '13 at 18:55
  • @GregH Did my example make sense? Let me know if that solves your issue or you need further explanation. – gonz Apr 04 '13 at 02:38
  • I guess I could not abstract it further. One thing I have is my own custom logging levels...didn't want to use the pre-defined levels (WARNING, etc.) In addition I am doing other things like having a custom "write" method that allows the caller to determine if they want the message to be output to stderr. However, your writeup was helpful. Thanks. – GregH Apr 04 '13 at 06:23
  • @GregH: I've update my answer with an alternative solution for that. – gonz Apr 04 '13 at 14:49
  • This is a much better answer than the other one, fitting more to the request/question in the OP, imo. Thanks for sharing :) – TAH Mar 07 '23 at 09:47