לוגים טקסטואליים אינם קריאים: מה פותר לוגינג מובנה
האירוע נראה כך: חלק מהבקשות לשירות מחזירות שגיאה, וצריך להבין מה שונה בקריאות מוצלחות לעומת כאלה שנכשלו. הלוגים קיימים, אבל כל שגיאה נכתבת בטקסט חופשי, ואותה סיבה בדיוק נראית אחרת — "לא ניתן לעבד את הזמנת המשתמש 42, timeout" ו-"order 42 failed: timeout".
פירוק כזה צריך להיעשות בעיניים: המהנדס מרים ארכיון יומי, מריץ עליו ביטויים רגולריים, מקבל אלפי התאמות ומסנן ידנית אירועים סמוכים.
המחיר של עבודה כזו גדל יחד עם הנפח. בארכיון — גיגה-בייטים של שורות, תבנית החיפוש לא מכסה את כל הניסוחים, ואת זמן התגובה של השירות ואת קוד השגיאה צריך לשלוף מהטקסט בביטויים נפרדים.
פירוק אירוע לפי לוגים אורך שעות, השחזור מתארך, וחלק מהמסקנות נשארות בגדר השערות: לקבץ רשומות לפי קוד שגיאה או לחשב את שיעור הבקשות האיטיות לפי קובץ כזה זה אפשרי טכנית, אבל בפועל אף אחד לא עושה זאת.
לוגינג מובנה משנה את פורמט כתיבת האירועים: במקום שורה — סט שדות עם שמות וטיפוסים קבועים. חתימת זמן, רמה, שם השירות, קוד שגיאה, מזהה בקשה ומזהה trace נמצאים כערכים נפרדים, וטקסט חופשי נשאר בשדה הודעה אחד.
החיפוש בלוגים של האפליקציה הופך לשאילתה עם סינון לפי שדה: "כל הרשומות של מזהה ה-trace הזה", "כל התגובות עם קוד 500 בטווח זמן", "שיעור הבקשות שארוכות מהסף לפי כל נתיב". האספן קורא את השדות ישירות, בונה גרפים והתראות, ולא עוסק בפירוק שורות.
לוגים שאינם קריאים מפסיקים להיות הבעיה של מהנדס בודד — הם הופכים לנתונים שלפיהם מחשבת הסיבה. בהמשך נפרט כיצד זה נסגר באפליקציה ובמערך האיסוף.
האפקט בתפעול: פירוק אירועים, רעש ואחסון לוגים
לוגינג מובנה מעביר את ה-observability של השירות ממצב של "קוראים בעיניים" למצב של שאילתות לפי שדות. להלן מה בדיוק בודקים בקבלה, כדי שהמעבר יניב אפקט מדיד, ולא רק יחליף את פורמט הקבצים.
- זמן איתור התקלה: לפי מזהה הבקשה אנו אוספים את כל שרשרת הקריאות בין השירותים בבחירה אחת — התורן מוצא את הסיבה תוך דקות במקום לעבור על קבצים במספר מארחים.
- רעש בלוגים: הרמות וסט השדות החובה מוגדרים על ידי הסכימה, הודעות דיבאג לא נכנסות לזרם הייצור, ומספר הרשומות לבקשה אחת מפסיק לגדול יחד עם העומס.
- נפח אחסון הלוגים: הרמה, השירות ותוחלת החיים של הרשומה מוגדרים על ידי מדיניות, ולכן הגידול בנפח מחושב מראש, ולא מתגלה כשהדיסק מתמלא.
- פירוק התורנות: חתימת זמן, שם השירות, מזהה הבקשה וקוד התגובה כבר בתוך הרשומה — הקונטקסט של האירוע נקרא בלי גישה לשרת.
- סוג כשל שנעלם: אובדן לוגים בעת הפעלה מחדש של הקונטיינר ושירותים "שותקים" נסגרים על ידי העברה מחייבת לאספן המשותף Grafana Loki עם בדיקת זמינות של הקולט.
כל סעיף מאמתים על אירוע תרגול: מכבים שירות אחד וסופרים כמה צעדים וכמה זמן אורך המסלול מהאות ועד הסיבה. אם הסיבה נמצאת לפי שדות, בלי לקרוא טקסט גולמי — הלוגינג עובד.
לוגינג מובנה לעומת טקסטואלי: השוואת מצבים
מצב כתיבת האירועים קובע מי יהיה הצרכן של הלוג — אדם או אספן. טקסט חופשי נקרא בעיניים, אבל רק עד התיקון הראשון של הניסוח: מכאן והלאה הפירוק בצד האיסוף מפסיק להתאים להודעה, וחלק מהאירועים נופלים בשקט מהבחירה. רשומה מובנית מעבירה את הפירוק לאפליקציה — הצרכן מקבל שדות מוכנים ומסנן לפיהם, בלי לנחש את הפורמט.
את הצפיות נותנת סכימת אירוע אחידה לכל השירותים: חתימת זמן, רמת לוגינג, מזהה בקשה, שם השירות, קוד אירוע וקונטקסט מקונן. הרמות אז עובדות כמדיניות, ולא כסימון בשורה: רשומות דיבאג עוברות למערך נפרד, אזהרות ושגיאות נכנסות לזרם המשותף ולא טובעות בו.
| אפשרות | מתי מתאים | מגבלות |
|---|---|---|
| טקסט חופשי | כלי עזר חד-פעמיים, שבהם את הלוג קורא רק המחבר | אין שדות: הבחירה נבנית לפי תת-מחרוזת |
| טקסט עם תבנית "מפתח=ערך" | שירות פעיל שאי אפשר לשנות במלואו | ההסכמה על המפתחות נשמרת ידנית, מבנים מקוננים מאבדים צורה |
| סכימת שדות קבועה (אובייקט JSON לאירוע) | את האירועים מפרק האספן: חיפוש, התראות, פירוק אירועים | נדרש מרשם שדות וכלל לשינוי שלהם בין הצוותים |
| העברה בזרימה לאספן | הרבה מופעים של השירות, נדרשת תמונה כוללת מיד | ניתוק הערוץ מחייב מאגר בדיסק, אחרת אירועים אובדים |
בפועל לוקחים שילוב: שורה קריאה לאדם לקונסול בהרצה מקומית ורשומה מובנית לאספן במערכי הייצור. המעבר מתבצע לפי שירותים — מתחילים באלה שבהם פירוק האירוע אורך את הזמן הרב ביותר, ועם מרשם שדות, כדי שאירוע חדש לא יחייב תיקונים בצד הקליטה.
הטמעה שלב-שלב: מביקורת אירועים ועד העברת נהלים
הרצף בנוי כך שסכימת האירועים תופיע לפני שהלוג הראשון יעבור לאחסון: לשנות פורמט בשירותים פעילים יקר יותר מלהסכים עליו כבר בהתחלה. להלן הסדר שכל פרויקט עובר.
- ביקורת מקורות הלוגים. אוספים מלאי: אילו שירותים כותבים בעצמם, אילו מוציאים זרם ל-stdout, אצל מי האירועים מפוזרים על פני כמה זרמים וקבצים. בתפוקה — טבלה עם תדירות האירועים והנפח היומי לכל מקור.
- תכנון סכימת האירועים. קובעים סט שדות חובה: זמן, רמה, שם השירות, מזהה בקשה, קוד שגיאה, טקסט ההודעה. בנפרד — מילון ערכי הרמה וכללי שמות, כדי ש-
errorו-errלא יחיו באותו אינדקס. - הגדרת האפליקציה. הרשומה יוצאת כשורה אחת לאירוע, בלי שבירות שורה בתוך ההודעה — אחרת האספן יקרע אותה לחלקים. הקונטקסט של הבקשה מועבר דרך מעטפת משותפת ומגיע לכל רשומה אוטומטית, כולל משימות רקע.
- הגדרת אספן הלוגים. אין צורך בפירוק שדות: הניתוב מתבצע לפי ערכים, מוסיפים מאגר בדיסק והגבלת קצב. מיסוך טוקנים ונתונים אישיים מתבצע בכניסה, לפני הכתיבה.
- בדיקה על תקלה אמיתית. משחזרים שגיאה מבוקרת ועוברים את השרשרת כולה: רשומה בשירות, אספן, אחסון, חיפוש לפי מזהה הבקשה. בנפרד בודקים אם הקריאות בין השירותים נאספות לשרשרת אחת.
- בקרת איכות ונהלים. מגדירים בדיקות שגרתיות: שיעור הרשומות בלי שדות חובה, שיעור הרמות שלא זוהו, נפח הרעש מבדיקות הזמינות. מעבירים מסמך עם סכימת השדות, כללים להוספת אירועים חדשים ותקופות שמירה.
ארטיפקטים: סכימות שדות, קונפיגורציות של אספנים, נהלים
אחרי ההטמעה נשאר לצוות לא סט הגדרות על השרת, אלא ערכה של קבצים ומסמכים שלפיהם אפשר להרחיב את הלוגינג המובנה בלי תלות בנו. כל הארטיפקטים מנוהלים בגרסאות באותו מאגר של קוד השירותים: תיקון עובר ביקורת ולא חי בראש של מישהו אחר.
- מילון אירועים — שמות אירועים עם שדות חובה ורשות, טיפוסים וערכים מותרים. הוא גם קובע מה לא נכנס ללוג: טוקנים, גופי בקשות במלואם, תוכן קבצים של משתמשים.
- חוזה השדות והכללים לשינוי שלו — כיצד להוסיף ולשנות שמות שדות בלי לשבור את הפירוק באחסון ואת הדוחות שכבר נבנו.
- כללי אנונימיזציה של לוגים — מה ממוסך, מה מגובב, היכן ערך מוחלף במזהה. מוגדרים בקונפיגורציה משותפת, ולא בקוד של כל שירות, ולכן מיושמים באופן אחיד.
- קונפיגורציית איסוף הלוגים — פירוק הזרם, מאגר בצד האפליקציה, התנהגות כשהאחסון אינו זמין, רוטציית קבצים וספים שבהם רשומה נזרקת במקום לחסום את השירות.
- מדיניות אחסון הלוגים — תקופות לפי סוגי אירועים, הפרדה בין אחסון תפעולי לארכיוני, סדר המחיקה. בנפרד מצוין אילו אירועים חיים זמן רב יותר בגלל פירוק אירועים.
- הוראות לתורן — כיצד לאסוף את כל האירועים של בקשה אחת, אילו שדות לסנן ראשונים, שאילתות מוכנות לתקלות טיפוסיות וסדר ההסלמה.
- נהל שינויי הסכימה — מי מאשר תיקון במילון, כיצד נבדקת התאימות ומה עושים עם רשומות ישנות לאחר החלפת שדות.
לוחות לפי תדירות שגיאות והשהיות נבנים מעל אותם שדות — אין צורך במקור נתונים נפרד עבורם.
מקרה בוחן: פירוק האירוע התקצר משעות לדקות
לפני: שישה פורמטים, חיפוש באקראי
הפלטפורמה של הלקוח — שישה יישומים: הזמנות, חיוב, התראות, אינטגרציות, אדמין ומשימות רקע. כל אחד כתב לוגים בטקסט חופשי לקובץ משלו על הצומת שלו: הזמן והרמה נכנסו לתוך שורת ההודעה, ומזהה הבקשה לא הועבר בין השירותים.
פירוק האירוע התחיל בסיור בין הצמתים ובחיפוש אחר שברי ביטויים — עד הגרסה הראשונה של הסיבה חלפו 3-4 שעות, ושישה התראות מתוך עשר התבררו ככוזבות: הופעלו על התאמות טקסט.
מה שינו ומה קיבלו
הציגו סכימת אירוע אחידה: רמה, חתימת זמן, שירות, מזהה בקשה, קוד פעולה, משך, תוצאה. מזהה הבקשה נוצר בכניסה ומועבר לאורך כל שרשרת הקריאות הפנימיות — המסלול "בקשה → שירות → אחסון → תור → תגובה" נראה בחיפוש אחד לפי שדה, ולא בהתאמת ביטויים לשורות. האיסוף והחיפוש מתבצעים ב-OpenSearch.
כללי האחסון הוגדרו בנפרד: בדיקות זמינות וסקרים שגרתיים עברו לרמת הדיבאג ונכתבים לא בכל קריאה, המערך החם מחזיק שבועיים, ומשם הנתונים עוברים לאחסון קר. נפח הלוגים גדל בערך פי שלושה, אבל בלי הכללים האלה הגידול היה גדול פי כמה, והוצאות האחסון הפסיקו לגדול יחד עם התעבורה.
אחרי שלושה חודשים: זמן חציוני לאיתור הסיבה — 8-10 דקות במקום 3-4 שעות, התראות כוזבות — מקרה אחד מתוך עשרה במקום שישה, פי שניים יותר אירועים משמרת התורנות סוגרת בלי מפתחים. בממשק בין השירותים האפקט בולט ביותר: קישור הלוגים לפי מזהה אחיד מראה מיד איזו קריאה החזירה שגיאה ומה קדם לה.
במה שונה לוג ממטריקה ומטרייסינג?
שלושת האותות עונים על שאלות שונות, והבלבול מתחיל כשמשתמשים באחד במקום באחר. מטריקה אומרת "כמה ובאיזו תדירות": זה אגרגט על פני סדרה מספרית — שיעור הכשלים, זמן התגובה לפי אחוזון הזנב. לוג אומר "מה קרה": זה אירוע עם קונטקסט — מזהה בקשה, משתמש, פרמטרי הקריאה. טרייסינג — "היכן אבד הזמן": מסלול הבקשה דרך השירותים עם משך כל צעד.
ההבדל בין לוג למטריקה נראה בזמן הפירוק: מהגרף ברור שהכשלים גדלו אחרי הפריסה, אבל לא באיזו כניסה. המטריקה מאבדת קונטקסט בכוונה למען אחסון זול ואגרגציה מהירה. לכן למטריקה מוציאים אותות שלפיהם נבנות התראות, וללוגינג המובנה — את מה שמפרקים לפי מקרה ספציפי.
טרייסינג הבקשה מכסה את השכבה השלישית: כשהעיבוד עובר דרך כמה שירותים ותור, רשומה אחת בלוג לא תראה היכן הצטברו כמעט כל ההשהיות. מזהה ה-trace בכל רשומת לוג נותן מעבר מהגרף לקריאה מסוימת.
הטעות העיקרית — שכפול טלמטריה: עובדה אחת נכתבת בבת אחת למטריקה, ללוג ולטרייסינג. זה מנפח את האחסון, מאיץ את הפער בין המערכות ומבלבל את בחירת כלי האבחון: התורן פותח שלושה לוחות ולא מבין איזה מהם נכון. הכלל העובד — לכל עובדה יש בית אחד, שאר האותות מפנים אליו במזהה.
תקן אחיד להעברת טלמטריה — OpenTelemetry — מסיר את התלות בספק: ה-backend של ה-observability של האפליקציה מוחלף בלי לשכתב את קוד ה-instrumentation.
מגבלות ושאלות נפוצות בנוגע להטמעה
ללוגינג מובנה יש מחיר, והגון יותר לנקוב בו לפני ההטמעה. רשומת "מפתח — ערך" תופסת פי שניים-שלושה יותר מקום משורה טקסטואלית, והסריאליזציה במסלול החם גוזלת זמן מהעיבוד של הבקשה עצמה.
פותרים זאת באמצעות רמות — לא כותבים דיבאג בפרודקשן — באמצעות מאגר לרשומה והעברתה לזרם נפרד, כדי שהיומן לא יאט את התרחיש הראשי.
המגבלה השנייה — מערך לא אחיד: חלק מהשירותים כותבים בפורמט טקסטואלי ישן, והסכימה המשותפת עבורם לא נאספת עד שהאספן יפרוק אותם בכלל. השלישית — נתונים רגישים: בשדות הבקשה מופיעים באופן קבוע טוקנים, כתובות, מזהים ונתונים אישיים.
אותם ממסכים בקליטה, לפני הכתיבה לאחסון, אחרת האינדקס המשותף חושף אותם לכל מי שיש לו הרשאה לקרוא את היומן.
האם לא נאבד רשומות בזמן קפיצת עומס?
המסירה היא נקודת הכשל השקטה ביותר: המאגר של האספן מתמלא, וחלק מהרשומות אובד בלי אף שגיאה באפליקציה. לכן מחשבים לא רק את הנפח, אלא גם את תפוקת הקליטה, מפעילים מאגר בדיסק ומחזיקים את שיעור הרשומות שנזרקו כמטריקה נפרדת עם התראה.
האם יהיה צורך לשכתב שירותים ישנים?
לא. ההתאמה לסכימה המשותפת נעשית בצד האספן בכללי פירוק, את האפליקציה לא נוגעים. משכתבים רק את השירותים שממילא משתנים בלוגיקה העסקית בשנה הקרובה — שם מתקינים את הפורמט החדש בדרך אגב.
כיצד להפריד נתונים אישיים משאר השדות?
מבצעים פירוק לפי הסכימה ורשימת השדות: מה נחשב מזהה, ומה — גוף הפעולה. משם מיסוך בקליטה, זרמי אחסון נפרדים עם תקופה מקוצרת והפרדת הרשאות, כדי שהאנליטיקה תתבצע על חתכים אנונימיים.
נדון במשימה ונחזור עם הערכה ותוכנית
השיחה מתחילה מנתוני המוצא — בלעדיהם ההערכה הופכת להשערה. די לנו בקונפיגורציה הנוכחית של לוגינג האפליקציות, בדוגמאות של רשומות אמיתיות מהיומנים, בסכימת הפריסה ובמידע לאן האירועים הולכים כיום: קבצים בדיסק, stdout של הקונטיינר, אספן חיצוני.
מה נבהיר בשיחה:
- אילו שירותים וסביבות נכנסים לנפח העבודה, ואילו נשארים מחוץ למשימה;
- אילו אירועים קריטיים לפירוק תקלות, ואילו יוצרים רעש ודוחקים רשומות מועילות;
- האם נדרש מזהה בקשה חוצה-מערכתי כדי לקשר רשומות של שירותים שונים לשרשרת אחת;
- כיצד נפתרת בחברה שאלת הנתונים האישיים ביומנים ומי אחראי על מדיניות המיסוך;
- תקופות שמירה, נפח זרם האירועים והדרישות למהירות החיפוש בארכיון;
- מי מצד הלקוח מקבל את התוצאה ובאיזה אופן הוא מצפה לתיעוד.
בהמשך אנו מבצעים ביקורת של הלוגינג הנוכחי: בודקים אילו שדות נכתבים כעת, היכן אובד הקונטקסט, כיצד בנויות רמות החשיבות והרוטציה. בסיומה נחזור עם תוכנית עבודה להטמעה — סדר העברת השירותים, סכימת שדות החובה, הגדרות האספן והאחסון, מחולקת לשלבים עם תוצאה ברורה בכל אחד.
כסעיף נפרד באה העברת הידע לצוות: תיאור פורמט הרשומות, כללים להוספת שדות חדשים, תבניות מוכנות לשאילתות ליומנים ופירוק על דוגמאות חיות. תארו את משימת הלוגינג — נחזור עם הערכה ותוכנית.







