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.LevelInfo | 0 | אירועים רגילים: התחלה, בקשה שטופלה, משימה שהסתיימה |
slog.LevelWarn | 4 | משהו לא צפוי שהתוכנית טיפלה בו |
slog.LevelError | 8 | פעולה נכשלה |
ה-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.Llongfile | main.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 כן רצות.