print() is fine for a script you're running once. In code that other code calls, teams use logging: every message gets a level and a source, and you choose what to see without editing the code.
Before
def charge(order_id, amount): print("charging", order_id, amount) if amount <= 0: print("bad amount!", order_id) return False print("done", order_id) return True charge("A-1001", 49.99) charge("A-1002", 0)
charging A-1001 49.99 done A-1001 charging A-1002 0 bad amount! A-1002
Every line looks equally important, and the only way to quieten it is to delete lines.
After
import logging, sys logging.basicConfig(stream=sys.stdout, level=logging.INFO, force=True, format="%(levelname)-7s %(name)s: %(message)s") log = logging.getLogger("orders") def charge(order_id, amount): log.debug("charging %s for %.2f", order_id, amount) if amount <= 0: log.warning("order %s has amount %.2f, skipped", order_id, amount) return False log.info("charged order %s", order_id) return True charge("A-1001", 49.99) charge("A-1002", 0)
INFO orders: charged order A-1001 WARNING orders: order A-1002 has amount 0.00, skipped
The debug line is still in the code, just hidden. Change level=logging.INFO to logging.DEBUG and run it again: nothing else changes and the detail comes back.
Why teams do this
- Levels.
debugfor you,infofor the normal story,warninganderrorfor the things someone should look at. - Sources.
getLogger("orders")names where a message came from, so one noisy module can be turned down on its own. - Destinations. The same calls can write to a file, a terminal or a log service, chosen in one place at start-up.
- Cheap when off.
log.debug("... %s", x)only builds the string if debug is on. An f-string is built every time.
When not to use it
Output that is the program's job (a report, a table, the answer a command-line tool prints) belongs on print(). Logging is for messages about the program, not from it.