Skip to content

Logging and Error Propagation

Page Maps

graph LR
  family["Python Programming"]
  program["Python Object-Oriented Programming"]
  section["Resources Failures Safe Evolution"]
  page["Logging and Error Propagation"]
  capstone["Capstone evidence"]

  family --> program --> section --> page
  page -.applies in.-> capstone
flowchart LR
  orient["Orient on the page map"] --> read["Read the main claim and examples"]
  read --> inspect["Inspect the related code, proof, or capstone surface"]
  inspect --> verify["Run or review the verification path"]
  verify --> apply["Apply the idea back to the module and capstone"]

Read the first diagram as a placement map: this page is one concept inside its parent module, not a detached essay, and the capstone is the pressure test for whether the idea holds. Read the second diagram as the working rhythm for the page: name the problem, study the example, identify the boundary, then carry one review question forward.

Why this lesson matters

Systems become harder to repair when errors are both logged everywhere and explained nowhere.

Two bad patterns often appear together:

  • the same failure is logged repeatedly at many layers
  • the exception still propagates upward with no clearer contract than before

That creates noise without understanding. This lesson is about deciding where meaning is added, where context is attached, and where propagation should stop or continue.

Logging is not recovery

Logging records that something happened. It does not decide:

  • whether the caller can retry
  • whether the state changed
  • whether compensation is needed
  • whether the operation should continue

If the code uses logging as a substitute for failure design, the logs become a graveyard of unresolved questions.

Propagation needs a purpose

An error should keep propagating only while the next layer can do something useful with it, such as:

  • attach boundary context
  • translate it into a smaller public contract
  • decide retry or compensation
  • terminate the request cleanly

If a layer cannot add meaning or action, it should not usually wrap the error just to make the stack deeper.

Add context where the boundary changes

The best place to enrich an error is often the place where the system changes boundary:

  • repository to application
  • adapter to orchestration
  • request handler to public API

At those points, you can explain:

  • what operation was being attempted
  • what remained authoritative
  • what category of failure now matters to the caller

That is more useful than logging raw internals repeatedly at every level.

Avoid duplicate logging

One failure should usually have one primary observability owner.

For example:

  • the boundary that will decide the public response may log the failure
  • inner layers may raise named failures without logging them again

Duplicate logging creates three problems:

  • noisy incident trails
  • false counts for one real failure
  • confusion about which layer truly owned the response

The student should leave this page knowing that "log everywhere" is not a serious observability strategy.

Translate, then propagate

Sometimes propagation should continue, but not in the original form.

For example:

  • a database exception may become storage_unavailable
  • a timeout may become dependency_unreachable
  • a malformed external response may become boundary_contract_violation

This translation step is not hiding reality. It is making the outward contract smaller and more teachable.

What good logs contain

Useful logs usually help answer:

  • what operation failed?
  • which owned boundary failed?
  • what identifiers or correlation data matter?
  • what category of failure was this?
  • what next action is expected, if any?

Useful logs do not need to dump every implementation detail. They need to make incident triage and repair easier.

What to avoid

Avoid logs that:

  • repeat the same stack trace three times
  • expose internal exception trees directly to users
  • report failure with no operation context
  • log normal domain rejection as if it were an infrastructure emergency
  • hide the true owner by making every layer look equally responsible

These patterns make failure review harder exactly when clarity matters most.

Common mistakes

  • logging at every catch site
  • swallowing the exception after logging without a recovery policy
  • wrapping errors repeatedly without adding new meaning
  • treating domain rejection as an operational incident
  • propagating raw dependency exceptions past the boundary that should translate them

These mistakes blur both observability and contract design.

Review checklist

Question Good sign
does each failure have a clear observability owner? yes
does propagation continue only while a higher layer can act meaningfully? yes
are boundary translations reducing noise and clarifying contract? yes
do logs help repair work instead of duplicating stack traces? yes

Capstone connection

Choose one capstone failure path and inspect:

  • where it is first raised
  • where it is translated
  • where it is logged
  • where the public response meaning is decided

If those steps are scattered without purpose, the design is still teaching propagation as accident rather than policy.

Exit check

Leave this lesson only when you can do all of these:

  • explain why logging alone does not solve failure handling
  • identify where an error should gain new context instead of new noise
  • describe one capstone path where duplicate logging should be avoided