Menu

slog ו-log ב-Golang: לוגים מובנים ב-Go

איך כותבים לוגים ב-Go: חבילת log הקלאסית עם ה-flags שלה ו-log.Fatal, ו-log/slog (Go 1.21) ללוגים מובנים עם רמות, מאפייני key-value, handlers של טקסט ו-JSON, ו-loggers שנושאים הקשר עם With.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

slog בדוגמה אחת

log/slog (Go 1.21) כותבת רשומות מובנות: הודעה, רמה ומאפייני key-value.

כל שורה יוצאת כ-time=... level=INFO msg="user logged in" user=ada attempts=1. מכיוון שכל ערך הוא שדה נפרד, מערכת איסוף לוגים (Loki, Elasticsearch, CloudWatch, Datadog) יכולה לסנן לפי user=ada או level=ERROR בלי ביטויים רגולריים על טקסט חופשי.

הדוגמאות בעמוד הזה כותבות ל-os.Stdout, כדי שהפלט יופיע לפי הסדר. בשירות אמיתי, לוגים בדרך כלל הולכים ל-os.Stderr, שגם אליו ה-logger ברירת המחדל כותב.

רמות

רמהערךשימוש
slog.LevelDebug-4פרטים למפתחים, כבוי ב-production
slog.LevelInfo0אירועים רגילים: התחלה, בקשה שטופלה, משימה שהסתיימה
slog.LevelWarn4משהו לא צפוי שהתוכנית טיפלה בו
slog.LevelError8פעולה נכשלה

ה-handler זורק רשומות מתחת לרמת המינימום שלו, ורמת המינימום כברירת מחדל היא Info. זו הסיבה ש-slog.Debug(...) לא מדפיס כלום עד שמגדירים handler עם Level: slog.LevelDebug. הרווחים בין הערכים משאירים מקום לרמות מותאמות אישית כמו slog.Level(2).

כדי לשנות את הרמה בזמן ריצה (מ-flag, מ-endpoint של ניהול או מ-signal), שימו slog.LevelVar באפשרויות וקראו לו Set מאוחר יותר:

var level slog.LevelVar // zero value: Info
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: &level}))
level.Set(slog.LevelDebug) // from now on, debug records are written

טקסט או JSON

slog.NewTextHandler כותב זוגות key=value, שקל לקרוא בטרמינל. slog.NewJSONHandler כותב אובייקט JSON אחד בכל שורה, הפורמט שרוב צינורות הלוגים מצפים לו. הקריאות ללוג נשארות אותו דבר; רק ה-handler משתנה.

ערכים שומרים על הטיפוסים שלהם: status הוא מספר בפלט ה-JSON ו-retry הוא בוליאני, time.Duration מודפס כ-42ms בטקסט וכננו-שניות ב-JSON, ו-error מדפיס את ההודעה שלו. ReplaceAttr הוא ה-hook לשכתוב או להסרה של מאפיינים. כאן הוא משמש להסרת ה-timestamp, ובפועל לשינוי שמות מפתחות (msg ל-message) או להסתרת ערכים.

מאפיינים (Attributes)

הצורה עם הטיפוסים הרופפים מחליפה בין מפתחות לערכים: "user", "ada", "attempts", 3. היא קצרה, ויש לה אופן כישלון אחד: מספר אי-זוגי של ארגומנטים. הערך שנשאר נרשם תחת המפתח !BADKEY. go vet תופס את זה:

./main.go:14:2: call to slog.Info missing a final value

לבטיחות טיפוסים ולקצת פחות הקצאות, השתמשו בבנאים של המאפיינים, וב-LogAttrs כשכותבים לוג בנתיב חם:

logger.Info("order placed",
	slog.Int("order_id", 1017),
	slog.String("currency", "EUR"),
	slog.Float64("total", 59.90),
	slog.Duration("took", elapsed),
)

logger.LogAttrs(ctx, slog.LevelInfo, "order placed", slog.Int("order_id", 1017))

השתמשו במוסכמת שמות אחת למפתחות בכל בסיס הקוד (user_id בכל מקום, לא userID בחבילה אחת ו-uid בחבילה אחרת). השאילתות במערכת הלוגים שלכם תלויות בזה.

With: loggers שנושאים הקשר

logger.With(attrs...) מחזירה logger חדש שמוסיף את המאפיינים האלה לכל רשומה. צרו אחד לכל בקשה או לכל משימה, וכל שורה שהוא כותב תהיה ניתנת לקישור לשאר:

כל שורה נושאת את service, version, request_id ו-user בלי לחזור עליהם בכל קריאה. slog.Group מקנן מאפיינים, וה-handler של JSON כותב אותם כאובייקט מקונן ("payment":{"amount":25,"currency":"USD"}) וה-handler של הטקסט כמפתחות עם נקודות (payment.amount=25). logger.WithGroup("db") שם כל מאפיין מאוחר יותר של ה-logger הזה תחת group.

העבירו את ה-logger של הבקשה הלאה כפרמטר או כשדה ב-struct. אפשר לשמור אותו ב-context.Context, אבל זה מסתיר את התלות; המתודות InfoContext(ctx, ...) של slog מעבירות את ה-context ל-handler, ו-handler מותאם אישית יכול להשתמש בו כדי לשלוף trace IDs.

הסתרת סודות עם LogValuer

טיפוס יכול לשלוט באופן שבו הוא נרשם ללוג על ידי מימוש slog.LogValuer. כך סיסמאות וטוקנים לא מגיעים ללוגים, לא משנה מי כותב את הערך ללוג:

User רושם רק את ה-ID ואת האימייל שלו, ו-Token שנרשם בפני עצמו מדפיס REDACTED. ה-handler קורא ל-LogValue רק כשהרשומה באמת נכתבת, כך שזה עובד גם לערכים שיקר לחשב.

חבילת log הקלאסית

log קדמה ל-slog ועדיין מתאימה לתוכניות קטנות ולסקריפטים. היא כותבת שורות ל-standard error עם prefix של תאריך ושעה:

Flagמוסיף
log.LstdFlags (ברירת המחדל)תאריך ושעה 2009/11/10 23:00:00
log.Lmicrosecondsמיקרו-שניות לשעה
log.LUTCשעה ב-UTC
log.Lshortfile / log.Llongfilemain.go:14 / הנתיב המלא
log.Lmsgprefixשם את ה-prefix לפני ההודעה במקום בתחילת השורה

שלוש פונקציות יוצאות מהתוכנית או נכנסות ל-panic, וההבדל חשוב:

  • log.Fatal, log.Fatalf, log.Fatalln מדפיסות ואז קוראות ל-os.Exit(1). קריאות defer לא רצות. השתמשו בהן ב-main לכישלונות באתחול, אף פעם לא בקוד של ספרייה או ב-handlers של בקשות.
  • log.Panic והדומות לה מדפיסות ואז נכנסות ל-panic, כך שקריאות defer רצות ואפשר לבצע recover ל-panic.
  • כל שאר הפונקציות פשוט כותבות שורה.

כדי לכתוב לוג לקובץ, פתחו אותו והעבירו אותו ל-log.New או ל-log.SetOutput; io.MultiWriter(os.Stderr, f) כותב לשניהם.

log ו-slog ביחד

slog.SetDefault(logger) הופכת את logger לברירת המחדל של פונקציות slog.Info ברמה העליונה, וגם מנתבת דרכו את הפלט של החבילה log. קריאות log.Printf קיימות בקוד שלכם או בתלויות יוצאות אז כרשומות מובנות ברמת Info:

slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
log.Printf("legacy message") // {"time":"...","level":"INFO","msg":"legacy message"}

לפני SetDefault, ה-logger ברירת המחדל של slog כותב דרך החבילה log, וזו הסיבה ש-slog.Info("hi") לבד מדפיס 2026/09/23 14:30:00 INFO hi.

כללים מעשיים

  • רשמו שגיאה ללוג או החזירו אותה, לא את שניהם. פונקציה שרושמת שגיאה ללוג ומחזירה אותה גורמת לכך שאותו כישלון נרשם בכל רמה של מחסנית הקריאות. החזירו שגיאות למעלה עם הקשר, ורשמו אותן ללוג פעם אחת במקום שבו מטפלים בהן.
  • שימו נתונים משתנים במאפיינים, לא בהודעה. logger.Info("user created", "user_id", id) מתקבץ היטב במערכת לוגים; logger.Info(fmt.Sprintf("user %d created", id)) יוצר הודעה שונה לכל משתמש.
  • לעולם אל תרשמו סודות או bodies מלאים של בקשות. השתמשו ב-LogValuer או ב-ReplaceAttr כדי להסתיר.
  • השתמשו ב-JSON ב-production ובטקסט בפיתוח. בחרו את ה-handler באתחול לפי flag או משתנה סביבה.
  • בחרו רמות בכוונה. אם הכול נרשם ברמת Error, התראות על שגיאות הופכות לרעש.

שאלות נפוצות

מה זה slog ב-Go?

log/slog היא חבילת הלוגים המובנים שנוספה לספרייה הסטנדרטית ב-Go 1.21. במקום מחרוזות מעוצבות, לכל רשומה יש הודעה, רמה (Debug, Info, Warn, Error) ומאפייני key-value, ו-handler כותב אותה כטקסט key=value או כ-JSON: slog.Info("login", "user", "ada", "attempts", 3).

איך מפעילים לוגים של debug ב-slog?

רמת המינימום כברירת מחדל היא Info, ולכן slog.Debug לא מדפיס כלום. צרו handler עם רמה נמוכה יותר והפכו אותו לברירת המחדל: slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). השתמשו ב-slog.LevelVar במקום בקבוע אם אתם רוצים לשנות את הרמה בזמן שהתוכנית רצה.

מה ההבדל בין log ל-slog ב-Go?

log כותב שורות חופשיות עם prefix אופציונלי של זמן, ואין לו רמות. slog כותב רשומות עם רמות ועם מאפייני key-value בעלי טיפוס, שמערכות איסוף לוגים יכולות לפרסר ולסנן. שניהם בספרייה הסטנדרטית; slog.SetDefault גם מנתב את הפלט של החבילה log דרך ה-handler של slog.

האם log.Fatal מריץ פונקציות defer?

לא. log.Fatal ו-log.Fatalf מדפיסות את ההודעה וקוראות ל-os.Exit(1), שמדלג על כל הקריאות שנדחו עם defer. השתמשו בהן רק ב-main או בקוד אתחול שאין בו מה לנקות. log.Panic נכנסת ל-panic במקום זה, ולכן הקריאות של defer כן רצות.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל