tartley / colorama

Simple cross-platform colored terminal text in Python
BSD 3-Clause "New" or "Revised" License
3.51k stars 249 forks source link

[BUG] error occurred when called with many times #351

Closed flt6 closed 1 year ago

flt6 commented 1 year ago

I build a example code like this.

from logging import Logger, Formatter, FileHandler
from logging import DEBUG
from colorama import Fore,init

class Log(Logger):
    def __init__(self, name) -> None:
        '''Provide log function'''
        super().__init__(name, DEBUG)
        init(autoreset=True)
        self.super = super()
        self.gen_handle()
        self.register_handles()

    def debug(self, text, *args, exc_info=None, **kwargs):
        if self.isEnabledFor(DEBUG):
            print(Fore.WHITE+text+ f"DEBUG: {str(text)}")
            self._log(DEBUG, text, args, exc_info=exc_info, **kwargs)

    def gen_handle(self) -> None:
        fmt = Formatter(
            "%(asctime)s - %(levelname)8s - %(name)s: %(message)s")

        self.debug_handle = FileHandler("logs/debug.log")
        self.debug_handle.setLevel(DEBUG)
        self.debug_handle.setFormatter(fmt)

    def register_handles(self) -> None:
        self.addHandler(self.debug_handle)

    def get_logger(self):
        return self

for i in range(10000):
    Log(str(i)).debug(str(i)*i)

After log for many times, an error occurred.

Traceback (most recent call last):
  File "E:\TEMP\git\git\Read_tts\interactive\test.py", line 37, in <module>
    Log(str(i)).debug(str(i)*i)
  File "E:\TEMP\git\git\Read_tts\interactive\test.py", line 18, in debug
    print(Fore.WHITE+text+ f"DEBUG: {str(text)}")
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 41, in write
    self.__convertor.write(text)
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 162, in write
    self.write_and_convert(text)
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 190, in write_and_convert
    self.write_plain_text(text, cursor, len(text))
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 195, in write_plain_text
    self.wrapped.write(text[start:end])
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 41, in write
    self.__convertor.write(text)

  <The last 4 lines repeated for many times>

  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 167, in write
    self.reset_all()
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 172, in reset_all
    self.call_win32('m', (0,))
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\ansitowin32.py", line 231, in call_win32
    func(*args, **kwargs)
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\winterm.py", line 46, in reset_all
    self.set_console(attrs=self._default)
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\winterm.py", line 83, in set_console
    win32.SetConsoleTextAttribute(handle, attrs)
  File "C:\Users\dicp\AppData\Roaming\Python\Python39\site-packages\colorama\win32.py", line 110, in SetConsoleTextAttribute
    handle = _GetStdHandle(stream_id)
ctypes.ArgumentError: argument 1: <class 'RecursionError'>: maximum recursion depth exceeded in __instancecheck__

This example means when overwrite Log of logging to support colorful print, call it for many times, this error occurred.

wiggin15 commented 1 year ago

Hi. I believe the problem here is you are calling colorama.init from Log's constructor, and then instantiating Log many times. This causes the stdout stream to be wrapped again and again, making the calls more and more recursive and slower, until you get the error. Here is a more minimal example that may reproduce the same behavior:

from colorama import init

class Log:
    def __init__(self) -> None:
        init(autoreset=True)
        print('Hello')

for i in range(10000):
    Log()

The solution would be to call colorama.init only once, outside the constructor.

B1NT0N commented 1 year ago

The above solution seams to work. Had the same problem, but now it's fixed. Thank you.

flt6 commented 1 year ago

Hi. I believe the problem here is you are calling colorama.init from Log's constructor, and then instantiating Log many times. This causes the stdout stream to be wrapped again and again, making the calls more and more recursive and slower, until you get the error. Here is a more minimal example that may reproduce the same behavior:

from colorama import init

class Log:
    def __init__(self) -> None:
        init(autoreset=True)
        print('Hello')

for i in range(10000):
    Log()

The solution would be to call colorama.init only once, outside the constructor.

This is just an example code just for showing the problem. However, I only called the constructor function of Log for once in the other program, but this error also occurred. Like this:

class ToServer:
    def __init__(self, optDir):
        self.logger = getLogger("ToServer")  # this is overwritten with my own `Log` class with colorful print
        self.logger.debug("Class 'ToServer' init successfully.")
        # ---------some other code------
    def _download(self, st, retry: set[Chapter], chapters, bar):
        for task, j in st:
            try:
                # -------Some Other Code----------
                if ret.reason != consts.TTS_SUC:
                    self.logger.debug("AsyncReq not success `get`")
                    self.logger.error(ret.reason)
                    self.logger.error(ret.cancellation_details)
                    self.logger.debug(chapters[j].idx)
wiggin15 commented 1 year ago

Hi @flt6 . I can't run your latest snippet to test. Can you try to get a minimal reproducible example? i.e. as few extra lines as possible, but with all required imports/definitions, so it can be run to show the problem, but without having to read unrelated code. The first example had the bug with multiple calls to init. I can't tell what the problem here from the code you posted because the given snippet doesn't reference colorama.

flt6 commented 1 year ago

Sorry, I reviewed all my code recently. I found some code called constructor for several times in a third party code.

I found maybe it is impossible to use logging with colorama for multiprocessing module which pickle is used but pickle is not available directly in logging. To deal with it, repeating calling constructor is needed ( By calling getLogger with Logger name for many times).