How to extend the logger.Logging Class?
<p>I would like to start with a basic logging class that inherits from Python's <code>logging.Logger</code> class. However, I am not sure about how I should be constructing my class so that I can establish the basics needed for customising the inherited logger.</p>

<p>This is what I have so far in my <code>logger.py</code> file:</p>

<pre><code>import sys
import logging
from logging import DEBUG, INFO, ERROR

class MyLogger(object):
    def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO):
        # Initial construct.
        self.format = format
        self.level = level
        self.name = name

        # Logger configuration.
        self.console_formatter = logging.Formatter(self.format)
        self.console_logger = logging.StreamHandler(sys.stdout)
        self.console_logger.setFormatter(self.console_formatter)

        # Complete logging config.
        self.logger = logging.getLogger("myApp")
        self.logger.setLevel(self.level)
        self.logger.addHandler(self.console_logger)

    def info(self, msg, extra=None):
        self.logger.info(msg, extra=extra)

    def error(self, msg, extra=None):
        self.logger.error(msg, extra=extra)

    def debug(self, msg, extra=None):
        self.logger.debug(msg, extra=extra)

    def warn(self, msg, extra=None):
        self.logger.warn(msg, extra=extra)
</code></pre>

<p>This is the main <code>myApp.py</code>:</p>

<pre><code>import entity
from core import MyLogger

my_logger = MyLogger("myApp")

def cmd():
    my_logger.info("Hello from %s!" % ("__CMD"))

entity.third_party()
entity.another_function()
cmd()
</code></pre>

<p>And this is the <code>entity.py</code> module:</p>

<pre><code># Local modules
from core import MyLogger

# Global modules
import logging
from logging import DEBUG, INFO, ERROR, CRITICAL

my_logger = MyLogger("myApp.entity", level=DEBUG)

def third_party():
    my_logger.info("Initial message from: %s!" % ("__THIRD_PARTY"))

def another_function():
    my_logger.warn("Message from: %s" % ("__ANOTHER_FUNCTION"))
</code></pre>

<p>When I run the main app, I get this:</p>

<pre><code>2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY!
2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY!
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION
2016-09-14 12:40:50,445 | INFO | Hello from __CMD!
2016-09-14 12:40:50,445 | INFO | Hello from __CMD!
</code></pre>

<p>Everything is printed twice, as probably I have failed to set the logger class properly.</p>

<p>--- <strong>UPDATE (01): Clarifying My Goals</strong> ---</p>

<p><strong>(1)</strong> I would like to encapsulate the main logging functionality in one single location so I can do this:</p>

<pre><code> from mylogger import MyLogger
 my_logger = MyLogger("myApp")
 my_logger.info("Hello from %s!" % ("__CMD"))
</code></pre>

<p><strong>(2)</strong> I am planning to use <code>CustomFormatter</code> and <code>CustomAdapter</code> classes. This bit does not require a custom logging class, those can be plugged in straight away.</p>

<p><strong>(3)</strong> I probably do not need to go very deep in terms of customisation of the underlying logger class (records etc.), intercepting <code>logger.info</code>, <code>loggin.debug</code> etc. should be enough.</p>

<p>So referring back to <a href="http://code.activestate.com/recipes/474089-extending-the-logging-module/" rel="nofollow">this python receipt</a> that has been circulated many times on these forums:</p>

<p>I am trying to the find the sweet point between having a <code>Logger Class</code>, yet still be able to use the built in functions like assigning <code>Formatters</code> and <code>Adapters</code> etc. So everything stays compatible with the <code>logging</code> module.</p>

<pre><code>class OurLogger(logging.getLoggerClass()):
    def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
        # Don't pass all makeRecord args to OurLogRecord bc it doesn't expect "extra"
        rv = OurLogRecord(name, level, fn, lno, msg, args, exc_info, func)
        # Handle the new extra parameter.
        # This if block was copied from Logger.makeRecord
        if extra:
            for key in extra:
                if (key in ["message", "asctime"]) or (key in rv.__dict__):
                    raise KeyError("Attempt to overwrite %r in LogRecord" % key)
                rv.__dict__[key] = extra[key]
        return rv
</code></pre>

<p>--- <strong>UPDATE (02): A work-in-progress possible solution</strong> ---</p>

<p>I have created a repo with a simple python app demonstrating a possible solution. Please feel free to peak and help me improve this.</p>

<p><a href="https://github.com/symbolix/xlog_example" rel="nofollow">xlog_example</a></p>

<p>This example effectively demonstrates the technique of overriding the <code>logging.Logger</code> class and the <code>logging.LogRecord</code> class through inheritance.</p>

<p>Two external items are mixed into the log stream: <code>funcname</code> and <code>username</code> without using any <code>Formatters</code> or <code>Adapters</code>.</p>

<p>This line</p>

<pre><code>self.logger = logging.getLogger("myApp")
</code></pre>

<p>always retrieves a reference to the same logger, so you are adding an additional handler to it every time you instantiate <code>MyLogger</code>. The following would fix your current instance, since you call <code>MyLogger</code> with a different argument both times.</p>

<pre><code>self.logger = logging.getLogger(name)
</code></pre>

<p>but note that you will still have the same problem if you pass the same <code>name</code> argument more than once.</p>

<p>What your class needs to do is keep track of which loggers it has already configured.</p>

<pre><code>class MyLogger(object):
    loggers = set()
    def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO):
        # Initial construct.
        self.format = format
        self.level = level
        self.name = name

        # Logger configuration.
        self.console_formatter = logging.Formatter(self.format)
        self.console_logger = logging.StreamHandler(sys.stdout)
        self.console_logger.setFormatter(self.console_formatter)

        # Complete logging config.
        self.logger = logging.getLogger(name)
        if name not in self.loggers:
            self.loggers.add(name)
            self.logger.setLevel(self.level)
            self.logger.addHandler(self.console_logger)
</code></pre>

<p>This doesn't allow you to re-configure a logger at all, but I leave it as an exercise to figure out how to do that properly.</p>

<p>The key thing to note, though, is that you can't have two separately configured loggers with the same name.</p>

<hr>

<p>Of course, the fact that <code>logging.getLogger</code> always returns a reference to the same object for a given name means that your class is working at odds with the <code>logging</code> module. Just configure your loggers once at program start-up, then get references as necessary with <code>getLogger</code>.</p>

python logging