* Format a string, pass it to a function, and the function decides whether to emit it, then how to render it.
* Pass in the template and parameters to a function, and the function decides whether to emit it, then how to render it.
F-strings are pretty darn fast. I don't know how much relative overhead there would be in calling the logging function, and in the logging function's decision tree about whether to emit a log message.
While it might not be too visible in Python, formatting is a very significant cost. I work with systems that can output tens to hundreds of millions of log lines per second per core with the limiting factor being memory bandwidth. It would be pretty challenging in many systems to get even a tenth that rate. I can literally add 10,000 logs per second per core at a 0.1% system overhead. Pre-formatting your logs is convenient when just starting out, but you should really switch to a more efficient logging system pretty quickly.
Really, your problem is actually getting the logs off the system. When you generate logs at 15 GB/s per core the only device fast enough is RAM. If you want any logs larger than a circular RAM disk you need to deliberately slow down your logging rate so your ethernet can keep up.
Much more likely is whether someone would call
LOG.info(“%s: %s”, request_ip, request_path)
or LOG.info(f”{request_ip}: {request_path}”)
a few dozen times a second. I suspect it’s a pointless micro-optimization for 99.9% of use cases. import logging
import timeit
LOG = logging.getLogger()
ip = "1.2.3.4"
request = "/index.html"
duration = 1.5
print(timeit.timeit("LOG.info('%s: %s (%s)', ip, request, duration)", globals=globals()))
print(timeit.timeit("LOG.info(f'{ip}: {request} ({duration})')", globals=globals()))
1,000,000 iterations of the flake8-happy logging took 0.090s. The f-string version took 0.264.On one hand, the flake8 version is significantly faster if the log messages aren't emitted. On the other, the "slow" f-string version ran 4 million times a second while inside a timing harness. That's likely to be a trivial percent of any interesting program's CPU time unless it's inside a timing-critical inner loop, in which case don't do that.
For an extra data point, I bumped that up to `LOG.warning()` and re-ran the tests. A million flake8 runs took 4.617. A million f-string runs took 4.553s. If you're actually emitting the debugging record, f-strings are slightly faster. Huh, interesting. Today I learned!
I'll continue to use f-string logging in common cases. When it's slower, it's so very slightly slower that I don't care. But as others have mentioned, the `extra` argument when using flake8-style logging is brilliant for emitting structured data that's easier to parse later.
I'd be more worried about cases where you're calling some function to produce a novel value that will then be included in the log string— and Python doesn't have lazy argument evaluation so you'll pay the cost of that function call regardless.
Basically, if you're worried about string formatting overhead, it's probably time to ditch Python.