Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: optionally redirect console logging to tqdm.write #786

Closed
de-code opened this issue Aug 2, 2019 · 4 comments · Fixed by #1143
Closed

Suggestion: optionally redirect console logging to tqdm.write #786

de-code opened this issue Aug 2, 2019 · 4 comments · Fixed by #1143
Assignees
Labels
c1-quick 🕐 Complexity low p3-enhancement 🔥 Much new such feature submodule ⊂ Periphery/subclasses to-merge ↰ Imminent
Projects
Milestone

Comments

@de-code
Copy link
Contributor

de-code commented Aug 2, 2019

This is somewhat related to #296, which is about stdout and stderr.

I believe the proposed example to redirect stdout and stderr doesn't work with logging, because it will already have saved the reference to stdout and stderr.

This seems to be a common "problem" with many related snippets. Rather than copy and pasting an example, it might be worth to have an option include with tqdm?

Here is one possible solution:

import logging
import sys
from contextlib import contextmanager
from typing import List

from tqdm import tqdm


class TqdmLoggingHandler(logging.StreamHandler):
    def emit(self, record):
        try:
            msg = self.format(record)
            tqdm.write(msg)
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:  # noqa pylint: disable=bare-except
            self.handleError(record)


def _is_console_logging_handler(handler: logging.Handler) -> bool:
    return isinstance(handler, logging.StreamHandler) and handler.stream in {sys.stdout, sys.stderr}


def _get_console_formatter(handlers: List[logging.Handler]) -> logging.Formatter:
    for handler in handlers:
        if _is_console_logging_handler(handler):
            return handler.formatter
    return None


@contextmanager
def redirect_logging_to_tqdm(logger: logging.Logger = None):
    if logger is None:
        logger = logging.root
    tqdm_handler = TqdmLoggingHandler()
    original_handlers = logger.handlers
    tqdm_handler.setFormatter(_get_console_formatter(original_handlers))
    try:
        logger.handlers = [
            handler
            for handler in logger.handlers
            if not _is_console_logging_handler(handler)
        ] + [tqdm_handler]
        yield
    finally:
        logger.handlers = original_handlers


@contextmanager
def tqdm_with_logging_redirect(*args, logger: logging.Logger = None, **kwargs):
    with tqdm(*args, **kwargs) as pbar:
        with redirect_logging_to_tqdm(logger=logger):
            yield pbar

And it could be used like this:

import logging
from <source package> import tqdm_with_logging_redirect

LOGGER = logging.getLogger(__name__)

if __name__ == '__main__':
    logging.basicConfig(level='INFO')

    file_list = ['file1', 'file2']
    with tqdm_with_logging_redirect(total=len(file_list)) as pbar:
        # logging to the console is now redirected to tqdm
        for filename in file_list:
            LOGGER.info('processing file: %s', filename)
            pbar.update(1)
    # logging is now restored

This is just an example.

(I could also provide tests for the above implementation if it's of any use)

@GiovanH
Copy link

GiovanH commented Aug 21, 2019

This is one of the biggest advantages progressbar2 still has over tqdm; the "canonical example" provided in the readme should really be a part of the library.

@ftrofin
Copy link

ftrofin commented Mar 31, 2021

I love this solution! This is the only solution that works in a realistic (complex) environment where you have multiple loggers, handlers, etc. The "canonical" example (https://pypi.org/project/tqdm/4.7.6/#redirecting-writing) doesn't work - output still gets jumbled.I agree that this code should be provided as part of tqdm library and used as the canonical example.
I think 99% of the developers who use tqdm in a realistic setup will run into this issue (output getting messed up by logging statements coming from other modules or other libraries (like "urllib3.connectionpool" logger when level is DEBUG).

@casperdcl casperdcl self-assigned this Apr 1, 2021
@casperdcl casperdcl added c1-quick 🕐 Complexity low p3-enhancement 🔥 Much new such feature submodule ⊂ Periphery/subclasses to-merge ↰ Imminent labels Apr 1, 2021
@casperdcl casperdcl added this to the Non-breaking milestone Apr 1, 2021
@casperdcl casperdcl added this to Next Release in Casper Apr 1, 2021
@casperdcl
Copy link
Sponsor Member

Thanks for bumping this. @de-code would you like to open a PR?

@de-code
Copy link
Contributor Author

de-code commented Apr 1, 2021

done (#1155)

casperdcl added a commit that referenced this issue Apr 5, 2021
Casper automation moved this from Next Release to Done Apr 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c1-quick 🕐 Complexity low p3-enhancement 🔥 Much new such feature submodule ⊂ Periphery/subclasses to-merge ↰ Imminent
Projects
Casper
  
Done
Development

Successfully merging a pull request may close this issue.

4 participants