Debugging and logs
Build a small HTTP service with clear boundaries, tests, logs and a repeatable runtime.
THE IDEA
Debugging is narrowing, not guessing
A wrong answer at the end of a program means some earlier step produced a wrong value. The method is always the same: reproduce the failure with an input that fails every time, narrow down the first place where reality differs from what you expected, then change one cause and re-run. If you change three things at once you do not know which one fixed it.
s = subtotal(lines); print("subtotal", s) # expected 370
d = apply_discount(s, code); print("discount", d) # expected 333
t = add_tax(d); print("total", t) # expected 359Halve the search
With eight steps you do not need to inspect all eight. Check the middle: if the value is already wrong there, the bug is in the first half; if it is right, in the second. Each look halves the suspects, so eight steps need at most three looks. Try it, then switch bug scenarios and see that the same strategy works wherever the bug hides.
The program starts at 1000 and runs eight steps. One step has a bug. You know what each value should be. Click a checkpoint to print the real value there.
Suspect range: steps 1 to 8. Inspections used: 0. Halving each time needs at most 3.
Predict first. You print the value after step 4 and it is correct. Where is the bug?
Logs: prints that survive
A print is for you, now. A log is for whoever runs the program later, often when you are not there. Logging adds a level (DEBUG, INFO, WARNING, ERROR), a timestamp and a message you can filter. A server handles many requests at once, so their lines interleave. A request id repeated on every line is what lets you pull one request's story out of the mix.
Without the request id column you could not tell which WARNING belongs to which customer. With it, one filter gives one story.
Log identifiers, counts and outcomes: request id, number of lines, final total, which stage failed.
Log whole payloads, passwords, tokens or full card numbers. Logs are copied, shipped and read by many people.
Chapter context
Engineering makes a program understandable when it fails or changes. Separate pure calculations from external I/O so behavior can be checked cheaply and errors can be connected to specific inputs.
TRY IT YOURSELF
Exercise: find two bugs and make the logs useful
checkout(request_id, raw_lines, code, card)should return the final price in minor units: subtotal, then discount, then 8 percent tax, each rounded down. For["2 x 125", "3 x 40"]and codeSAVE10the correct answer is **359** (370, then 333, then 359).- Run it. It prints something else. Add
printorlogcalls between the stages to see the first stage whose value is wrong, then fix that stage. There are **two separate bugs**. - Then make the logging safe and useful: every log message must contain the request id, the card number must never appear in a log, a normal call should log at least one record per stage, and an unknown discount code should log a warning.
- The checker calls
checkoutwith other orders and captures what you log, so the fix has to be real.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Why keep a request identifier in logs?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For money examples, use integer minor units rather than binary floating point. The subtotal is sum(quantity × unit_price), and the domain must specify rounding and tax rules before those operations are implemented.
Worked example
Two items cost 125 minor units each and three cost 40 each. The subtotal is 2×125 + 3×40 = 370. A test can exercise this calculation without starting an HTTP server. Request parsing and response formatting need separate checks.
Reference code
The following example uses the Python standard library and does not call a remote service.
def subtotal(items):
return sum(quantity * price for quantity, price in items)
assert subtotal([(2, 125), (3, 40)]) == 370
assert subtotal([]) == 0
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Test coverage counts executed lines, not verified requirements. A container does not turn an unsafe operation into a safe one. JSON validity alone does not establish application validity.