Mayil-AI-Sandbox / loguru-Jan2023

MIT License
0 stars 0 forks source link

Is there a way to record errors to be logged later eg. on program exit? (hashtag590) #117

Closed vikramsubramanian closed 2 months ago

vikramsubramanian commented 2 months ago

Is there any built-in way to record some errors to be logged later eg. on program exit? My use case is that my program produces a lot of necessary log output and certain very important errors should be collected together and logged (again) at the end (making it quicker and easier to see what problems there were in GitHub Actions for example).

I don't mean putting around the whole program to only catch errors at the top level, but a way to do something like:

try:
   ...
except MyException:
   msg = "some error"
   logger.exception(msg)
   logger.record(msg)
...
try:
   ...
except AnotherException:
   logger.record("some other error", log_exception=True)
...
...
if logger.has_any_recorded():
    logger.output_all_recorded()
    sys.exit(1)

I have a class to do something like this that can be used in a with block, but then the object needs to be passed around to all things that need to log. It is not nearly as elegant as if there were functionality built into the logger.

class ErrorsOnExit:
    """Class that enables recording of errors with logging of those errors on exit"""

    def __init__(self) -> None:
        self.errors = list()

    def add(self, message: str) -> None:
        """Add error to be logged later

        Args:
            message (str): Error message

        Returns:
            None
        """
        self.errors.append(message)

    def log(self) -> None:
        """Log errors

        Returns:
            None
        """
        for error in self.errors:
            logger.error(error)

    def exit_on_error(self) -> None:
        """Exit with a 1 code if there are errors

        Returns:
            None
        """
        if self.errors:
            sys.exit(1)

    def log_exit_on_error(self) -> None:
        """Log errors and exit with a 1 code if there are errors

        Returns:
            None
        """
        self.log()
        self.exit_on_error()

    def __enter__(self) -> "ErrorsOnExit":
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        self.log_exit_on_error()
vikramsubramanian commented 2 months ago

Hi Would a custom sink do the trick?

from loguru import logger

class PrintErrorsOnExit:
    def __init__(self):
        self._errors = []

    def write(self, message):
        self._errors.append(message)

    def stop(self):
        print("=== Summary of errors ===")
        for error in self._errors:
            print(error, end="")

if __name__ == "__main__":
    logger.add(PrintErrorsOnExit(), level="ERROR", colorize=True)
    logger.error("Problem")
    logger.info("Ok")
    logger.info("Ok again")
    logger.success("Program exit")

The downside is that it doesn't propagate errors to existing handlers but only print() them.

Your implementation using a context manager is nice but it's too specific for me to integrate it as a built-in. You can maybe try to add an extra method to logger.__class__ to avoid having to pass it around (making it directly accessible through the logger instance).

vikramsubramanian commented 2 months ago

Thanks for your reply and also for a great utility. Do you mean subclass logaru's logger and add a method?

vikramsubramanian commented 2 months ago

No I was thinking to modifying logger.__class__ directly to add a custom method (because Logger isn't meant to be subclassed):

logger.__class__.errors_on_exit = ErrorsOnExit()

with logger.errors_on_exit as errors:
    errors.add("Some error")
    logger.info("Done")
vikramsubramanian commented 2 months ago

Thanks, closing this as I can add the ErrorsOnExit to the logging module (loguru captures the standard logger in my setup).

    def __enter__(self) -> "ErrorsOnExit":
        logging.errors_on_exit = self
        return self

Then use:

logging.errors_on_exit.add("error 1")