(I was advised to move this discussion from the Python bug tracker to here)
Logging f-strings is quite popular, but is potentially unsafe with the current logging methods. There are different ways to solve this problem – while keeping new-style formatting.
The problem with logging f-strings
Consider the examples below:
logger.info('look: %s', untrusted_string) # OK
logger.info('look: %(foo)s', {'foo', untrusted_string}) # OK
logger.info(f'look: {untrusted_string}') # OK
logger.info(f'look: {untrusted_string}', some_dict) # DANGER!
In the last example, untrusted_string
will be interpreted as a string template by the logger.
If it has the value %(foo)999999999s
(where foo is present in some_dict
),
logging gets stuck trying to add over a gigabyte of whitespace to a string.
In other words: a Denial-of-Service attack.
PEP675 and typing.overload
to the rescue
Assuming you wouldn’t want to ban logging of f-strings entirely (it is readable and often faster), you’d want the logging API to detect and disallow combining f-strings with logger arguments (i.e. the 4th line in the code sample above).
One way to do this is to use LiteralString
(from PEP675) and typing.overload
to define better logger methods (the debug
variant show below):
# Case for handling any (potentially untrusted) string.
# Additional arguments are explicitly forbidden.
@overload
def debugf(self, msg: str, exc_info=False):
...
# Case for performing formatting by the logger.
# Untrusted strings are explicitly forbidden.
@overload
def debugf(self, msg: LiteralString, *args, exc_info=False, **kwargs):
...
This new API would disallow dangerous situations. Anybody using a type checker or IDE would get notified.
Why the
f
suffix? Since changing the existing methods is off the table,f
suffixes are a nice and common way to indicate it is formatting-related.
Big bonus: switching logging
to the modern formatting style
An additional advantage to a new set of logger methods is that it’s possible to migrate away from the old %
-style string formatting. The new logging methods could support:
logger.debugf("hello {planet}", planet='world')
Even better: the implementation for this already exists in the logging cookbook, so it’s already guaranteed to work idiomatically with the stdlib .