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.
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.
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.
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")
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.
What you could also do and it is shorter then setup a real logger with a NullHander:
logger = Mock()
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).
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)
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.
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.
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.
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")
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.
What you could also do and it is shorter then setup a real logger with a NullHander:
logger = Mock()
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).
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)