Implementing an optional logger in code

Question:

I’d like to implement an optional logger in a function. Something like:

def foo(arg1, arg2, arg3, logger=None):
    logger = logger or (lambda *x: None)

    ...
    self.logger.debug("The connection is lost.")

I want the logging to happen in case a logger exists. Otherwise, the logger’s debugging won’t do a thing.

Basically the easy way to achieve it is to nest every debug statement in an if logger block, but it seems messy when there are many debug statements.

Asked By: iTayb

||

Answers:

Well, that’s what the logging module is for. How to use, Cookbook.

If you really want to roll your own, I see a few alternatives:

  • self.logger attribute. Set when constructing the object or inherited from a base class. Each object has its own logger, so you can have selective logging per instance.

  • Logger class with static methods or standalone module. Could have default methods that do nothing, but the user is free to replace them with real handlers whenever the need arises. All classes access the same object or module. Lose granularity, but less work to set up.

  • Decorators. Put a @log('message', LEVEL) above each method you want to be logged and this will automatically call the log when the method is invoked. Considerably cleaner, less flexible.

Answered By: BoppreH

I think what you want is logging filtering,
so my answer is about how to simply achieve logging filtering.

Python’s logging package already does this, you have many ways to do logging filtering.

Two basic ways are:

  • logging level filtering
  • logger name filtering

Both of them uses logging’s config so it can be easily configured.

For example:

import logging

logging.basicConfig()  # easily setup a StreamHandler output

logging.getLogger("realm1").setLevel(logging.WARNING)
logging.getLogger("realm2").setLevel(logging.INFO)

def test():
    r1logger = logging.getLogger("realm1")
    r2logger = logging.getLogger("realm2")
    r1logger.info('r1 info')  # won't print
    r2logger.info('r2 info')  # will print

if __name__ == '__main__':
    test()

So unless you needs run-time dynamic local changes of logging policy,
use default logger with careful logging config would be enough.

Answered By: tdihp

Few options:

Create a dummy logger (my favorite):

logger = logger or logging.getLogger('dummy') #  without configuring dummy before.

Create a dummy object with one level null effect:

class DummyObject(object):
    def __getattr__(self, name):
        return lambda *args, **kwargs: None

logger = logger or DummyObject()

Nesting every debug statement in a block:

if logger:
    logger.debug("abc")
Answered By: iTayb

A do-nothing NullHandler is included in the logging module since Python 2.7:

import logging      
logging.getLogger('foo').addHandler(logging.NullHandler())

See the docs for configuring logging for a library.

Answered By: fcs

What you could also do and it is shorter then setup a real logger with a NullHander:

logger = Mock()
Answered By: Viatorus

By default the class used to construct a new logger when logging.getLogger is invoked is logging.Logger, which will by default set the propagate attribute to True (documentation source). According to the documentation,

If this attribute [propagate] evaluates to true, events logged to this logger will be passed to the handlers of higher level (ancestor) loggers, in addition to any handlers attached to this logger.

So the answer by @fcs will not work as long as one of the logger’s parent has some nontrivial handler, which is most likely the case since the root logger is all logger’s parent and it pretty much always has StreamHandler with the stderr stream.

The following simple fix will work:

import logging
null_logger = logging.getLogger('foo')
null_logger.addHandler(logging.NullHandler())  # read below for reason
null_logger.propagate = False
null_logger.error("error")  # This message will still go nowhere

Note that adding the logging.NullHandler is necessary since if a logging record is not handled by any handler, it will be handled by logging.lastResort which will still emit this message to stderr (behavior since Python 3.2).

Answered By: cicolus

When I want a message to be logged if there is a logger, I make custom function to do the logger checking. Otherwise, I print the message, but you can just remove the else clause and it will do nothing if there is no logger present.

def emit_message(message: str, level: Optional[str] = None) -> None:
    allowed_levels = ['debug', 'info', 'warning', 'error', 'critical']
    if logging.getLogger().hasHandlers() and level in allowed_levels:
        method = getattr(logging, level)
        method(message)
    else:
        print(message)
Answered By: Finncent Price
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.