This is not supported out-of-the-box, but can be done with a minimal amount of code.
Python 3.2 introduced a new API to make it possible to support your preferred style throughout your whole application without customizing individual Logger:
Format/bracket style
import logging
class BracketStyleRecord(logging.LogRecord):
def getMessage(self):
msg = str(self.msg) # see logging cookbook
if self.args:
try:
msg = msg % self.args # retro-compability for 3rd party code
except TypeError as e:
if e.args and "not all arguments converted" in e.args[0]:
# "format" style
msg = msg.format(*self.args)
else:
raise # other Errors, like type mismatch
return msg
logging.setLogRecordFactory(BracketStyleRecord)
logging.basicConfig()
logging.error("The first number is %s", 1) # old-style
logging.error("The first number is {}", 1) # new-style
Caveats:
- Keyword arguments are not supported. That is only possibly in a single
Logger (see "Using a specific style in a single Logger" section below)
F-string style
import logging
import inspect
class FStringStyleRecord(logging.LogRecord):
def getMessage(self):
msg = str(self.msg) # see logging cookbook
if self.args:
msg = msg % self.args # retro-compability for 3rd party code
elif "{" in msg:
# it might be a f-string
try:
frame = inspect.currentframe().f_back
while "/logging/" in frame.f_code.co_filename:
frame = frame.f_back
variables = {}
variables.update(frame.f_locals)
variables.update(frame.f_globals)
msg = msg.format(**variables)
except Exception:
pass # Give up, treat the string as a literal message
return msg
logging.setLogRecordFactory(FStringStyleRecord)
logging.basicConfig()
one = 1
logging.error("The first number is %s", 1) # old-style
logging.error("The first number is {one}") # f-string-style (note the lack of f"")
Caveats:
- Do NOT use this in production code. It can have many security, performance and memory implications (including remote code execution if you're not extremely careful)
- This will not work as-is if your package contains a directory named
logging
Sometimes it might be easier or better to only support a different style in individual Loggers. For that, you can use a LoggerAdapter, as recommended in the cookbook:
import logging
class Message:
def __init__(self, fmt, args, kwargs):
self.fmt = fmt
self.args = args
self.kwargs = kwargs
def __str__(self):
return self.fmt.format(*self.args, **self.kwargs)
class StyleAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra=None):
super().__init__(logger, extra or {})
def log(self, level, msg, /, *args, **kwargs):
if self.isEnabledFor(level):
msg, kwargs = self.process(msg, kwargs)
self.logger._log(level, Message(msg, args,kwargs), ())
logger = StyleAdapter(logging.getLogger(__name__))
logging.basicConfig()
logger.error("The first number is {one}", one=1)
This supports keyword arguments, unlike the version in the cookbook
For Pylint support can use logging-format-style=new in your pylintrc to support the new format.
Note: don't be misled by the style argument of the Formatter or of basicConfig. These arguments are only used to set the style of the logging format string, and are not applied to individual messages:
formatter = logging.Formatter(style='{', fmt="{levelname}:{name}:{message}")
Useful references: