Delgan / loguru

Python logging made (stupidly) simple
MIT License
19.76k stars 695 forks source link

Modifying or passing some argument to `logger.catch` to *only* log the exception message, rather than the full traceback? #1220

Open svioletg opened 6 days ago

svioletg commented 6 days ago

My searching through the docs and issues didn't lead me to much, but it's always possible I've just missed something, so I apologize in advance if that has been asked before. In most cases I have no issue with having the logger.catch decorator print out a full traceback, but right now I'm working on a GUI version of a script I'm writing, and within the GUI there's a "console window" which I have another log handler (in addition to the previous two sys.stdout and log file handlers already present) set up to route log messages to a callback that tacks on the message to this area, so relevant messages can be displayed in the app itself.

Tracebacks for this project can get particularly large since there's a number of things working in tandem here, and for that handler specifically I wanted to see if there was possibly some way to have logger.catch still log the full trace as normal to stdout and the current log file, but for that GUI-related hander, I'd prefer it only showing the final exception message.

For example, as it is now:

ERROR: An error has been caught in function 'run', process 'MainProcess' (38996), thread 'Thread-2 (create_image)' (59604):
Traceback (most recent call last):
  File "C:\Users\Violet\Documents\Code\Python\py-squaremap-combiner\.venv\Lib\site-packages\PIL\JpegImagePlugin.py", line 639, in _save
    rawmode = RAWMODE[im.mode]
KeyError: 'RGBA'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\Violet\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1030, in _bootstrap
    self._bootstrap_inner()
  File "C:\Users\Violet\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1073, in _bootstrap_inner
    self.run()
> File "C:\Users\Violet\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\Violet\Documents\Code\Python\py-squaremap-combiner\src\squaremap_combine\gui\actions.py", line 182, in create_image
    result.img.save(out_file)
  File "C:\Users\Violet\Documents\Code\Python\py-squaremap-combiner\.venv\Lib\site-packages\PIL\Image.py", line 2568, in save
    save_handler(self, fp, filename)
  File "C:\Users\Violet\Documents\Code\Python\py-squaremap-combiner\.venv\Lib\site-packages\PIL\JpegImagePlugin.py", line 642, in _save
    raise OSError(msg) from e
OSError: cannot write mode RGBA as JPEG

...versus what my goal would be:

ERROR: OSError: cannot write mode RGBA as JPEG

Is this something I'd be able to set up either in the handler itself, or some argument I can pass to catch()? For reference, my handlers are being added like so:

stdout handler:

stdout_handler = logger.add(sys.stdout, colorize=True, format="<level>[{time:HH:mm:ss}] {level}: {message}</level>", level=stdout_level, diagnose=False)

Log file handler:

file_handler = logger.add(LOGS_DIR / '{time:YYYY-MM-DD_HH-mm-ss}.log', format="[{time:HH:mm:ss}] {level}: {message}", level='DEBUG', mode='w', retention=5, diagnose=False)

GUI "console" updater handler:

logger.add(actions.update_console, format='<level>{level}: {message}</level>', level='GUI_COMMAND')
sillydan1 commented 2 days ago

I found that you can do something along the lines of:

def handler(e: BaseException):
    logger.error(e)
    sys.exit(1)

@logger.catch(level="DEBUG", onerror=handler)
def main():
    a = 1 / 0

That way, if you set the verbosity level of your logger to DEBUG or more, it will print the stacktrace as a debug message and the error message is logged as an error (assuming you dont raise any errors before setting the verbosity level).

Delgan commented 12 hours ago

Although you can reduce the verbosity of traceback formatting with diagnose=False and backtrace=False, you can also disable it entirely. To do this, you need to remove the “{exception}” field from the format used by your handler.

For convenience, "\n{exception}" is automatically added at the end of the format string-parameter by default. In your case, you have to use a custom formatter function to prevent that. In this function, you can replace the "{exception}" with a custom field, such as the error message alone.

For example:

from loguru import logger
import sys

def formatter(record):
    base = "{time} {level} {message}\n"
    if record["exception"] is not None:
        error_type, error_message, _ = record["exception"]
        record["extra"]["error"] = f"{error_type.__name__}: {error_message}"
        base += "{extra[error]}\n"
    return base

@logger.catch
def main():
    logger.remove()
    logger.add(sys.stderr)  # Full traceback
    logger.add("file.log", format=formatter)  # Only error message

    logger.info("Hello")

    1 / 0  # Oops!

if __name__ == "__main__":
    main()

The suggestion from @sillydan1 is another elegant solution.