22

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.

tdihp
  • 2,329
  • 2
  • 23
  • 40
iTayb
  • 12,373
  • 24
  • 81
  • 135
  • Not sure how this would work in your situation, but could you lead the function with a check if the logger exists, and if not, create a logger of the same name (`logger`, it seems) and route the output to `os.devnul`? – RocketDonkey Nov 23 '12 at 01:14
  • @iTayb Hi, you have a rather simple description of what you want (to have a dummy logger). but python's logging usually not used in this way as far as I know. As you can achieve logging filtering use logger's name, May I ask why (in what situation) you still need this kind of workaround? – tdihp Nov 23 '12 at 13:41
  • 1
    @tdihp I want to develop a code piece, that may log it's activity to a provided logger. This code must be portable. It (hopefully) will be implemented in many different projects, and I'm not providing the logging environment. It's up to the developer to decide if he wants to log the function's activity or not. I'm just giving him the option. – iTayb Nov 23 '12 at 14:03
  • 1
    @iTayb In famous Django project(and many others), they actually assign a unique logger name and just use them. then the user's option will be to turn on specified logger name in logging config and job is done. this is the easier and recommended way of logging filtering. – tdihp Nov 24 '12 at 00:28
  • 1
    @tdihp Thanks, I understood the concept. However this code piece is 20 lines long and I don't think it'll worth the usage of a unique logger just for it. In bigger projects I'll have that in mind. – iTayb Nov 24 '12 at 01:59

7 Answers7

19

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")
crypdick
  • 16,152
  • 7
  • 51
  • 74
iTayb
  • 12,373
  • 24
  • 81
  • 135
14

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.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
fcs
  • 141
  • 1
  • 2
4

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.

Community
  • 1
  • 1
BoppreH
  • 8,014
  • 4
  • 34
  • 71
  • That's good for projects. All I need here is a very basic function, with an optional logging, where the logger can be passed as an argument. – iTayb Nov 23 '12 at 01:27
  • Why not use the code you posted? You just need to create a very simple class for the logger, or even just a function to print what's passed in the parameters. – BoppreH Nov 23 '12 at 01:28
  • I wondered if there's an object in python that will allow writing to, even though it does not exist, and supports subclassing (like calling class1.class2.func(a, b, c) and nothing will happen). – iTayb Nov 23 '12 at 01:34
4

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).

cicolus
  • 697
  • 5
  • 16
2

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.

tdihp
  • 2,329
  • 2
  • 23
  • 40
1

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

logger = Mock()
Viatorus
  • 1,804
  • 1
  • 18
  • 41
0

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)
Finncent Price
  • 543
  • 1
  • 8
  • 20