# Logging B: Saving Logs to a File and Recording Important Actions<no value>

## 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

```python
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 traceback
```

## First code example

`week-2/file_logger.py`:

```python
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:**

```text
Done. Check app.log
```

**In the file `app.log`** (after **two** runs):

```text
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 finished
```

## Breaking 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`:

```python
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:**

```text
Batch done. Total: 200.5 | Failed: 2
```

**In `payments.log`:**

```text
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 failed
```

**Breakdown:**

- `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 are `ValueError`, but for different reasons. The file shows exactly which line threw each one: `float()` (line 13) or our `raise` (line 15).
- **The program did not crash**: it continues to the next payment, and all the information for investigating is saved.
- `print` summarizes 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.**

```python
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.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## 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.

{{< solution >}}
```python
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`
{{< /solution >}}

### Exercise 2: append versus write

1. What is the difference between `filemode="a"` and `filemode="w"`?
2. A server crashed during the night and was restarted automatically. The log was set up with `"w"`. What is the problem?

{{< solution >}}
1. **`"a"`** (append) — adds to the end of the existing file. **`"w"`** (write) — deletes the file and starts over on every run.
2. With `"w"`, the restart **deleted the log** — exactly the information needed to understand why the server crashed. For logs you almost always use `"a"`.
{{< /solution >}}

### 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.

```python
payments = ["120", "abc", "80"]
```

**Expected output:**

```text
Total charged: 200
```

{{< solution >}}
```python
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)
```

```text
Total charged: 200
```

`logging.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.
{{< /solution >}}

### Exercise 4: What to record and what not

For each item — record it in the log or not? Explain.

1. The number of the order that failed.
2. The password the user typed, when the login failed.
3. The username that tried to log in.
4. The full credit card number.
5. How long a slow action took.

{{< solution >}}
1. **Record** — without it you cannot investigate the fault.
2. **Do not record** — passwords must never appear in the log. Anyone who can read the log would see them.
3. **Record** — important for security: who tried to log in, and when.
4. **Do not record** — sensitive information. At most the last four digits.
5. **Record** — it helps find performance problems.

The rule: enough details to **investigate** — but never **sensitive** information.
{{< /solution >}}

### 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.

```python
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]
```

**Expected output:**

```text
Successful: 2, failed: 2
```

{{< solution >}}
```python
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))
```

```text
Successful: 2, failed: 2
```

Notice: the WARNING records **the username** — not the password.
{{< /solution >}}

<!-- exercises:end -->

## 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("...")` inside `except` records 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 -Wait` follow it in real time.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Official documentation

- [Logging HOWTO — Logging to a file](https://docs.python.org/3/howto/logging.html#logging-to-a-file)
- [logging.basicConfig()](https://docs.python.org/3/library/logging.html#logging.basicConfig)
- [logging.exception()](https://docs.python.org/3/library/logging.html#logging.exception)

<!-- docs:end -->
