# Logging א': למה print אינו Logging, ורמות הלוג<no value>

## מה נלמד?

- מה זה **Log** (יומן) ולמה מערכות אמיתיות כותבות אותו.
- למה **`print` אינו Logging**.
- המודול **`logging`** של Python.
- **חמש רמות** הלוג: `debug`, `info`, `warning`, `error`, `critical`.

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

השרת שתכתבו בנושא Backend ומסדי נתונים ירוץ **בלי שאף אחד יסתכל עליו**, אולי על מחשב בענן, באמצע הלילה. מחר בבוקר לקוח יגיד "ההזמנה שלי נעלמה בלילה". איך תדעו מה קרה?

- `print` הדפיס למסך שאף אחד לא ראה, וכבר נעלם.
- ה-Debugger לא עוזר, כי הבאג קרה **אתמול**, לא עכשיו.

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

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

### מה זה Log?

**Log** הוא רצף **רשומות** (Records). כל רשומה מתארת אירוע אחד: **מתי** קרה, **כמה חמור** הוא, ו**מה** קרה.

```text
2026-10-06 09:14:02 INFO     User dana logged in
2026-10-06 09:14:30 WARNING  Login attempt with wrong password for avi
2026-10-06 09:15:11 ERROR    Payment failed for order 1042: card declined
```

### למה `print` אינו Logging

| | `print` | `logging` |
|---|---|---|
| **זמן** | אין. לא יודעים מתי זה קרה. | אוטומטי בכל רשומה |
| **חומרה** | כל ההודעות נראות אותו דבר | 5 רמות. אפשר לסנן |
| **כיבוי/הדלקה** | צריך למחוק או להוסיף שורות בקוד | משנים **הגדרה אחת**, בלי לגעת בקוד |
| **יעד** | רק למסך | מסך, **קובץ**, שרת לוגים... |
| **בשרת** | הולך לאיבוד | נשמר ונבדק אחר כך |

**כלל:** `print` הוא לתקשורת עם **משתמש** (פלט של התוכנית). `logging` הוא לתקשורת עם **מפתחים ואנשי תפעול** (מה קרה בתוך התוכנית).

### חמש הרמות

| רמה | מספר | מתי משתמשים | דוגמה |
|---|---|---|---|
| `DEBUG` | 10 | פרטים טכניים למפתח, בזמן פיתוח ודיבאג | `"cart contents: [...]"` |
| `INFO` | 20 | אירועים רגילים וחשובים — "הכול תקין" | `"user dana logged in"` |
| `WARNING` | 30 | משהו חריג, אבל המערכת ממשיכה | `"disk 90% full"`, `"retrying request"` |
| `ERROR` | 40 | פעולה **נכשלה** | `"payment failed for order 1042"` |
| `CRITICAL` | 50 | המערכת כולה בסכנה ואולי לא יכולה להמשיך | `"database unreachable - shutting down"` |

### רמה מינימלית — הסינון

מגדירים **רמה מינימלית**, ומוצגות רק הודעות **ממנה ומעלה**:

- בזמן פיתוח: `DEBUG`. רואים הכול.
- בשרת אמיתי (Production): `INFO` או `WARNING`. רק מה שחשוב, בלי "רעש".

**ברירת המחדל של Python היא `WARNING`.** לכן אם לא מגדירים כלום, הודעות `debug` ו-`info` **לא יוצגו בכלל**.

{{< box "analogy" "אנלוגיה: מערכת התראות בבית חולים" >}}
**DEBUG** = הרישום המלא של כל מדידה (רק הרופא החוקר מסתכל). **INFO** = "המטופל קיבל ארוחת צהריים". **WARNING** = "חום מעט גבוה, לעקוב". **ERROR** = "תרופה לא ניתנה בזמן". **CRITICAL** = "קוד כחול!". האחות במשמרת לילה מגדירה לעצמה לראות רק WARNING ומעלה, אחרת היא תטבע בהודעות.
{{< /box >}}

## תחביר

```python
import logging

# configure once, at the start of the program
logging.basicConfig(
    level=logging.INFO,                                  # minimum level to show
    format="%(asctime)s %(levelname)s %(message)s",      # how each record looks
)

# write log records
logging.debug("technical details")
logging.info("normal important event")
logging.warning("something unusual")
logging.error("an operation failed")
logging.critical("the whole system is in danger")
```

**שדות נפוצים ב-`format`:**

| שדה | מה מוצג |
|---|---|
| `%(asctime)s` | תאריך ושעה |
| `%(levelname)s` | שם הרמה (`INFO`, `ERROR`...) |
| `%(message)s` | ההודעה שלכם |
| `%(funcName)s` | שם הפונקציה שכתבה את הרשומה |
| `%(lineno)d` | מספר השורה |

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

`week-2/log_levels.py`:

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

logging.debug("This is a debug message")
logging.info("Application started")
logging.warning("Config file not found, using defaults")
logging.error("Could not connect to the payment service")
logging.critical("Out of memory - shutting down")
```

**פלט:**

```text
2026-10-06 09:30:12,481 INFO Application started
2026-10-06 09:30:12,481 WARNING Config file not found, using defaults
2026-10-06 09:30:12,482 ERROR Could not connect to the payment service
2026-10-06 09:30:12,482 CRITICAL Out of memory - shutting down
```

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

| שורה | הסבר |
|---|---|
| `import logging` | המודול המובנה של Python ללוגים. אין צורך בהתקנה. |
| `logging.basicConfig(...)` | הגדרה **חד-פעמית** בתחילת התוכנית: מה הרמה המינימלית ואיך נראית כל רשומה. |
| `level=logging.INFO` | מציגים רק מ-INFO ומעלה. |
| `format="..."` | תבנית הרשומה: זמן, רמה, הודעה. |
| `logging.debug(...)` | **לא מוצג!** DEBUG (10) נמוך מ-INFO (20), ולכן סונן. |
| `logging.info(...)` עד `logging.critical(...)` | מוצגים, כי הם ברמה INFO ומעלה. |
| `,481` בפלט | אלפיות שנייה. שימושי כשהרבה אירועים קורים באותה שנייה. |

**נסו:** שנו ל-`level=logging.DEBUG` והריצו שוב. עכשיו מופיעות 5 שורות. ב-`level=logging.ERROR` מופיעות רק 2. **שיניתם שורה אחת, ולא נגעתם בהודעות עצמן.** זה הכוח של רמות.

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

**לוגים בתוך תוכנית אמיתית**, משולבים עם Exceptions מהשיעורים הקודמים. `week-2/orders.py`:

```python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)-8s %(funcName)s: %(message)s",
)

STOCK = {"bread": 5, "milk": 0, "eggs": 12}


def place_order(product, quantity):
    logging.debug("place_order called with product=%s quantity=%s", product, quantity)

    if product not in STOCK:
        logging.error("Unknown product requested: %s", product)
        return False

    available = STOCK[product]
    if available == 0:
        logging.warning("Product out of stock: %s", product)
        return False

    if quantity > available:
        logging.warning("Not enough %s: requested %s, available %s", product, quantity, available)
        return False

    STOCK[product] = available - quantity
    logging.info("Order placed: %s x%s (left in stock: %s)", product, quantity, STOCK[product])
    return True


place_order("bread", 2)
place_order("milk", 1)
place_order("cake", 1)
place_order("eggs", 20)
```

**פלט:**

```text
2026-10-06 09:41:03,115 DEBUG    place_order: place_order called with product=bread quantity=2
2026-10-06 09:41:03,115 INFO     place_order: Order placed: bread x2 (left in stock: 3)
2026-10-06 09:41:03,116 DEBUG    place_order: place_order called with product=milk quantity=1
2026-10-06 09:41:03,116 WARNING  place_order: Product out of stock: milk
2026-10-06 09:41:03,116 DEBUG    place_order: place_order called with product=cake quantity=1
2026-10-06 09:41:03,116 ERROR    place_order: Unknown product requested: cake
2026-10-06 09:41:03,117 DEBUG    place_order: place_order called with product=eggs quantity=20
2026-10-06 09:41:03,117 WARNING  place_order: Not enough eggs: requested 20, available 12
```

**פירוק:**

- `%(levelname)-8s` — ה-`-8` מרפד את שם הרמה ל-8 תווים, כך שההודעות מיושרות בעמודה. נוח לקריאה.
- `%(funcName)s` — רואים **מאיזו פונקציה** הגיעה כל רשומה.
- `logging.info("... %s ...", product, quantity)` — `%s` הוא **מקום** לערך, והערכים מועברים כארגומנטים נוספים. זו הדרך המומלצת ב-logging: הטקסט נבנה רק אם ההודעה באמת תוצג, ולכן הודעות DEBUG מסוננות כמעט לא עולות כלום.
- **בחירת הרמות:** "הוזמן" הוא INFO (אירוע רגיל וחשוב). "אזל מהמלאי" הוא WARNING (העסק עובד, אבל כדאי לדעת). "מוצר לא קיים" הוא ERROR (משהו לא תקין בבקשה, אולי באג בצד הלקוח). פרטי הקריאה הם DEBUG.
- `if product not in STOCK` — `in` על dict בודק אם **המפתח** קיים.

עכשיו שנו ל-`level=logging.WARNING`: הלוג יציג רק את 3 הבעיות. כך ייראה בשרת אמיתי, שבו מעניינות רק התקלות.

## טעות נפוצה

**הטעות 1: לקרוא ל-`basicConfig` אחרי שכבר נכתבה הודעה, או פעמיים.**

```python
import logging
logging.info("starting")                     # uses the default config!
logging.basicConfig(level=logging.INFO)      # ignored - already configured
logging.info("now?")                         # still not shown
```

הקריאה הראשונה ל-`logging.info` מגדירה אוטומטית את ברירת המחדל (`WARNING`), ו-`basicConfig` מאוחר **מתעלם** בשקט. **`basicConfig` תמיד ראשון, ופעם אחת**, מיד אחרי ה-`import`.

**הטעות 2: "לא רואה את ה-info שלי".** הסיבה היא כמעט תמיד ברירת המחדל `WARNING`. הפתרון: `level=logging.INFO` (או `DEBUG`).

**הטעות 3: רמה לא נכונה.**

```python
logging.error("User dana logged in")       # not an error!
logging.info("Payment failed!")            # this IS an error
```

רמה שגויה הופכת את הסינון לחסר תועלת: מי שמסתכל רק על ERROR יפספס תשלום שנכשל, ויוצף בהתחברויות רגילות.

**הטעות 4: לרשום מידע רגיש.** `logging.info("login: user=dana password=1234")` — **לעולם לא** סיסמאות, מספרי כרטיס אשראי או טוקנים. לוגים נקראים על ידי הרבה אנשים ונשמרים זמן רב.

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

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

### תרגיל 1: איזו רמה?

לכל אירוע — באיזו רמת לוג הייתם רושמים אותו: DEBUG, INFO, WARNING, ERROR או CRITICAL?

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

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

### תרגיל 2: רמה מינימלית

```python
import logging

logging.basicConfig(level=logging.WARNING)

logging.debug("checking item 3")
logging.info("order 17 created")
logging.warning("stock is low: 2 left")
logging.error("payment failed for order 17")
```

אילו מההודעות יופיעו, ולמה? מה צריך לשנות כדי לראות את כולן?

{{< solution >}}
יופיעו רק **WARNING** ו-**ERROR**:

```text
WARNING:root:stock is low: 2 left
ERROR:root:payment failed for order 17
```

`level=logging.WARNING` הוא **רמה מינימלית**: מוצגות רק הודעות ברמה הזו ומעליה. כדי לראות את כולן משנים ל-`level=logging.DEBUG`. בלי לגעת בשאר הקוד — זה כל היתרון.
{{< /solution >}}

### תרגיל 3: מ-print ל-logging

החליפו כל `print` ברמת הלוג המתאימה, והגדירו פורמט עם זמן, רמה והודעה:

```python
def withdraw(balance, amount):
    print("withdraw called with", amount)
    if amount > balance:
        print("not enough money, balance is", balance)
        return balance
    print("withdrew", amount)
    return balance - amount
```

{{< solution >}}
```python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)-8s %(message)s",
)


def withdraw(balance, amount):
    logging.debug("withdraw called with %s", amount)
    if amount > balance:
        logging.error("not enough money, balance is %s", balance)
        return balance
    logging.info("withdrew %s", amount)
    return balance - amount


withdraw(100, 30)
withdraw(100, 500)
```

- כניסה לפונקציה — **DEBUG**, משיכה מוצלחת — **INFO**, כישלון — **ERROR**.
- הפורמט מוסיף לכל שורה זמן ורמה, למשל: `2026-11-01 10:15:02 ERROR    not enough money, balance is 100`.
{{< /solution >}}

### תרגיל 4: לקרוא שורת לוג

```text
2026-11-01 09:14:30 WARNING  Wrong password for avi
```

1. מה כל חלק בשורה, ואיזה חלק בהגדרה `format` יוצר אותו?
2. למה חשוב שיש בשורה זמן?

{{< solution >}}
1. החלקים:
   - `2026-11-01 09:14:30` — הזמן, נוצר מ-`%(asctime)s`.
   - `WARNING` — הרמה, נוצרת מ-`%(levelname)s`.
   - `Wrong password for avi` — ההודעה, נוצרת מ-`%(message)s`.
2. כשמשהו קורה בשרת בשלוש בלילה, הזמן מאפשר לשחזר **מה קרה ובאיזה סדר**, ולהצליב עם אירועים אחרים.
{{< /solution >}}

### תרגיל 5: print או logging?

לכל מקרה — print או logging? הסבירו.

1. תוכנית שמבקשת מהמשתמש את שמו ומברכת אותו.
2. שרת שרץ כל הלילה, ורוצים לדעת בבוקר אילו הזמנות נכשלו.
3. בדיקה זמנית של ערך משתנה בזמן פיתוח, שתרצו לכבות בלי למחוק.

{{< solution >}}
1. **print** — זה הפלט שהתוכנית מציגה **למשתמש**.
2. **logging** (ERROR) — מידע **למפתח**, עם זמן ורמה, שאפשר לשמור ולחפש בו.
3. **logging** (DEBUG) — אפשר לכבות את כל הודעות ה-DEBUG בשורה אחת (העלאת הרמה המינימלית), בלי למחוק קוד.

הכלל: **print למשתמש, logging למפתח.**
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **Log** = תיעוד של אירועים: מתי, כמה חמור, ומה קרה. "קופסה שחורה" של התוכנה.
- `print` = פלט למשתמש. `logging` = תיעוד למפתחים: זמן, רמה, סינון ויעדים.
- 5 רמות: **DEBUG < INFO < WARNING < ERROR < CRITICAL**. מוצגות רק הודעות מהרמה המינימלית ומעלה.
- ברירת המחדל היא **WARNING**. `basicConfig` נקרא **פעם אחת, ראשון**.
- `logging.info("x=%s", x)` היא הצורה המומלצת. ולעולם לא רושמים סיסמאות.

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

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

- [Logging HOWTO — When to use logging](https://docs.python.org/3/howto/logging.html#when-to-use-logging) — print מול logging, ואיזו רמה מתי
- [logging — Logging Levels](https://docs.python.org/3/library/logging.html#logging-levels) — debug, info, warning, error, critical

<!-- docs:end -->
