Just to add an alternative implementation I went for.
The following is redirecting the exception output to a logger. In my case the logger was set to stdout (default) so I used contextlib.redirect_stdout to redirect the logger's output to a file, but you could of course directly make the logger write to a file.
import logging
import contextlib
from typing import Iterator
@contextlib.contextmanager
def capture_exception(logger: logging.Logger) -> Iterator[None]:
    """
    Captures exceptions and redirects output to the logger
    >>> import logging
    >>> logger = logging.getLogger()
    >>> with capture_exception(logger=logger):
    >>>     raise Exception("This should be outputed to the logger")
    """
    try:
        # try/except block where exception is captured and logged
        try:
            yield None
        except Exception as e:
            logger.exception(e)
    finally:
        pass
Usage:
logger = logging.getLogger()   # default should be stdout
with open("test.txt", "a") as f:
    with contextlib.redirect_stdout(f), capture_exception(logger):
        # ^-- multiple context managers in one `with` statement
        raise Exception("The output of this exception will appear in the log. Great success.")
Exception output as it appears in the log:
The output of this exception will appear in the log. Great success.
Traceback (most recent call last):
  File "/tmp/ipykernel_9802/2529855525.py", line 52, in capture_exception
    yield None
  File "/tmp/ipykernel_9802/3344146337.py", line 9, in <module>
    raise Exception("The output of this exception will appear in the log. Great success.")
Exception: The output of this exception will appear in the log. Great success.