trapping f format errors so they log to file and don't crash running code
Thanks for loguru.
I know the "issue" with f-format strings causing problems with some data is noted in the documentation and I've seen several github issues such as #1008 discussing aspects of this.
I want to use f-strings in my logging because it's more elegant and significantly reduces line length and ugly 'redundant' code like data=data, value=value etc, however I naturally don't want my program to crash based on data values like dictionaries causing issues.
So I try to balance the problem by using f strings for most data but adding {{data}} ... with the attendant data=data for the known problem cases. This seems pragmatic to me.
However I have a large code base and it's too hard to exhaustively check every value of every log for possible problems. When the above problem occurs, exceptions are ONLY output to the console, not the log files and my program doesn't crash but one of threads in it does. Trying to redirect stderr/stdout kind of defeats half the point of a good logging system anyway.
I accept that the troublesome logging statements are technically faulty and one could argue it's not loguru's place to address that but it's actually an undesired interaction between two pieces of unrelated code (mine and loguru) where neither code on its own is wrong.
I have three issues:
- It is not acceptable for logging to silently crash one of my threads whilst the rest of the program merrily carries on in a broken way (I cannot check liveness of all threads all the time, especially as I don't own all threads and logging should never contribute program failure).
- I cannot easily monitor the console of my app long term - logging issues may be latent in "production" code. I need to be able to look through log-FILES and find the issue.
- It's nearly impossible to find the faulty code lines.
I made the following exploratory change to the loguru code (Logger._log) (
try:
log_record["message"] = message.format(*args, **kwargs)
except Exception as e:
log_record["message"] = f"*** LOGURU FORMAT ERROR: {message} :: {args} :: {kwargs}"
This has the twofold benefit that a) Logging issues do not crash application threads. b) Issues are clearly logged and I can find the errant lines and address
I know the use of the broad Exception is not ideal, it could certainly be narrowed down to the handful of possible exceptions but also being just one line it seemed relatively harmless. Anyhow my point is the principle. I am led to understand that the cost of try-except is also near-enough free in cases where the exception is not triggered, so I infer performance would not be adversely affected.
Certainly this change allowed my program to continue in the face or errors whilst also highlighting those lines that were causing an issue. For me it's a win-win. Would you consider adding this handling into the code base so others can benefit.
regards Kim
Hi @kiml.
Thank you for your detailed feedback. I completely agree with you, it is not acceptable that the application could crash because of arbitrary logged message.
Using a generic try / except could be an acceptable mitigation solution, but I want to go further. You see, it's essentially a problem with the design of the Loguru API. The keyword arguments are used both to format the message and to capture additional contextual information. This is a problem because Loguru cannot infer the intent behind the argument, which leads to the issue you've identified when users combine f-strings with keyword arguments that are not meant to be formatted.
For this reason, I have been saying for some time now that I want to change the API and use keyword arguments exclusively to capture context. This would allow them to be used with f-strings without risk. I haven't had time to implement it yet, but I'm thinking about it a lot at the moment.
I think this approach would be preferable to a try / except statement. Loguru should continue to raise an uncaught exception if the logging message is malformed, so that the developer is notified and can correct it immediately. Furthermore, although there exists a catch argument in the add() function, it only applies to handlers, whereas formatting is done beforehand.
That said, in the meantime, maybe you could patch the logging functions this way to preemptively catch the possible formatting errors:
from loguru import logger
from functools import wraps
def patch_logger(logger):
error = logger.__class__.error
def safe_log(func):
@wraps(func)
def wrapper(__logger, __message, *args, **kwargs):
logger_ = __logger.opt(depth=1)
try:
func(logger_, __message, *args, **kwargs)
except Exception:
error(logger_, f"*** LOGURU FORMAT ERROR: {__message} :: {args} :: {kwargs}")
return wrapper
for method in ["trace", "debug", "info", "warning", "success", "error", "critical", "exception"]:
setattr(logger.__class__, method, safe_log(getattr(logger.__class__, method)))
patch_logger(logger)
data = {"foo": "bar"}
logger.info(f"Data: {data}", baz=42)
2025-08-02 10:17:50.657 +02:00 | ERROR | __main__:<module>:26 - *** LOGURU FORMAT ERROR: Data: {'foo': 'bar'} :: () :: {'baz': 42}
This should be technically sound, since the logger object is global anyway.
Delgan, Thanks for the reply. I understand where you're coming from. I'll implement a patch for the present and await an updated api in future.
One comment I would add (which might not be an issue in your future api) but it refers to your comment "Loguru should continue to raise an uncaught exception if the logging message is malformed, so that the developer is notified and can correct it immediately."
I'm not sure what distinct cases define 'malformed' but assuming the program "compiles" so the python is valid, then the remaining definition of malformed might include: a) contrary to the defined api (bad keywords etc) b) data sensitive faults (basically the current issue we were discussing)
I think we agree that b) should not cause any logger to raise an exception. This leave other cases such as "a)"
My thoughs for your future consideration are:
- Logging is often of importance not just during development but in "working" production code - in error handling paths to capture potentially rare problems.
- Noting that of course many routine python functions and operators throw exceptions with bad data - this is fair - I'd argue however that there may be a case for optionally disabling logging exceptions because: * No-one is going to put try-catch around logging statements. * In my world it's a fantasy to assume that I'd be able to create test cases for every failure code path and every logging variation (the project would blow out to "Government/Defence/Medical project" proportions. I therefore rely on "compilation" acceptance, python type checkers and reasonable eyeball experience to avoid the most egregious errors. * I can't afford logging a (possibly rare but not inherently fatal) failure case to bring down the app (and lose the failure log info as well).
So I'd rather have the option (production mode?) to prevent the logger throwing a "hissy fit" :-) and shooting everyone in the foot for valid-python-but-malformed-logging calls when it can simply log its objection as well as (hopefully) enough info to capture the actual code-path data that led to the logging line being executed in the first place. After all we're using logging to do exactly this for our own code - it's not unreasonable to extend the "protection" to the very thing doing the fault reporting.
Anyhow - just food for thought. thanks again - I'll leave it to you to decide whether to close this ticket or if there's enough unique content in it to keep it open for now. cheers Kim
I'm not sure what distinct cases define 'malformed'
Basically, I meant something like this should not be swallowed by Loguru:
logger.info("Addition: {} + {} = {}", 1, 2) # Missing 3rd parameter!
I felt this was a clear mistake of the logging call at the application level, and therefore should be reported right away to the developer by crashing the application. I distinguished it from an internal error in the handlers, which are not controlled by the developer at the application level and therefore should be swallowed.
Now, this decision is debatable. I don't really have a strong argument in favor of it.
Notably, the builtin logging module will swallow the exception:
import logging
logger = logging.getLogger(__name__)
# Error printed, not raised.
logger.warning("Addition: %d + %d = %d", 1, 2)
On the other hand, structlog library will not capture it:
import structlog
logger = structlog.getLogger(__name__)
# Error raised, not printed.
logger.warning("Addition: %d + %d = %d", 1, 2)
Interestingly, there is an ongoing discussion about this design choice: https://github.com/hynek/structlog/issues/258 I tend to agree with @hynek that errors should not pass silently.
Argument could be made that logging is generally disabled during unit tests, therefore even with perfect coverage some logging error could be missed (as opposed to actual application code). However, perhaps this is an hint that logging should not be disabled but rather configured with a null sink.
I don't have a definitive answer at this time, but I will continue to think about it.
@Delgan
Could we at least add an option to at least disable this formatting functionality? I always format the _message parameter using f-string and only want to pass additional properties as extra context for my json logs.
elif args or kwargs:
colored_message = None
if arg_formating_disabled:
log_record["message"] = str(message)
else:
log_record["message"] = message.format(*args, **kwargs)
I just ran into this (documented here https://github.com/Delgan/loguru/issues/1395) and I'm not even sure how to deal with it...
I can't have the logger crashing my application, we rely on json logs for cloudwatch metrics and I just spent an entire day refactoring to loguru...
I'm willing to contribute to resolving this however I can...
@ChuckJonas Formatting of **kwargs will be removed altogether, therefore a configuration option shouldn't even be necessary.
Are you able to identify the logging messages containing braces through a regex maybe? You could then use logger.bind() to capture the context instead of **kwargs.
As a last-resort hacky solution, you can always patch the logger methods as demonstrated in a previous comment: https://github.com/Delgan/loguru/issues/1366#issuecomment-3146327098.
@Delgan gotcha...
I just put a work around in place. Sounds like once ya'll remove "formatting of **kwargs", I could probably swap back and everything would just work.
class LoguruWrapper:
"""
A wrapper around loguru's logger that changes kwargs behavior:
- Instead of using kwargs for message formatting, we bind them as extra fields
- This prevents curly braces in messages from breaking logging
- Provides better DX: logger.info("message", foo="bar") just works
"""
def __init__(self, logger_instance=None):
self._logger = logger_instance or _loguru_logger
def _log_with_bind(self, level: str | int, message: str, *args, **kwargs):
"""
Internal method that binds kwargs and logs the message.
Args are still supported for positional formatting if needed.
"""
if kwargs:
# Bind the kwargs as extra fields, then log with just the message and args
bound_logger = self._logger.bind(**kwargs)
if args:
bound_logger.log(level, message, *args)
else:
bound_logger.log(level, message)
else:
# No kwargs, just pass through
self._logger.log(level, message, *args)
def trace(self, message: str, *args, **kwargs):
"""Log with severity 'TRACE'."""
self._log_with_bind("TRACE", message, *args, **kwargs)
#def info ...