Repository: pypa/pip

Issue:
Leaking logging.root.handlers
Hi,

I have the following use case: I am using a standalone Python script starting like this:

```
import pip
pip.main(['install', 'requests=1.3.3'])

import logging, requests
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
```

Now, this last line fail because there are already some `logging.root.handlers` defined.

The code creating those handlers is here:
https://github.com/pypa/pip/blob/master/pip/basecommand.py#L160

To clean-up those loggers after pip ran, I'd suggest to add a `finally` clause to the following `try`/`except` block in that code, that would simply: `logging.root.handlers = []`.

What do you think ?
I can send a pull request with tests if you agree with this proposal.

Trivia: I've actually traced (with git bisect) this issue of "leaking logging.root.handlers" back to commit 767d11e first released in 6.0 last December, when the switch from pip.log to the stdlib logger happened.



Discussion:
Anybody had time to take a look at this ?

Just a note that this is probably because pip does not offer any official API (there were many requests for this but nothing is done yet) so pip does not take into account being run from within some other Python script. That being said it seems restoring (which I think would be better than clearing which you suggested) root's handlers (is this the only thing to restore in logging state?) is not hard to do and could be easily added to pip.

I'm also suffering from this problem. When I use pip in my code it breaks my root logging.

**Example**

``` python
import logging
import pip

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

logging.debug("Test")
# prints 'DEBUG:root:Test'

pip.main(["install", "requests"])
# prints pip output

logging.debug("Test")
# doesn't print anything
```

As noted, you should use `subprocess.Popen` to call pip. Having said that, I don't believe there would be any major problem with someone raising a PR that addressed this issue, it's just not a priority for the core devs as this is not supported usage.

Here is a PR that fixes the issue I think.

I'm going to close this, not because I don't think this issue happens but because pip does not have a public API so this is not technically supported so I don't feel there is value in holding open an issue to track this. However a PR that implements this would be accepted (and it looks like maybe @Lucas-C is working on one, but I don't see a PR number, but I'll be going through PRs at some point too!).

Resolving patch:
diff --git a/pip/basecommand.py b/pip/basecommand.py
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -210,6 +210,8 @@ def main(self, args):
                 )
                 sys.exit(VIRTUALENV_NOT_FOUND)
 
+        original_root_handlers = set(logging.root.handlers)
+
         try:
             status = self.run(options, args)
             # FIXME: all commands should return an exit status
@@ -249,6 +251,10 @@ def main(self, args):
                         retries=0,
                         timeout=min(5, options.timeout)) as session:
                     pip_version_check(session, options)
+            # Avoid leaking loggers
+            for handler in set(logging.root.handlers) - original_root_handlers:
+                # this method benefit from the Logger class internal lock
+                logging.root.removeHandler(handler)
 
         return SUCCESS
 


Test patch: