Skip to content

Commit 21189c7

Browse files
jdclaude
andauthored
fix: format traceback inline for loguru compatibility (#622)
Stop passing `exc_info=` as a kwarg to `logger.log()`. Standard `logging.Logger` treats this specially (appends traceback), but loguru interprets all kwargs as message format parameters, causing a KeyError. Instead, format the traceback into the message string directly using `traceback.format_exception()`. This works with all loggers that implement `LoggerProtocol` (logging, loguru, structlog, etc.). Also tighten `LoggerProtocol.log()` by removing `**kwargs` since we no longer pass any keyword arguments. Fixes #420 Change-Id: Icc145539d443c83f09266e25b9819bd2516aeddc Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e104116 commit 21189c7

2 files changed

Lines changed: 12 additions & 15 deletions

File tree

tenacity/_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ class LoggerProtocol(typing.Protocol):
3232
Compatible with logging, structlog, loguru, etc...
3333
"""
3434

35-
def log(
36-
self, level: int, msg: str, *args: typing.Any, **kwargs: typing.Any
37-
) -> typing.Any: ...
35+
def log(self, level: int, msg: str, *args: typing.Any) -> typing.Any: ...
3836

3937

4038
def find_ordinal(pos_num: int) -> str:

tenacity/before_sleep.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17+
import traceback
1718
import typing
1819

1920
from tenacity import _utils
@@ -35,8 +36,6 @@ def before_sleep_log(
3536
"""Before sleep strategy that logs to some logger the attempt."""
3637

3738
def log_it(retry_state: "RetryCallState") -> None:
38-
local_exc_info: BaseException | bool | None
39-
4039
if retry_state.outcome is None:
4140
raise RuntimeError("log_it() called before outcome was set")
4241

@@ -46,22 +45,22 @@ def log_it(retry_state: "RetryCallState") -> None:
4645
if retry_state.outcome.failed:
4746
ex = retry_state.outcome.exception()
4847
verb, value = "raised", f"{ex.__class__.__name__}: {ex}"
49-
50-
if exc_info:
51-
local_exc_info = retry_state.outcome.exception()
52-
else:
53-
local_exc_info = False
5448
else:
5549
verb, value = "returned", retry_state.outcome.result()
56-
local_exc_info = False # exc_info does not apply when no exception
5750

5851
fn_name = retry_state.get_fn_name()
5952

60-
logger.log(
61-
log_level,
53+
msg = (
6254
f"Retrying {fn_name} "
63-
f"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}.",
64-
exc_info=local_exc_info,
55+
f"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}."
6556
)
6657

58+
if exc_info and retry_state.outcome.failed:
59+
ex = retry_state.outcome.exception()
60+
if ex is not None:
61+
tb = "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))
62+
msg = f"{msg}\n{tb.rstrip()}"
63+
64+
logger.log(log_level, msg)
65+
6766
return log_it

0 commit comments

Comments
 (0)