rsalmei/alive-progress

Usage with loguru

nzqo opened this issue · 8 comments

nzqo commented

Hi!

I really like alive-progress, but I can't seem to get it to work with loguru without breaking formatting. Is there a method to hook similar to what is already done for the standard logging module, but maybe enable that for third-party logging libraries as well?

Just as a showcase of how logging output is broken when done concurrently to using a bar:

|██████████████████████▌                 | ▄▂▂ 282/500 [56%] in 2s (~1s, 150.4/s) 2023-11-08 09:47:14.490 | DEBUG    | __main__:<module>:10 - Happy logging 282
2023-11-08 09:47:14.497 | DEBUG    | __main__:<module>:10 - Happy logging 283
2023-11-08 09:47:14.503 | DEBUG    | __main__:<module>:10 - Happy logging 284
2023-11-08 09:47:14.509 | DEBUG    | __main__:<module>:10 - Happy logging 285
2023-11-08 09:47:14.516 | DEBUG    | __main__:<module>:10 - Happy logging 286
2023-11-08 09:47:14.523 | DEBUG    | __main__:<module>:10 - Happy logging 287
2023-11-08 09:47:14.530 | DEBUG    | __main__:<module>:10 - Happy logging 288
2023-11-08 09:47:14.536 | DEBUG    | __main__:<module>:10 - Happy logging 289
|███████████████████████▎                | ▃▁▃ 290/500 [58%] in 2s (~1s, 150.4/s) 2023-11-08 09:47:14.543 | DEBUG    | __main__:<module>:10 - Happy logging 290

Hi @nzqo,

Please, give me some test code so I can run it here.

nzqo commented

Sure, here you go:

from loguru import logger
from alive_progress import alive_bar
import time

with alive_bar(500) as bar:
   for i in range(500):
       time.sleep(.005)
       logger.debug("uh oh")
       bar()

Okay, I got it.
It seems loguru does things very weirdly: it 1. initializes logger on import time, 2. captures the real stdout within it, and 3. it does not use logging.getLogger()!!
This means alive-progress, when it gets to initialize the print and logging hooks, cannot find loguru's logging handler since it is not registered in Python's logging...

Look what happens if I import it only within alive-progress's context manager (it works!):

Screen.Recording.2023-11-09.at.01.01.21.mov

I'm not sure yet how to proceed, gonna think about it.

After some research, it seems I can't do anything to properly support it. According to Delgan/loguru#376, the maintainers do not want to expose the internal handlers, so I can't fetch and instrument them...

What you have to do in case you can't avoid importing loguru's logger at the top, is to reinitialize it by removing previous handlers and reinserting them (the default one plus any others you're using). When the alive-progress' context finishes, you have to reinitialize loguru's handlers again.
This works:

Screen.Recording.2023-11-09.at.01.47.16.mov

I've just realized that, in the first video, all indexes were outputted as 500 😂
It did work, I assure you. But when we try it the second time onward it doesn't, loguru maintains cached the instrumented stderr from the first alive_bar!!
So, even in that case, you would have to reinitialize handlers, exactly as the above...

image

So, here is the code to reinitialize loguru's handlers:

def reinitialize_loguru():
    logger.remove()
    logger.add(sys.stderr)

Then just call it within and after alive_bar's context manager:

with alive_bar(500) as bar:
    reinitialize_loguru()
    for i in range(500)
        ...

reinitialize_loguru()

I'm afraid it's the best we can do.

I'm closing this one, but feel free to continue if you want.

nzqo commented

So, here is the code to reinitialize loguru's handlers:

def reinitialize_loguru():
    logger.remove()
    logger.add(sys.stderr)

Then just call it within and after alive_bar's context manager:

with alive_bar(500) as bar:
    reinitialize_loguru()
    for i in range(500)
        ...

reinitialize_loguru()

I'm afraid it's the best we can do.

I'm closing this one, but feel free to continue if you want.

thanks for looking into this, a pity there wasnt a simple way to do this 😢
Reinitializing could have unwanted side effects (in the case of developing a library, this would also remove the user-added handlers), I'm not sure I can use that. There seems to be some guidance on using loguru with tqdm, but no idea if there is something fundamentally different at play there

You're welcome!

tqdm does not generate and install logging hooks like alive-progress do, and even using an internal tqdm.write() func they got similar problems.
It seems they had to remove all handlers, add another one writing into that internal func, and certainly reinitialize everything after the bar is gone, exactly like I did here.

Unfortunately, there's nothing I can do, it's the poor loguru's API fault.

EDIT: When I say "reinitialize" I mean "set it like the first time", thus you should include again exactly the same user-added handlers.

EDIT 2: Actually, we just need to update stderr! It is the handler zero initially, but we can't set it to anything else, nor remove it and re-insert it into the same position, thus breaking the second iteration onwards. And if it changed the position, we can't even list them all to try to find where it ended up! Poor API... 🤦‍♂️

EDIT 3: Perhaps you could remove the handler zero right at the beginning! If you do it before adding the user ones, you'll be able to remove only the last handler, and re-add it into the exact same position. That should make it easier I think 😜