Logging ב': שמירת Logs לקובץ ותיעוד פעולות חשובות
- להגדיר Logging שכותב לקובץ ולהבין למה הלוג “שורד” בין הרצות
- להבחין בין מצב הוספה (append) לבין מצב דריסה (write)
- לרשום Exception כולל Traceback מלא עם
logging.exception - להחליט אילו פעולות חשוב לתעד ובאיזו רמה
מה נלמד?
- איך כותבים לוגים לקובץ, ולא רק למסך.
- מצבי כתיבה: להוסיף לסוף הקובץ, או להתחיל מחדש.
- איך רושמים שגיאה עם כל הפרטים (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 |
תחביר
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:
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")במסך:
Done. Check app.logבקובץ app.log (אחרי שתי הרצות):
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:
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)במסך:
Batch done. Total: 200.5 | Failed: 2ב-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 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 כשרוצים לחקור.
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) היא עדיין שלכם.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: לוג לקובץ
הגדירו לוג שנשמר לקובץ shop.log, ברמה INFO, עם זמן, רמה והודעה, ושלא נמחק בין הרצות. רשמו בו:
- INFO כשנוצרת הזמנה מספר 17;
- WARNING כשבמלאי נשארו 2 יחידות בלבד.
הצג פתרון
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
תרגיל 2: append מול write
- מה ההבדל בין
filemode="a"ל-filemode="w"? - שרת קרס בלילה והופעל מחדש אוטומטית. הלוג הוגדר עם
"w". מה הבעיה?
הצג פתרון
"a"(append) — מוסיף לסוף הקובץ הקיים."w"(write) — מוחק את הקובץ ומתחיל מחדש בכל הרצה.- עם
"w", ההפעלה מחדש מחקה את הלוג — בדיוק את המידע שהיה צריך כדי להבין למה השרת קרס. ללוגים כמעט תמיד משתמשים ב-"a".
תרגיל 3: logging.exception
כתבו לולאה שמחייבת רשימת תשלומים. כל סכום הוא טקסט; סכום שאינו מספר נרשם ללוג עם כל פרטי השגיאה, והלולאה ממשיכה. בסוף מודפס הסכום הכולל.
payments = ["120", "abc", "80"]פלט צפוי:
Total charged: 200הצג פתרון
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 נקראת בתוך except. היא רושמת ברמת ERROR, ומוסיפה ללוג את ה-Traceback המלא — כך אפשר לראות בדיוק איפה ולמה הסכום נכשל.
תרגיל 4: מה לרשום ומה לא
לכל פריט — לרשום ללוג או לא? הסבירו.
- מספר ההזמנה שנכשלה.
- הסיסמה שהמשתמש הקליד, כשהכניסה נכשלה.
- שם המשתמש שניסה להתחבר.
- מספר כרטיס האשראי המלא.
- כמה זמן לקחה פעולה איטית.
הצג פתרון
- לרשום — בלי זה אי אפשר לחקור את התקלה.
- לא לרשום — אסור שסיסמאות יופיעו בלוג. כל מי שיכול לקרוא את הלוג היה רואה אותן.
- לרשום — חשוב לאבטחה: מי ניסה להתחבר, ומתי.
- לא לרשום — מידע רגיש. לכל היותר ארבע ספרות אחרונות.
- לרשום — עוזר למצוא בעיות ביצועים.
הכלל: מספיק פרטים כדי לחקור — אבל אף פעם לא מידע רגיש.
תרגיל 5: יומן כניסות
נתונה רשימת ניסיונות כניסה. כל ניסיון הוא שם משתמש וסיסמה (שתי רשימות מקבילות). הסיסמה הנכונה של כל המשתמשים בתרגיל היא "1234". רשמו ללוג security.log:
- INFO על כל כניסה מוצלחת;
- WARNING על כל סיסמה שגויה (בלי לרשום את הסיסמה!).
בסוף הדפיסו כמה כניסות הצליחו וכמה נכשלו.
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]פלט צפוי:
Successful: 2, failed: 2הצג פתרון
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: 2שימו לב: ה-WARNING רושם את שם המשתמש — לא את הסיסמה.
סיכום
basicConfig(filename="app.log")כותב לוגים לקובץ על ה-Storage, והם נשמרים אחרי שהתוכנית נגמרת.filemode="a"(ברירת מחדל) מוסיף."w"דורס. בשרתים, תמיד הוספה.logging.exception("...")בתוךexceptרושם ERROR עם Traceback מלא, בלי שהתוכנית קורסת.- מתעדים התחלה/סיום, שינויים במידע, אבטחה ושגיאות. לא מתעדים סיסמאות ומידע רגיש.
- הקובץ נוצר ב-Current Working Directory.
tail -f/Get-Content -Waitעוקבים אחריו בזמן אמת.
דוקומנטציה רשמית
- Logging HOWTO — Logging to a file — שמירת לוג לקובץ
- logging.basicConfig() — filename, level ו-format
- logging.exception() — רישום שגיאה עם ה-Traceback