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 Logger
s. 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: