# Logging ב': שמירת Logs לקובץ ותיעוד פעולות חשובות<no value>

## מה נלמד?

- איך כותבים לוגים **לקובץ**, ולא רק למסך.
- **מצבי כתיבה**: להוסיף לסוף הקובץ, או להתחיל מחדש.
- איך רושמים שגיאה **עם כל הפרטים** (Traceback).
- **מה לתעד**: אילו פעולות חשובות ובאיזו רמה.

## למה צריך את זה?

זוכרים את נושא מחשב, מערכת הפעלה ורשת? מה שעל המסך ובזיכרון נעלם כשהתוכנית נגמרת. **קובץ** נשמר על ה-Storage. לוג על המסך עוזר רק למי שיושב מולו **עכשיו**. לוג בקובץ אפשר לפתוח **מחר**, לחפש בו ולהבין מה קרה בלילה.

בשרתים אמיתיים, הלוגים הם הכלי העיקרי לחקור תקלות. "תביא לי את הלוגים מהשעה 3 בלילה" הוא משפט שגרתי בצוות פיתוח.

## הסבר מהבסיס

### לוג לקובץ

ל-`basicConfig` מוסיפים את הפרמטר `filename`. מרגע זה, הרשומות נכתבות **לקובץ** במקום למסך. הקובץ נוצר אוטומטית אם הוא לא קיים, **בתיקייה הנוכחית** (Current Working Directory, כמו כל נתיב יחסי).

### מצבי כתיבה (`filemode`)

| מצב | משמעות | מתי |
|---|---|---|
| `"a"` (Append) — **ברירת מחדל** | מוסיף לסוף הקובץ. הלוגים הקודמים נשמרים. | כמעט תמיד. רוצים היסטוריה. |
| `"w"` (Write) | **מוחק** את התוכן הקודם ומתחיל מחדש בכל הרצה | בתרגול, כשרוצים רק את ההרצה האחרונה |

### `logging.exception` — שגיאה עם כל הפרטים

`logging.error("Payment failed")` רושם רק את ההודעה. בתוך בלוק `except`, אפשר להשתמש ב-**`logging.exception("...")`**: הוא רושם ברמת ERROR **וגם מצרף את ה-Traceback המלא**, כך שבקובץ יופיע בדיוק באיזו שורה ובאיזו פונקציה השגיאה קרתה. כאילו ראיתם את הקריסה בעצמכם, בלי שהתוכנית קרסה.

### מה כדאי לתעד?

| כדאי לתעד | למה |
|---|---|
| התחלה וסיום של התוכנית/השרת | לדעת מתי רצה ומתי נעצרה |
| פעולות שמשנות מידע: יצירה, עדכון, מחיקה | "מי מחק את ההזמנה?" |
| התחברות והתנתקות, ניסיונות כושלים | אבטחה |
| כל שגיאה שנתפסה (`except`) | לא להעלים בעיות |
| קריאות לשירותים חיצוניים ותוצאתן | "האם התשלום נשלח?" |

| **לא** לתעד | למה |
|---|---|
| סיסמאות, מספרי אשראי, טוקנים | אבטחה ופרטיות |
| כל סיבוב בלולאה גדולה ב-INFO | "רעש" שמסתיר את החשוב. אם בכלל, ב-DEBUG |

## תחביר

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

## דוגמת קוד ראשונה

`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")
```

**במסך:**

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

**בקובץ `app.log`** (אחרי **שתי** הרצות):

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

## פירוק הקוד שורה אחר שורה

| שורה | הסבר |
|---|---|
| `filename="app.log"` | הלוגים נכתבים לקובץ הזה, **ולא** למסך. |
| (אין `filemode`) | ברירת המחדל `"a"`: כל הרצה **מוסיפה** לסוף הקובץ. לכן אחרי שתי הרצות יש 6 שורות. |
| `encoding="utf-8"` | מאפשר לכתוב כל תו, כולל עברית בהודעות (בקוד שלנו ההודעות באנגלית, אבל שמות משתמשים יכולים להיות בכל שפה). |
| `print("Done...")` | `print` עדיין מגיע למסך. הוא פלט **למשתמש**, ואין לו קשר ללוג. |

לקריאת הקובץ: לפתוח ב-VS Code, או מה-Terminal עם `cat app.log`. ב-PowerShell אפשר גם לעקוב בזמן אמת: `Get-Content app.log -Wait`. ב-Mac/Linux: `tail -f app.log`.

## דוגמה נוספת

**לוג מלא לתוכנית שמעבדת תשלומים**, עם `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)
```

**במסך:**

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

**ב-`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
```

**פירוק:**

- `filemode="w"` — כאן בחרנו לדרוס בכל הרצה, כדי שבתרגול יהיה רק הלוג האחרון.
- `%.2f` — מספר עשרוני עם 2 ספרות אחרי הנקודה (`120.00`). מתאים לסכומי כסף.
- `%r` — מציג את הערך **עם מירכאות** (`'abc'`). כך רואים בבירור מה התקבל, כולל רווחים מיותרים או מחרוזת ריקה.
- `logging.exception(...)` — רשומת ERROR, **ומתחתיה ה-Traceback המלא**. שתי השגיאות הן `ValueError`, אבל מסיבות שונות. הקובץ מראה בדיוק איזו שורה זרקה כל אחת: `float()` (שורה 13) או ה-`raise` שלנו (שורה 15).
- **התוכנית לא קרסה**: היא ממשיכה לתשלום הבא, וכל המידע לחקירה נשמר.
- `print` מסכם למשתמש בשורה אחת. הלוג מספר את הסיפור המלא למפתח.

## טעות נפוצה

**הטעות 1: "הלוג לא נכתב / לא מוצא את הקובץ".** הקובץ נוצר ב-**Current Working Directory**. אם הרצתם מתיקייה אחרת, הקובץ נמצא **שם**. (נושא מחשב, מערכת הפעלה ורשת, נושא Exceptions ו-Logging!)

**הטעות 2: `logging.error` בתוך `except` כשרוצים לחקור.**

```python
except ValueError:
    logging.error("Payment failed")
```

כתוב שנכשל, אבל **לא למה ולא איפה**. בתוך `except`, `logging.exception` נותן את התמונה המלאה בלי מאמץ. (אם השגיאה צפויה ורגילה, כמו קלט שגוי של משתמש, ולא צריך Traceback, אפשר `logging.warning` עם ההודעה של השגיאה: `logging.warning("bad input: %s", error)`.)

**הטעות 3: `filemode="w"` בשרת אמיתי.** כל הפעלה מחדש של השרת **מוחקת את ההיסטוריה**. בדיוק אחרי קריסה, כשהכי צריך את הלוג, הוא נמחק. בשרתים: רק `"a"`.

**הטעות 4: לחשוב ש-`logging.exception` מטפל בשגיאה.** הוא רק **רושם**. ההחלטה מה לעשות (להמשיך, לנסות שוב, לזרוק הלאה עם `raise`) היא עדיין שלכם.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.

### תרגיל 1: לוג לקובץ

הגדירו לוג שנשמר לקובץ `shop.log`, ברמה INFO, עם זמן, רמה והודעה, ושלא נמחק בין הרצות. רשמו בו:

- INFO כשנוצרת הזמנה מספר 17;
- WARNING כשבמלאי נשארו 2 יחידות בלבד.

{{< 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` — הלוג נכתב לקובץ במקום למסך.
- `filemode="a"` — **append**: כל הרצה מוסיפה לסוף הקובץ, וההיסטוריה נשמרת.
- תוכן הקובץ ייראה כך:

`2026-11-01 10:00:01 INFO     order 17 created`
`2026-11-01 10:00:01 WARNING  low stock: 2 left`
{{< /solution >}}

### תרגיל 2: append מול write

1. מה ההבדל בין `filemode="a"` ל-`filemode="w"`?
2. שרת קרס בלילה והופעל מחדש אוטומטית. הלוג הוגדר עם `"w"`. מה הבעיה?

{{< solution >}}
1. **`"a"`** (append) — מוסיף לסוף הקובץ הקיים. **`"w"`** (write) — מוחק את הקובץ ומתחיל מחדש בכל הרצה.
2. עם `"w"`, ההפעלה מחדש **מחקה את הלוג** — בדיוק את המידע שהיה צריך כדי להבין למה השרת קרס. ללוגים כמעט תמיד משתמשים ב-`"a"`.
{{< /solution >}}

### תרגיל 3: logging.exception

כתבו לולאה שמחייבת רשימת תשלומים. כל סכום הוא טקסט; סכום שאינו מספר נרשם ללוג עם **כל פרטי השגיאה**, והלולאה ממשיכה. בסוף מודפס הסכום הכולל.

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

**פלט צפוי:**

```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` נקראת **בתוך** `except`. היא רושמת ברמת ERROR, ומוסיפה ללוג את ה-**Traceback המלא** — כך אפשר לראות בדיוק איפה ולמה הסכום נכשל.
{{< /solution >}}

### תרגיל 4: מה לרשום ומה לא

לכל פריט — לרשום ללוג או לא? הסבירו.

1. מספר ההזמנה שנכשלה.
2. הסיסמה שהמשתמש הקליד, כשהכניסה נכשלה.
3. שם המשתמש שניסה להתחבר.
4. מספר כרטיס האשראי המלא.
5. כמה זמן לקחה פעולה איטית.

{{< solution >}}
1. **לרשום** — בלי זה אי אפשר לחקור את התקלה.
2. **לא לרשום** — אסור שסיסמאות יופיעו בלוג. כל מי שיכול לקרוא את הלוג היה רואה אותן.
3. **לרשום** — חשוב לאבטחה: מי ניסה להתחבר, ומתי.
4. **לא לרשום** — מידע רגיש. לכל היותר ארבע ספרות אחרונות.
5. **לרשום** — עוזר למצוא בעיות ביצועים.

הכלל: מספיק פרטים כדי **לחקור** — אבל אף פעם לא מידע **רגיש**.
{{< /solution >}}

### תרגיל 5: יומן כניסות

נתונה רשימת ניסיונות כניסה. כל ניסיון הוא שם משתמש וסיסמה (שתי רשימות מקבילות). הסיסמה הנכונה של כל המשתמשים בתרגיל היא `"1234"`. רשמו ללוג `security.log`:

- INFO על כל כניסה מוצלחת;
- WARNING על כל סיסמה שגויה (בלי לרשום את הסיסמה!).

בסוף הדפיסו כמה כניסות הצליחו וכמה נכשלו.

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

**פלט צפוי:**

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

שימו לב: ה-WARNING רושם **את שם המשתמש** — לא את הסיסמה.
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- `basicConfig(filename="app.log")` כותב לוגים לקובץ על ה-Storage, והם נשמרים אחרי שהתוכנית נגמרת.
- `filemode="a"` (ברירת מחדל) מוסיף. `"w"` דורס. בשרתים, תמיד הוספה.
- `logging.exception("...")` בתוך `except` רושם ERROR **עם Traceback מלא**, בלי שהתוכנית קורסת.
- מתעדים התחלה/סיום, שינויים במידע, אבטחה ושגיאות. לא מתעדים סיסמאות ומידע רגיש.
- הקובץ נוצר ב-Current Working Directory. `tail -f` / `Get-Content -Wait` עוקבים אחריו בזמן אמת.

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

## דוקומנטציה רשמית

- [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) — filename, level ו-format
- [logging.exception()](https://docs.python.org/3/library/logging.html#logging.exception) — רישום שגיאה עם ה-Traceback

<!-- docs:end -->
