WoJ
WoJ

Reputation: 29957

How to silence the logging of a module?

Overview

I want to use httpimport as a logging library common to several scripts. This module generates logs of its own which I do not know how to silence.

In other cases such as this one, I would have used

logging.getLogger('httpimport').setLevel(logging.ERROR)

but it did not work.

Details

The following code is a stub of the "common logging code" mentioned above:

# toconsole.py

import logging
import os

log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(message)s')
handler_console = logging.StreamHandler()
level = logging.DEBUG if 'DEV' in os.environ else logging.INFO
handler_console.setLevel(level)
handler_console.setFormatter(formatter)
log.addHandler(handler_console)

# disable httpimport logging except for errors+
logging.getLogger('httpimport').setLevel(logging.ERROR)

A simple usage such as

import httpimport

httpimport.INSECURE = True
with httpimport.remote_repo(['githublogging'], 'http://localhost:8000/') :
    from toconsole import log

log.info('yay!')

gives the following output

[!] Using non HTTPS URLs ('http://localhost:8000//') can be a security hazard!
2019-08-25 13:56:48,671 yay!
yay!

The second (bare) yay! must be coming from httpimport, namely from its logging setup.

How can I disable the logging for such a module, or better - raise its level so that only errors+ are logged?


Note: this question was initially asked at the Issues section of the GitHub repository for httpimport but the author did not know either how to fix that.

Upvotes: 1

Views: 4239

Answers (2)

operatorequals
operatorequals

Reputation: 391

Author of httpimport here. I totally forgot I was using the basicConfig logger thing.

It is fixed in master right now (0.7.2) - will be included in next PyPI release: https://github.com/operatorequals/httpimport/commit/ff2896c8f666c3f16b0f27716c732d68be018ef7

Upvotes: 6

Marius Mucenicu
Marius Mucenicu

Reputation: 1783

The reason why this is happening is because when you do import httpimport they do the initial configuration for the logging machinery. This happens right here. What this means is that the root logger already has a StreamHandler attached to it. Because of this, and the fact that all loggers inherit from the root logger, when you do log.info('yay') it not only uses your Handler and Formatter, but it also propagates all they way to the root logger, which also emits the message.

Remember that whoever calls basicConfig first when an application starts that sets up the default configuration for the root logger, which in turn, is inherited by all loggers, unless otherwise specified.

If you have a complex logging configuration you need to ensure that you call it before you do any third-party imports which might call basicConfig. basicConfig is idempotent meaning the first call seals the deal, and subsequent calls have no effect.

Solutions

  1. You could do log.propagate = False and you will see that the 2nd yay will not show.
  2. You could attach the Formatter directly to the already existent root Handler by doing something like this (without adding another Handler yourself)
root = logging.getLogger('')
formatter = logging.Formatter('%(asctime)s %(message)s')
root_handler = root.handlers[0]
root_handler.setFormatter(formatter)
  1. You could do a basicConfig call when you initialize your application (if you had such a config available, with initial Formatters and Handlers, etc. that will elegantly attach everything to the root logger neatly) and then you would only do something like logger = logging.getLogger(__name__) and logger.info('some message') that would work the way you'd expect because it would propagate all the way to the root logger which already has your configuration.

  2. You could remove the initial Handler that's present on the root logger by doing something like

root = logging.getLogger('')
root.handlers = []

... and many more solutions, but you get the idea.

Also do note that logging.getLogger('httpimport').setLevel(logging.ERROR) this works perfectly fine. No messages below logging.ERROR would be logged by that logger, it's just that the problem wasn't from there.

If you however want to completely disable a logger you can just do logger.disabled = True (also do note, again, that the problem wasn't from the httpimport logger, as aforementioned)

One example demonstrated

Change your toconsole.py with this and you won't see the second yay.

import logging
import os

log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)

root_logger = logging.getLogger('')
root_handler = root_logger.handlers[0]
formatter = logging.Formatter('%(asctime)s %(message)s')
root_handler.setFormatter(formatter)

# or you could just keep your old code and just add log.propagate = False
# or any of the above solutions and it would work

logging.getLogger('httpimport').setLevel(logging.ERROR)

Upvotes: 4

Related Questions