Logging B: Saving Logs to a File and Recording Important Actions
- Set up Logging that writes to a file and understand why the log “survives” between runs
- Tell apart append mode and write (overwrite) mode
- Record an Exception including the full Traceback with
logging.exception - Decide which actions are important to record and at which level
What will we learn?
- How to write logs to a file, and not only to the screen.
- Writing modes: add to the end of the file, or start over.
- How to record an error with all the details (Traceback).
- What to record: which actions are important and at which level.
Why do we need this?
Remember the topic on the computer, the operating system and the network? What is on the screen and in memory disappears when the program ends. A file is kept on Storage. A log on the screen helps only whoever is sitting in front of it now. A log in a file can be opened tomorrow, searched, and used to understand what happened during the night.
On real servers, logs are the main tool for investigating faults. “Get me the logs from 3 a.m.” is a routine sentence in a development team.
Explanation from the basics
Logging to a file
You add the filename parameter to basicConfig. From that moment, the records are written to the file instead of to the screen. The file is created automatically if it does not exist, in the current folder (the Current Working Directory, like any relative path).
Writing modes (filemode)
| Mode | Meaning | When |
|---|---|---|
"a" (Append) — the default | Adds to the end of the file. The previous logs are kept. | Almost always. You want history. |
"w" (Write) | Deletes the previous content and starts over on every run | In practice, when you want only the last run |
logging.exception — an error with all the details
logging.error("Payment failed") records only the message. Inside an except block, you can use logging.exception("..."): it records at the ERROR level and also attaches the full Traceback, so the file shows exactly on which line and in which function the error happened. As if you saw the crash yourself, without the program crashing.
What is worth recording?
| Worth recording | Why |
|---|---|
| The start and end of the program/server | To know when it ran and when it stopped |
| Actions that change data: create, update, delete | “Who deleted the order?” |
| Logging in and out, failed attempts | Security |
Every error that was caught (except) | Not to make problems disappear |
| Calls to external services and their result | “Was the payment sent?” |
| Do not record | Why |
|---|---|
| Passwords, credit card numbers, tokens | Security and privacy |
| Every round of a big loop at INFO | “Noise” that hides what matters. If at all, at DEBUG |
Syntax
import logging
logging.basicConfig(
filename="app.log", # write to this file (relative to the working directory)
filemode="a", # "a" = append (default), "w" = overwrite each run
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8", # safe for any characters in messages
)
try:
risky()
except SomeError:
logging.exception("What we were trying to do") # ERROR + full tracebackFirst code example
week-2/file_logger.py:
import logging
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
logging.info("Program started")
logging.warning("Low disk space")
logging.info("Program finished")
print("Done. Check app.log")On the screen:
Done. Check app.logIn the file app.log (after two runs):
2026-10-06 10:02:41,203 INFO Program started
2026-10-06 10:02:41,203 WARNING Low disk space
2026-10-06 10:02:41,204 INFO Program finished
2026-10-06 10:03:15,881 INFO Program started
2026-10-06 10:03:15,881 WARNING Low disk space
2026-10-06 10:03:15,882 INFO Program finishedBreaking down the code line by line
| Line | Explanation |
|---|---|
filename="app.log" | The logs are written to this file, not to the screen. |
(no filemode) | The default is "a": every run adds to the end of the file. That is why after two runs there are 6 lines. |
encoding="utf-8" | Allows writing any character, including non-English letters in messages (in our code the messages are in English, but usernames can be in any language). |
print("Done...") | print still reaches the screen. It is output for the user, and has nothing to do with the log. |
To read the file: open it in VS Code, or from the Terminal with cat app.log. In PowerShell you can also follow it in real time: Get-Content app.log -Wait. On Mac/Linux: tail -f app.log.
Another example
A full log for a program that processes payments, with logging.exception. week-2/payments.py:
import logging
logging.basicConfig(
filename="payments.log",
filemode="w",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
def charge(customer, amount_text):
amount = float(amount_text)
if amount <= 0:
raise ValueError("amount must be positive, got " + str(amount))
logging.info("Charged %s: %.2f NIS", customer, amount)
return amount
payments = [("dana", "120"), ("avi", "abc"), ("noa", "-5"), ("yossi", "80.5")]
logging.info("Batch started: %s payments", len(payments))
total = 0
failed = 0
for customer, amount_text in payments:
try:
total = total + charge(customer, amount_text)
except ValueError:
failed = failed + 1
logging.exception("Payment failed for %s (input: %r)", customer, amount_text)
logging.info("Batch finished: total %.2f NIS, %s failed", total, failed)
print("Batch done. Total:", total, "| Failed:", failed)On the screen:
Batch done. Total: 200.5 | Failed: 2In payments.log:
2026-10-06 10:20:01,010 INFO Batch started: 4 payments
2026-10-06 10:20:01,010 INFO Charged dana: 120.00 NIS
2026-10-06 10:20:01,011 ERROR Payment failed for avi (input: 'abc')
Traceback (most recent call last):
File "payments.py", line 28, in <module>
total = total + charge(customer, amount_text)
File "payments.py", line 13, in charge
amount = float(amount_text)
ValueError: could not convert string to float: 'abc'
2026-10-06 10:20:01,012 ERROR Payment failed for noa (input: '-5')
Traceback (most recent call last):
File "payments.py", line 28, in <module>
total = total + charge(customer, amount_text)
File "payments.py", line 15, in charge
raise ValueError("amount must be positive, got " + str(amount))
ValueError: amount must be positive, got -5.0
2026-10-06 10:20:01,012 INFO Charged yossi: 80.50 NIS
2026-10-06 10:20:01,012 INFO Batch finished: total 200.50 NIS, 2 failedBreakdown:
filemode="w"— here we chose to overwrite on every run, so that in practice there is only the last log.%.2f— a decimal number with 2 digits after the point (120.00). Suitable for money amounts.%r— shows the value with quotes ('abc'). That way you clearly see what was received, including extra spaces or an empty string.logging.exception(...)— an ERROR record, with the full Traceback below it. Both errors areValueError, but for different reasons. The file shows exactly which line threw each one:float()(line 13) or ourraise(line 15).- The program did not crash: it continues to the next payment, and all the information for investigating is saved.
printsummarizes for the user in one line. The log tells the full story to the developer.
Common mistake
Mistake 1: “the log is not written / I can’t find the file”. The file is created in the Current Working Directory. If you ran from another folder, the file is there. (Remember the topic on the computer and the operating system!)
Mistake 2: logging.error inside except when you want to investigate.
except ValueError:
logging.error("Payment failed")It says it failed, but not why and not where. Inside except, logging.exception gives the full picture with no effort. (If the error is expected and ordinary, like wrong input from a user, and you do not need a Traceback, you can use logging.warning with the error’s message: logging.warning("bad input: %s", error).)
Mistake 3: filemode="w" on a real server. Every restart of the server deletes the history. Exactly after a crash, when you need the log most, it is deleted. On servers: only "a".
Mistake 4: thinking that logging.exception handles the error. It only records it. The decision what to do (continue, try again, pass it on with raise) is still yours.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: A log to a file
Set up a log that is saved to the file shop.log, at level INFO, with time, level and message, and that is not deleted between runs. Record in it:
- INFO when order number 17 is created;
- WARNING when only 2 units are left in stock.
Show solution
import logging
logging.basicConfig(
filename="shop.log",
filemode="a",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
logging.info("order 17 created")
logging.warning("low stock: 2 left")filename— the log is written to a file instead of to the screen.filemode="a"— append: every run adds to the end of the file, and the history is kept.- The content of the file will look like this:
2026-11-01 10:00:01 INFO order 17 created
2026-11-01 10:00:01 WARNING low stock: 2 left
Exercise 2: append versus write
- What is the difference between
filemode="a"andfilemode="w"? - A server crashed during the night and was restarted automatically. The log was set up with
"w". What is the problem?
Show solution
"a"(append) — adds to the end of the existing file."w"(write) — deletes the file and starts over on every run.- With
"w", the restart deleted the log — exactly the information needed to understand why the server crashed. For logs you almost always use"a".
Exercise 3: logging.exception
Write a loop that charges a list of payments. Each amount is text; an amount that is not a number is recorded in the log with all the error’s details, and the loop continues. At the end the total amount is printed.
payments = ["120", "abc", "80"]Expected output:
Total charged: 200Show solution
import logging
logging.basicConfig(filename="payments.log", filemode="a", level=logging.INFO, encoding="utf-8")
payments = ["120", "abc", "80"]
total = 0
for amount_text in payments:
try:
total = total + int(amount_text)
logging.info("charged %s", amount_text)
except ValueError:
logging.exception("invalid amount: %s", amount_text)
print("Total charged:", total)Total charged: 200logging.exception is called inside except. It records at the ERROR level, and adds the full Traceback to the log — that way you can see exactly where and why the amount failed.
Exercise 4: What to record and what not
For each item — record it in the log or not? Explain.
- The number of the order that failed.
- The password the user typed, when the login failed.
- The username that tried to log in.
- The full credit card number.
- How long a slow action took.
Show solution
- Record — without it you cannot investigate the fault.
- Do not record — passwords must never appear in the log. Anyone who can read the log would see them.
- Record — important for security: who tried to log in, and when.
- Do not record — sensitive information. At most the last four digits.
- Record — it helps find performance problems.
The rule: enough details to investigate — but never sensitive information.
Exercise 5: A login journal
You have a list of login attempts. Each attempt is a username and a password (two parallel lists). The correct password for all the users in the exercise is "1234". Record in the log security.log:
- INFO for every successful login;
- WARNING for every wrong password (without recording the password!).
At the end, print how many logins succeeded and how many failed.
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]Expected output:
Successful: 2, failed: 2Show solution
import logging
CORRECT_PASSWORD = "1234"
logging.basicConfig(
filename="security.log",
filemode="a",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]
successful = 0
failed = 0
for i in range(len(users)):
if passwords[i] == CORRECT_PASSWORD:
successful = successful + 1
logging.info("login ok: %s", users[i])
else:
failed = failed + 1
logging.warning("wrong password for %s", users[i])
logging.info("summary: %s successful, %s failed", successful, failed)
print("Successful: " + str(successful) + ", failed: " + str(failed))Successful: 2, failed: 2Notice: the WARNING records the username — not the password.
Summary
basicConfig(filename="app.log")writes logs to a file on Storage, and they are kept after the program ends.filemode="a"(the default) appends."w"overwrites. On servers, always append.logging.exception("...")insideexceptrecords an ERROR with the full Traceback, without the program crashing.- Record start/end, changes to data, security and errors. Do not record passwords and sensitive information.
- The file is created in the Current Working Directory.
tail -f/Get-Content -Waitfollow it in real time.