321

For the following code:

logger.debug('message: {}'.format('test'))

pylint produces the following warning:

logging-format-interpolation (W1202):

Use % formatting in logging functions and pass the % parameters as arguments Used when a logging statement has a call form of “logging.(format_string.format(format_args...))”. Such calls should use % formatting instead, but leave interpolation to the logging function by passing the parameters as arguments.

I know I can turn off this warning, but I'd like to understand it. I assumed using format() is the preferred way to print out statements in Python 3. Why is this not true for logger statements?

Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
pfnuesel
  • 14,093
  • 14
  • 58
  • 71
  • 3
    Is this still up-to-date? Isn't the usage of string interpolation vs. f-strings generally deprecated? What is the drawback of writing: `the_msg = ... [...] logger.debug(f'message: {the_msg}')` besides the noise generated by the linter? – Wör Du Schnaffzig Oct 26 '22 at 13:42
  • f-strings produce warning W1203: "Use lazy % formatting in logging functions (logging-fstring-interpolation)", so I guess not. It's true, I think, that the preferred way of formatting strings is to use f-strings, but in this case it wastes CPU cycles. – Paul Dec 19 '22 at 17:55

8 Answers8

390

It is not true for logger statement because it relies on former "%" format like string to provide lazy interpolation of this string using extra arguments given to the logger call. For instance instead of doing:

logger.error('oops caused by %s' % exc)

you should do

logger.error('oops caused by %s', exc)

so the string will only be interpolated if the message is actually emitted.

You can't benefit of this functionality when using .format().


Per the Optimization section of the logging docs:

Formatting of message arguments is deferred until it cannot be avoided. However, computing the arguments passed to the logging method can also be expensive, and you may want to avoid doing it if the logger will just throw away your event.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
sthenault
  • 14,397
  • 5
  • 38
  • 32
  • 1
    What exactly is the functionality that one cannot benefit from? Is `.format()` always expanded? Is that what "lazy interpolation" means, that it is only expanded when called? – pfnuesel Jan 06 '16 at 20:01
  • 8
    @pfnuesel, .format() is expanded before the call to logger.error, while "lazy interpolation" means expansion is done only if needed (eg the message is actually displayed somewhere) – sthenault Jan 08 '16 at 09:43
  • 16
    Is there a good reference for this lazy evaluation being preferred and making a difference? I couldn't find one in [PEP282](https://www.python.org/dev/peps/pep-0282/) or [the logging library](https://docs.python.org/2/library/logging.html) – culix May 06 '16 at 14:48
  • 2
    How does PEP498 fit here? On which side of laziness is it? – marmistrz Aug 11 '17 at 07:43
  • 72
    But does this just mean that we will have maintenance problems with our code later? Will we later be "recommended" by pylint to move to the `.format()` style at some point because `logging` has upgraded? I ask because I'm more interested in maintainability than I am in bleeding-edge speed performance, at least for most tasks. – Mike Williamson Jun 20 '18 at 17:42
  • 3
    @MikeWilliamson : I think this message is a warning because there are possible side effects, but you can safely ignore it. – saihtamtellim Nov 29 '18 at 13:38
  • 15
    While much of the motivation behind the warning is around performance (ie: if a log statement is not emitted, then the interpolation cost is saved), it's worth noting that in many (arguably most) applications the performance cost is negligible. See: https://github.com/PyCQA/pylint/issues/2395 and https://github.com/PyCQA/pylint/issues/2354 – Adam Parkin Aug 15 '19 at 22:01
  • 1
    How do you compare these two options with fstring `logger.error(f'oops caused by {exec}')`. Sonar analysis is saying I shouldn't use that. Should I listen to Sonar or @mustafagok as mentioned in his answer. – Ganesh Satpute Aug 26 '20 at 10:11
  • 1
    @MikeWilliamson No! Unless python changes it's logging mechanism this won't change. As mentioned [here](https://github.com/PyCQA/pylint/issues/1788) This isn't just about performance, there are actual bugs that f-strings etc. introduce. – Philip Couling Apr 07 '21 at 23:44
  • Thanks, @PhilipCouling . I read that link and I do not understand what you are referring to. The only bug seems to be when both f-string and log interpolation are happening. – Mike Williamson Apr 19 '21 at 14:05
  • @MikeWilliamson Apologies I think I meant to give you a different link but now can't find it. Either way issue 1788 (that I linked) is used as a reference in a few issues as a "definitive decision" on the part of pylint. – Philip Couling Apr 19 '21 at 14:55
  • After reading the discussions, I've decided to just use f-strings. `logging` ftw, as always... – Nathan Chappell Sep 22 '22 at 09:57
  • 1
    Missing from this discussion is the topic of errors in formatting. If the string formatting causes an unintended exception when done inside the logging library it will be handled and not trigger an exception in your application code. – Tim Nov 09 '22 at 05:06
  • Using format-style strings in logging (while keeping lazy interpolation) is possible since python 3.2. See [this answer](https://stackoverflow.com/a/75715402/1595865) – loopbackbee Mar 13 '23 at 10:43
88

Maybe this time differences can help you.

Following description is not the answer for your question, but it can help people.

If you want to use fstrings (Literal String Interpolation) for logging, then you can disable it from .pylintrc file with disable=logging-fstring-interpolation, see: related issue and comment.

Also you can disable logging-format-interpolation.


For pylint 2.4:

There are 3 options for logging style in the .pylintrc file: old, new, fstr

fstr option added in 2.4 and removed in 2.5

Description from .pylintrc file (v2.4):

[LOGGING]

# Format style used to check logging format string. `old` means using %
# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings.
logging-format-style=old

for old (logging-format-style=old):

foo = "bar"
self.logger.info("foo: %s", foo)

for new (logging-format-style=new):

foo = "bar"
self.logger.info("foo: {}", foo)
# OR
self.logger.info("foo: {foo}", foo=foo)

Note: you can not use .format() even if you select new option.

pylint still gives the same warning for this code:

self.logger.info("foo: {}".format(foo))  # W1202
# OR
self.logger.info("foo: {foo}".format(foo=foo))  # W1202

for fstr (logging-format-style=fstr):

foo = "bar"
self.logger.info(f"foo: {foo}")

Personally, I prefer fstr option because of PEP-0498.

mustafagok
  • 1,672
  • 10
  • 15
  • 8
    you can add `"python.linting.pylintArgs": ["--logging-format-style=old"]` to the vscode/settings.json file. [docs](https://code.visualstudio.com/docs/python/linting#_pylint) – mustafagok Jan 07 '20 at 19:15
  • 3
    in pylint 2.3.1: `optparse.OptionValueError: option logging-format-style: invalid value: 'fstr', should be in ['old', 'new']` upgrading to latest pylint (2.4.4) fixed this. – Florian Castellane Jan 28 '20 at 05:18
  • I am having following error: `Try installing a more recent version of python-pylint, and please open a bug report if the issue persists in t\ he latest release. Thanks! ` – alper Jun 05 '20 at 17:24
  • 12
    update: the `fstr` logging format option was removed, set `disable=logging-fstring-interpolation` instead in `.pylintrc`. https://github.com/PyCQA/pylint/issues/3570 – Florian Castellane Aug 14 '20 at 05:34
  • 1
    A shorter use of f strings is self.logger.info(f"{foo=}"). No need to spell out the variable or expression twice. – Tom Ekberg Aug 16 '21 at 20:52
36

In my experience a more compelling reason than optimization (for most use cases) for the lazy interpolation is that it plays nicely with log aggregators like Sentry.

Consider a 'user logged in' log message. If you interpolate the user into the format string, you have as many distinct log messages as there are users. If you use lazy interpolation like this, the log aggregator can more reasonably interpret this as the same log message with a bunch of different instances.

  • 5
    yes, this is the strongest argument for the lazy logging arguments formatting – Elias Dorneles Jul 29 '20 at 14:00
  • This isn't really the answer to the question. I could just as easily do a constant log message with a varying value, such as for JSON logging. @sthenault's answer is much clearer and makes much more sense given the context of the OP's question. – Arcsector Mar 08 '23 at 00:41
4

Here is an example of why it's better to use %s instead of f-strings in logging.

>>> import logging
>>> logging.basicConfig(level=logging.INFO)
>>> logger = logging.getLogger('MyLogger')
>>>
>>> class MyClass:
...     def __init__(self, name: str) -> None:
...         self._name = name
...     def __str__(self) -> str:
...         print('GENERATING STRING')
...         return self._name
...
>>> c = MyClass('foo')
>>> logger.debug('Created: %s', c)
>>> logger.debug(f'Created: {c}')
GENERATING STRING

Inspired by Python 3.7 logging: f-strings vs %.

Dave
  • 41
  • 1
0

format is indeed the preferred new style.

You should however not call format() directly, instead letting the interpolation of messages in logging do its job.

You can use the following code to enable using format style with lazy interpolation in python 3.2+

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: # not all arguments converted during formatting
                msg = msg.format(*self.args)
        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

For details, caveats and references see this answer.

loopbackbee
  • 21,962
  • 10
  • 62
  • 97
0

maybe for someone the solution is disabling in settings.json in Visual Studio Code

 "python.linting.pylintArgs": [
    "--disable=C0111,C0114,C0115,C0116б,W1203",
    "--disable=missing-module-docstring",
    "--disable=missing-class-docstring",
    "--disable=missing-function-docstring",        
]
DimNsk
  • 21
  • 1
  • 2
-1

Might be several years after but having to deal with this the other day, I made simple; just formatted the string before logger.

message = 'message: {}'.format('test')
logger.debug(message)

That way there was no need to change any of the settings from log, if later on desire to change to a normal print there is no need to change the formatting or code.

pelos
  • 1,744
  • 4
  • 24
  • 34
  • 1
    The answer from Tristan Crockett is the best reason NOT to use your method, even if it seems more practical at first. – richieadler Feb 22 '22 at 12:26
  • 1
    Solve the question about how to remove the warning. is the logger that is not capable of parse and join the string properly. and should be keep in mind that the programmer who needs set what he wants to log. – pelos Feb 23 '22 at 17:50
-1

"logging-format-interpolation (W1202)" is another one wrong recommendation from pylint (like many from pep8). F-string are described as slow vs %, but have you checked ? With 500_000 rotation of logging with f-string vs % -> f-string:23.01 sec. , %:25.43 sec.

So logging with f-string is faster than %. When you look at the logging source code : log.error() -> self.logger._log() -> self.makeRecord() -> self._logRecordFactory() -> class LogRecord() -> home made equivalent to format()

code :

import logging
import random
import time

loops = 500_000
r_fstr = 0.0
r_format = 0.0

def test_fstr():
    global loops, r_fstr
    for i in range(0, loops):
        r1 = time.time()
        logging.error(f'test {random.randint(0, 1000)}')
        r2 = time.time()
        r_fstr += r2 - r1

def test_format():
    global loops, r_format
    for i in range(0 ,loops):
        r1 = time.time()
        logging.error('test %d', random.randint(0, 1000))
        r2 = time.time()
        r_format += r2 - r1

test_fstr()
test_format()
print(f'Logging f-string:{round(r_fstr,2)} sec. , %:{round(r_format,2)} sec.')
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 07 '22 at 20:28
  • 1
    Pylint is correct. The reason is not performance-related (logging is the slow part, not string interpolation) but about the handling of errors. For example, if the formatting causes an exception when done within the logging code it will be caught and handled rather than causing an exception to be raised in your application code. – Tim Nov 09 '22 at 04:58