diff --git a/loguru/_handler.py b/loguru/_handler.py index 5afd66ef..24b8bb41 100644 --- a/loguru/_handler.py +++ b/loguru/_handler.py @@ -93,12 +93,12 @@ def emit(self, record, level_color, ansi_message, raw): formatter_record = record.copy() - if not record["exception"]: - error = "" - else: + if record["exception"] and self._exception_formatter: type_, value, tb = record["exception"] lines = self._exception_formatter.format_exception(type_, value, tb) error = "".join(lines) + else: + error = "" formatter_record["exception"] = error diff --git a/loguru/_logger.py b/loguru/_logger.py index d93c36b4..38d47711 100644 --- a/loguru/_logger.py +++ b/loguru/_logger.py @@ -32,6 +32,8 @@ start_time = now() +UNSET = object() + class Logger: """An object to dispatch logging messages to configured handlers. @@ -163,6 +165,7 @@ def add( diagnose=_defaults.LOGURU_DIAGNOSE, enqueue=_defaults.LOGURU_ENQUEUE, catch=_defaults.LOGURU_CATCH, + exception_formatter=UNSET, **kwargs ): r"""Add a handler sending log messages to a sink adequately configured. @@ -201,6 +204,8 @@ def add( Whether or not errors occurring while sink handles logs messages should be caught or not. If ``True``, an exception message is displayed on |sys.stderr| but the exception is not propagated to the caller, preventing your app to crash. + exception_formatter : |ExceptionFormatter|, optional + By default ``loguru._better_exceptions.ExceptionFormatter`` is used. **kwargs Additional parameters that will be passed to the sink while creating it or while logging messages (the exact behavior depends on the sink type). @@ -738,18 +743,6 @@ def filter_func(r): "Invalid level value, it should be a positive integer, not: %d" % levelno ) - if isinstance(format, str): - formatter = format + "\n{exception}" - is_formatter_dynamic = False - elif callable(format): - formatter = format - is_formatter_dynamic = True - else: - raise ValueError( - "Invalid format, it should be a string or a function, not: '%s'" - % type(format).__name__ - ) - try: encoding = sink.encoding except AttributeError: @@ -758,10 +751,7 @@ def filter_func(r): if not encoding: encoding = "ascii" - with self._lock: - handler_id = next(self._handlers_count) - colors = [lvl.color for lvl in self._levels.values()] + [""] - + if exception_formatter is UNSET: exception_formatter = ExceptionFormatter( colorize=colorize, encoding=encoding, @@ -770,6 +760,24 @@ def filter_func(r): hidden_frames_filename=self.catch.__code__.co_filename, ) + if isinstance(format, str): + formatter = format + if exception_formatter: + formatter += "\n{exception}" + is_formatter_dynamic = False + elif callable(format): + formatter = format + is_formatter_dynamic = True + else: + raise ValueError( + "Invalid format, it should be a string or a function, not: '%s'" + % type(format).__name__ + ) + + with self._lock: + handler_id = next(self._handlers_count) + colors = [lvl.color for lvl in self._levels.values()] + [""] + handler = Handler( writer=writer, stopper=stopper, diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 59eb8096..f3a78f13 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -121,3 +121,20 @@ def test_extra_formatting(writer): def test_invalid_color_markup(writer): with pytest.raises(AnsiMarkupError): logger.add(writer, format="Not closed tag", colorize=True) + + +def test_handler_with_no_exception_formatter(): + class Handler: + def write(self, message): + assert message == "got_exception" + self.passed_write = True + + handler = Handler() + logger.add(handler, format="{message}", catch=False, exception_formatter=None) + assert [x._exception_formatter for x in logger._handlers.values()] == [None] + try: + raise ValueError + except Exception: + logger.exception("got_exception") + + assert handler.passed_write is True