עקיבה עם OpenTelemetry ו-Jaeger

עקיבה, רכיב קריטי, מעקבת אחר בקשות דרך מערכות מורכבות. תצוגה זו חושפת בקבוקי צוואר ושגיאות, ומאפשרת פתרונות מהירים יותר. בפוסט קודם של הסדרה שלנו על שירותי אינטרנט ב-Go, חקרנו את החשיבות של נראות. היום, אנחנו מתמקדים בעקיבה. Jaeger אוספת, שומרת ומציגת עקבות ממערכות מבוזרות. היא מספקת תובנות קריטיות לגבי זרימת בקשות בין שירותים. על ידי שילוב Jaeger עם OpenTelemetry, מפתחים יכולים לאחד את גישת העקיבה שלהם, ולבטח נראות עקבית ומקיפה. שילוב זה מפשט אבחון של בעיות ביצועים ומשפר את אמינות המערכת. בפוסט הזה, נערוך הגדרות ל-Jaeger, נשלב אותה עם OpenTelemetry באפליקציה שלנו, ונחקור הצגה של עקבות לתובנות עמוקות יותר.

מוטיבציה

מה שאנחנו עובדים בכיוון אליו הוא לוח בקרה של Jaeger שנראה כך:

כאשר אנחנו הולכים לחלקים שונים של האפליקציה (בחזית של Onehub), העקבות של הבקשות השונות נאספות (מהנקודה של הפגיעה ב-grpc-gateway) עם סיכום של כל אחת מהן. אפילו נוכל לחדור לאחת העקבות לתצוגה מפורטת יותר. תראו את הבקשה הראשונה POST (ליצירה/שליחת הודעה בנושא):

כאן אנחנו רואים את כל הרכיבים שהבקשה Create נוגעת בהם יחד עם זמני הכניסה/יציאה שלהם והזמן שנלקח בתוך ומחוץ לשיטות. מאוד חזק אכן.

התחלה

TL;DR: כדי לראות את זה בפעולה ולאמת את שאר הבלוג:

  1. מקור זה בענף PART11_TRACING.
  2. בונה את כל הדברים הנדרשים (אחרי שהוצאת את הענף):
make build

  1. שברנו את docker-compose לשני חלקים (על זה נדבר בהמשך), אז ודא שיש לך שתי חלונות פעולה.
  • טרמינל 1: make updb dblogs
  • טרמינל 2: make up logs
  1. עבור localhost:7080 וזה כל הדרך.

סקירה גבוהה ברמה

המערכת שלנו ברגע זה:

עם הדגמציה של OpenTelemetry, המערכת שלנו תהפך ל:

כפי ששמענו קודם, לשירות כל אחד להשתמש בלקוחות נפרדים כדי לשלוח לספקים ספציפיים זה די מאתגר. במקום זאת, עם משדר של OTel שמופעל באופן נפרד, אנחנו יכולים לוודא שכל (השירותים המעניינים) יכולים פשוט לשלוח מדדים/מעברים/עקבות למשדר זה, שאז יכול להעביר אותם למערכות הרצאה הנדרשות — במקרה זה, Jaeger עבור עקבות.

בואו נתחיל.

הגדרת משדר הOTel

הצעד הראשון הוא להוסיף את המשדר הOTel שמופעל בסביבת Docker ביחד עם Jaeger כך שהם נגישים.

הערה: שברנו את ההגדרה המקיפה המקורית שלdocker-compose.ymlלשני חלקים:

  1. db-docker-compose.yml: מכיל את כל הרכיבים הקשורים לבases ולמערכות התשתית (שאינם שירותים יישומיים) כמו בases (Postgres, Typesense) ושירותים הנערכים על מנת השגחה (מ
  2. docker-compose.yml: מכיל את כל השירותים הקשורים ליישום (Nginx, gRPC Gateway, dbsync, frontend וכו')

שני סביבות docker-compose מחוברות על ידי רשת משותפת (onehubnetwork) שדרכה שירותים בסביבות אלו יכולים לתקשר אחד עם השני. עם הפרדה זו, אנו רק צריכים לאתחל מחדש תת-קבוצה של שירותים עם שינויים, מה שמאיץ את הפיתוח שלנו.

חזרה להגדרתנו: ב-db-docker-compose.yml שלנו, הוסף את השירותים הבאים:

YAML

 

פשוט מספיק, זה מגדיר שני שירותים בסביבת ה-Docker שלנו:

  1. otel-collector: הכלי של כל האותות (מדדים/יומנים/עקבות) שנשלחו על ידי השירותים השונים שנפקחים (נמשיך להוסיף לרשימה הזו במשך הזמן), הואיל), הוא משתמש בתמונת OTel הסטנדרטית יחד עם הגדרת OTel המותאמת אישית שלנו (להלן) שמתארת צינורות תצפית שונים (כלומר, איך אותות צריכים להתקבל, לעובד ולייצא בדרכים שונות).
  2. jaeger: המקרה שלנו של Jaeger שיקבל ויאחסן עקבות (מיוצאות על ידי otel-collector), זה מארח הן את האחסון והן את הלוח המוצג (UI) שנייצא בקידומת הנתיב HTTP /jaeger כדי להיות נגיש דרך nginx.
  3. prometheus: אף על פי שאינו נדרש לפוסט הזה, נייצא גם מדדים כך שניתן יהיה לגרוף אותם על ידי Prometheus. לא נדון בפרט בפוסט הזה.

כמה דברים לשים לב:

  • אף על פי שאינו נדרש לפוסט הזה, אנו מעבירים את פרטי החיבור של POSTGRES (כמשתני סביבה) ל-otel-collector כך שיוכל לגרוף מדדי בריאות של Postgres.
  • ג'ייגר (מאחר ו1.35) מסupports OTLP באופן טבעי.
  • היופי של OTLP הוא שקולטי OTel יכולים להיות חוצים בצורה מעגלית וליצור רשת של קולטים/מעבדים/מעבירים/נתבים של OTel.
  • OTLP יכול להיות משורה באמצעות קצת GRPC או HTTP (על נמלים 4317 ו4318 בהעדר).
  • בהתבסס, השירותים של OTLP מותחים בlocalhost:4317/4318. זה נוראי אם Jaeger נרץ על אותה מאשה/פוד בה מופעלים השירותים המנוטרים. אך בגלל שJaeger נרץ על פוד נפרד, הם חייבים להיות קשורים לכתוביות חיצוניות (0.0.0.0). זה לא היה ברור במדריך ההגדרות וזה הוביל להתמודדות משמעותית עם השיער.
  • COLLECTOR_OTLP_ENABLED: true הוא עכשיו הבריר ולא צריך להיות מסומן בבירור.

הגדרות OTel

OTel גם צריך להיווצר עם קולטים, מעבדים ומספקים ספציפיים. אנחנו נעשה את זה בconfigs/otel-collector.yaml.

הוספת קולטים

אנחנו צריכים לספר לקולט של OTel איזה קולטים צריכים להיות מופעלים. זה מסוגל להיות ספציפי באחד המחלקות receivers:

YAML

 

זוהי פעולה שמעוררת קולטן OTLP על פורטים 4317 ו-4318 (grpc, http בעצם). קולטנים רבים בעלי סוגים שונים יכולים להתחילו. כמו דוגמה, גם הוספנו קולטן "postgresql" שיסקוף פעם פעמית את המדגם של Postgres עבור מדגמים (למרות שזה אינו רלוונטי למשוב הזה). קולטנים יכולים גם להיות בעלי עיסוק בדרך משוך או של הולך-אל, קולטנים בעלי עיסוק בדרך משוך מתקבלים במחזורים וסקוף מטרות ספציפיות (לדוגמה, postgres), בעוד קולטנים בעלי עיסוק בדרך של הולך-אל מקשיבים ומקבלים מדגמים/לוגים/עוקבות מיישמים בעזרת את הSDK הלקוחותי של OTel.

זה הכל. עכשיו האוסף שלנו מוכן לקבל (או לסקוף) את המדגמים הנראים הנכונים.

הוסף מעבדים

מעבדים בOTel הם דרך לשנות, מפית, להרכיב, לסנן ואולי למלא עוד את האותות שאנחנו מקבלים לפני שליחתם. לדוגמה, מעבדים יכולים לסקוף מדגמים, לסנן לוגים או אפילו להרכיב אותם לביצועים יעילים. במצב ברת המצב, אין מעבדים נוספים (עם זאת, האוסף הוא דרך דרך ברת המצב). אנחנו נתעלם מזה עכשיו.

הוסף משדרים

עכשיו הגיע הזמן לזהות לאן אנחנו רוצים שהאותות ייוצאו: בקצוות אחוריים שהכי מתאימים לאותות השונים. כמו מקלטים, גם מייצאים יכולים להיות בעלי משיכה או דחיפה. מייצאים בעלי דחיפה משמשים לשליחת אותות למקלט אחר שפועל במצב דחיפה. אלה הם יוצאים. מייצאים בעלי משיכה חושפים קצוות שניתן לגרד על ידי מקלטים אחרים בעלי משיכה (לדוגמה, prometheus). נוסיף מייצא מכל סוג: אחד לעקוב ואחד שפרומתאוס יגרד ממנו (אף על פי שפרומתאוס אינו נושא הפוסט הזה):

YAML

 

כאן יש לנו מייצא ל-Jaeger שרץ את האוסף OTLP, כפי שמצוין על ידי otlp/jaeger. מייצא זה ידחוף עקבות באופן קבוע ל-Jaeger. אנחנו גם מוסיפים קצה "גורד" בפורט 9090 שפרומתאוס יגרד ממנו באופן קבוע.

המייצא "debug" פשוט משמש לפליטת אותות לזרמי פלט/שגיאה סטנדרטיים.

הגדרת צינורות

החלקים מקלט, מעבד ומייצא פשוט מגדירים את המודולים שיופעלו על ידי האוסף. הם עדיין לא מופעלים. כדי להפעיל/להפעיל אותם באמת, עליהם להיות מופנים כ"צינורות". צינורות מגדירים איך אותות זורמים ומעובדים על ידי האוסף. הגדרות הצינורות שלנו (בחלק services) יבהירו זאת:

YAML

 

כאן אנחנו מגדירים שני צינורות. שימו לב כמה דומים הצינורות אך מאפשרים שתי מצבי ייצוא שונים (Jaeger ו-Prometheus). עכשיו אנחנו רואים את כוחו של OTel וביצירת צינורות בתוכו.

  1. traces:

  • קבלת אותות מ-SDKs של לקוחות
  • ללא עיבוד
  • עוברת העקבות ל控制台 ו Jaeger
  1. metrics:
  • מקבלת אותן מ SDK הלקוחות
  • אין עיבוד
  • עוברת המדידות ל控制台 ו Prometheus (על ידי חיצוף קו מסע לזה).

חיצוף לוחות דרך Nginx

Jaeger מספק לוח עבור הדמות נתוני העקבות על כל הבקשות שלנו. זה ניתן להסתכל בדפדפן על ידי הפעלת אחד הדברים הבאים ב ערימת Nginx שלנו. שוב, למרות שזה לא נושא המאמר הזה – אנחנו גם מחיצים את מסך השימוש של Prometheus דרך nginx בתווך הפתח הראשי לנתונים HTTP /prometheus.

YAML

 

הדמות עקבות ב Jaeger

מסך העברת העקבות ב Jaeger הוא די ממושך ויש בו מספר תכונות שאתה יכול לחקור. ניווט למסך העברת בדפדפן. תראו מסך מקיף לחיפוש וניתוח עקבות. ניתן לחפש עקבות מסויימים באמצעות קריטריונים ולסנן על פי שירות, תקופות זמן, רכיבים וכו'.

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

אינטגרציית SDK הלקוחות

עד כה, הקוד שלנו מוגדר עבור הסוגים הבאים של תפקידים: חישוף וצריכה של אותם אותות. אך השירותים שלנו עדיין לא עודכנו עבור פלטת האותות ל-OTel. בדברים האלה אנחנו נשלב עם ה-SDK הלקוחותי (Golang) בחלקים מסויימים של הקוד שלנו. המדריך ל-SDK SDK הוא מקום נהדר בו לראשונה תהיה מוכרחה להתמודד עם חלק מהתפיסות.

התפיסות המפתחים שאנחנו נתמודד עם מופיעות למטה.

משאבים

משאבים הם היצירה שמייצרת את האותות. במקרה שלנו, המגבלה של המשאב היא הייצור הבינארי של השירותים. כרגע, יש לנו משאב יחיד עבור כל השירות Onehub, אך יכול להיות מפורץ מאוחר יותר.

זה מוגדר בcmd/backend/obs.go. שימו לב שהSDK לקוחות לא צריך מאיתנו להיכנס לפרטים של ההגדרה של המשאב באופן מובהק. הסיוע הסטנדרטי (sdktrace.WithResource) מאפשר לנו ליצור הגדרה למשאב על-ידי הבנה של החלקים הכי מועילים (כמו שם תהליך, שם תיבת פולים וכו') בזמן ההתנהגות.

אנחנו נצטרך רק להחליף דבר אחד: המשתנה הסביבתי OTEL_RESOURCE_ATTRIBUTES: service.name=onehub.backend,service.version=0.0.1 עבור השירות onehub ב-docker-compose.yml.

המשך…

התעברות ההקשריםהיא נושא חשוב מאוד באובסביליביליות. העמודים השונים מסוגלים להיות חזקים במעלה בהיכולתנו לקשר אותם בין עצמם בזמן זיהוי הבעיות במערכת שלנו. תחשבו על ההקשרים כעל ביטים נוספים שיכולים להיות מוצגים בקשר לאותם אותות: למשל, יכולים להיות "מחברים" בדרך ייחודית לתפקיד הקשר בין האותות השונים לקבוצה מסוימת (לדוגמה, בקשה).

ספקים/מוציאים

עבור כל אחד מהאותות, אוטל מספק משף פועל (למשל, TracerProvider עבור ייצא החלקים/העקבות, MeterProvider עבור ייצא המדדים, LoggerProvider עבור ייצא המעברים, וכך הלאה). עבור כל אחד מהמשפים האלה, יכולים להיות מספר הימושים, למשל, ספק דיבוג לשליחת לזרימות stdout/err, ספק אוטל עבור ייצא לנקודת קשר אוטל נוספת (בשרשרת), או אפילו ישירות דרך מגוון של מוציאים. אך במקרה שלנו, אנחנו רוצים לדחות את הבחירה של כל ספקים מחוץ לשירותים שלנו ובמקום זה לשלוח את כל האותות למשלהב אוטל שמקיים בסביבת ההתנהגויות שלנו.

כדי להסתיר את הקשר הזה ניצור סוג "OTELSetup" שישמר על המשפים השונים שאולי נרצה להשתמש או להחליף. ב cmd/backend/obs.go, יש לנו:

Go

 

זוהי קידמה פשוטה שמקישה אחר היבטים רגולריים בהם צריך לשימוש ב SDK OTel. כאן ישנם ספקנים (סירוף דיבור, סירוף עיסקים, ומדדים) בנוסף לדרכים לספק הקשר (עבור עיסקים). המשאבה הכי גדולה שמשמשת כל הספקנים גם מסוגלה כאן. פונקציות הכיבוי מענינות. הן פונקציות שמוצעות על ידי הספקנים כשהאקספורטר הבסיסי נסגר (באופן מלאך או בגלל יציאה). עצמת הקידמה הזו מקבלת מבנה גנרי כך שמייצרים מותאמים לעצמם יכולים להשתמש במידע המותאם להם.

המאגר מכיל שתי ביצועים של זה:

אנחנו ניצור את השני בתוכנה שלנו. לא נדבר על פרטי הביצועים הספציפיים בגלל שהם נלקחו מהדוגמאותב SDK עם תיקון קטן ורפאקציה. בעיקר, תסתכלו על otel-collector הדוגמה להשראה.

מתקין ספקנים אוטל.

המהות של הפעילות המאפשרת לאסף בשירותים שלנו היא שסוג כלשהו של "ההקשר המקורי" של OTel מותחדש בכל נקודת הכניסה. אם ההקשר נוצר בהתחלה, הוא יישלח לכל המטרות המוצגות כאן, שאחר כך יתפשט (כל עוד אנחנו מבצעים את הדבר הנכון).

בואו נחשוב על הפעילות הפשוטה של ListTopics בקשת API (api/vi/topics), הבקשה שלנו לוקחת את המסלול הבא וחזרה:

[ Browser ] ---> [ Nginx ] ---> [ gRPC Gateway ] ---> [ gRPC Service ] ---> [ Database ]

במקרה שלנו, הנקודות הכניסה כאן הן בהתחלה כשהגרPC גייטד מקבל בקשת API מ Nginx (אנחנו יכולים להתחיל לעקוב אחריהם מהנקודה בה הבקשת HTTP מגיעה ל Nginx על מנת להדגיש את הטרחה ב Nginx, אך נשאר על זה לרגע).

מה שנדרש:

  • הגרPC גייטד מקבל בקשה.
  • הוא יוצר מבנה "מותאם" של context.Context של OTel.
  • הוא יוצר קישור מותאם אל השירות הגרPC הנאמר (לדוגמה, TopicService) ומעביר את ההקשר הזה במקום הברירת מחדל.
  • השירות הנאמר ישתמש בהקשר הזה כשהוא מפרץ את המעקבים.

בואו נעבור דרך את הצעדים האלה בדיוק.

התחלת והכנה של ה SDK של OTel לשימוש

ב main.go, בואו נתחיל בהתחלת החיבור לאסף:

Go

 

  • שורות 8-16: אנו יוצרים חיבור לotel-collector שרץ בסביבת Docker שלנו.
  • שורות 17-21: אנו מתקינים את ההגדרות של OTel עם טרייסר וספקי מטריקות, כך שהקולקטור שלנו יוכל עכשיו לדחוף את כל הטרייסים והמטריקות (נזכרו שהגדרנו קולטים בקונפיג של OTel)).
  • שורות 23-25: הגדרנו סיומי חיבורים לניקוי חיבורים וספקים של OTel בעת סגירה.
  • שורה 27: הגדרנו את ה-DB והחיבורים כמקודם.
  • שורות 29+: בעבר, התחלנו את שירותי GRPC ו-Gateway ברקע ולא היינו מעוניינים במיוחד בסטטוס החזרה או היציאה שלהם. למערכת עמידה יותר, חשוב להיות בעלי הבנה טובה יותר בנוגע למחזור החיים של השירותים שאנו מתחילים. כך, עכשיו אנו מעבירים את ערוץ ה"callback" לכל אחד מהשירותים שאנו מתחילים. כאשר השרתים יוצאים, השיטות הרלוונטיות י�ראו בחזרה על אותם ערוצים הזמינים להם כדי להודיע שהם יצאו בצורה חלקה. הבינארי שלנו יסתיים כאשר אחד מהשירותים הללו יצא.

כדוגמה, בואו נראה כיצד שירות הגייטוויי שלנו מנצל את הערוץ הזה.

במקום להתחיל את שרת HTTP (לגייטוויי-GRPC) כ:

1    http.ListenAndServe(gw_addr, mux)

עכשיו יש לנו:

Go

 

תשומת לב לשורות 9-14 שבהן כיבוי השרת נצפה בגורוטין נפרד ושורה 15 שבה אם הייתה שגיאה כאשר השרת יצא, היא נשלחת בחזרה דרך ערוץ ה"notification" שהועבר כארגומנט לשיטה הזו.

עכשיו, לחלקים השונים של השירותים שלנו יש גישה לקישור "פעיל" OTLP שישמש בעת שהולידות רצופות יוצאות.

OTel Middleware לגרPC Gateway

למעלה, המיקסים http.Server שמשמשים כדי להתחיל את הגרPC Gateway משתמשים במנגן מותאם: ה http.Handler בערך מעבר את התוכנית OTel HTTP. מנגן זה לוקח מיקסים http.Handler קיים, מדורף אותו עם הקונTEXT OTel ומובטח שהוא יתפסף אל כל מיקסים הדרוך הלאומי האחרים שמתקרבים.

Go

 

המנגן שלנו לHTTP פשוט:

  • שורה 4: אנחנו יוצרים מעטף מיוחד OTel כדי לטפל בבקשות HTTP חדשות.
  • שורה 5: אנחנו מעלים את האפשרות SpanFormatter כך שהעותקים יכולים להיות זוהרים באופן ייחודי על ידי השיטה והמסלולים הHTTP של הבקשות. בלילד המנגן SpanNameFormatter זה, השמות הבריריים של העותקים שלנו בגאטאווד יהיו פשוט "gateway", וכך כל העותקים יראו כך:

קישור מעטף גאטאווד לבקשות gRPC עם OTel

למעשה, ספרי הגרPC Gateway מיציאים "פשוט" קונTEXT כשהם מקימים/מנהלים קישורים לשירותים הבסיסיים GRPC. אחרי הכל, הגאטאווד לא יודע מה על OTel. במצב זה, קישור (מהמשתמש/הדפדפן) אל הגאטאווד והקישור מהגאטאווד אל השירות הGRPC יהיו טיפוסים של שני עותקים שונים.

אז חשוב להסיר את האחריות של

לפני אינטגרציה עם OTel, היינו רושמים ג'ייטווי הנדלר ל-gRPC שלנו עם:

Go

 

עכשיו, מעבר לחיבור אחר הוא פשוט:

Go

 

מה שעשינו הוא יצירת לקוח (שורה 6) שמשמש כמפעל חיבורים לשרת gRPC שלנו. הלקוח הוא פשוט. רק הנדלר gRPC (otelgrpc.NewClientHandler) משמש ליצירת החיבור. זה מבטיח שההקשר בעקבה הנוכחית שהחלה בבקשת HTTP חדשה עכשיו מועבר לשרת gRPC דרך הנדלר הזה.

זהו. עכשיו אנחנו צריכים לראות את הבקשה החדשה לג'ייטווי ואת הבקשה gRPC->Gateway בעקבה מאוחדת אחת במקום שתי עקבות שונות.

תחילת וסיום ספאנים

אנחנו כמעט שם. עד כה:

  • אנחנו הפעילנו את אוסף OTel ו-Jaeger לקבלת ואחסון נתוני עקבה (span) (ב-docker-compose).
  • אנחנו הגדרנו את אוסף OTel הבסיסי (רץ כפוד נפרד) כ"ספק" של מעקבים, מדדים ולוגים (כלומר, אינטגרציית OTel של האפליקציה שלנו תשתמש בנקודת קצה זו כדי להפקיד את כל האותות).
  • אנחנו עטפנו את הנדלר HTTP של הג'ייטווי כדי לאפשר OTel כך שעקבות והקשרים שלהן נוצרו והועברו.
  • אנחנו החלפנו את הלקוח (gRPC) בג'ייטווי כך שעכשיו הוא עוטף את ההקשר OTel מההגדרות OTel שלנו במקום להשתמש בהקשר הברירת מחדל.
  • אנחנו יצרנו מעקבים גלובליים, מדדים ולוגרים כך שאנחנו יכולים לשלוח אותות אמיתיים באמצעותם.

אנו צריכים לשלח ספנים לכל המקומות ה"מעניינים" בקוד שלנו. לדוגמה, ניקח את השיטה `ListTopics` (בקובץ `services/topics.go`):

Go

 

אנו קוראים לבסיס הנתונים להביא את הנושאים ולהחזיר אותם. בדומה לשיטת גישה לבסיס הנתונים (בקובץ `datastore/topicds.go`):

Go

 

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

התוספות שלנו לשיטות השירות ובסיס הנתונים (בהתאמה) הן:

  • services/topics.go:
Go

 

  • "`go
    func (s *TopicService) ListTopics(ctx context.Context, req *protos.ListTopicsRequest) (resp *protos.ListTopicsResponse, err error) {
    ctx, span := Tracer.Start(ctx, "ListTopics")
    defer span.End()
    // … שאר הקוד לשאיבת הנושאים מהבסיס והחזרת התגובה
    }
    "`

    datastore/topicds.go:

Go

 

"`go
func (tdb *OneHubDB) ListTopics(ctx context.Context, pageKey string, pageSize int) (out []*Topic, err error) {
_, span := Tracer.Start(ctx, "db.ListTopics")
defer span.End()
// … שאר הקוד לשאיבת השורות מהבסיס והחזרתן
}
"`

הדפוס הכללי הוא:

1. **יצירת ספן** :

ctx, span := Tracer.Start(ctx, "<span name>")

כאן, ההקשר הנתון (`ctx`) מורכב והקשר חדש מוחזר. אנו יכולים (וצריך) להעביר את ההקשר החדש הזה לשיטות נוספות. אנו עושים דבר זה כשאנו קוראים לשיטת `ListTopics` של בסיס הנתונים.

2. **סיום הספן** :

defer span.End()

סיום הספן (כאשר השיטה מחזירה) מוודא שהזמנים הסופיים, קודים וכדומה נרשמים נכונה. אנו יכולים גם לעשות דברים אחרים כמו הוספת תגים וסטטוסים לספן אם יש צורך לספק מידע נוסף לסיוע באבחון.

זהו. אתה יכול לראות את העקבות היפות שלך ב-Jaeger ולקבל יותר ויותר מבטים על ביצועי הבקשות, מקצה לקצה.

**מסקנה**

אנחנו עשינו הרבה בפוסט הזה ועדיין רק קצת חתך על כל הפרטים מאחוריהם בOTel ובמעקב. במקום להעלות את המאמץ בפוסט הזה (שכבר מאובטח), אנחנו נציג רעיונות חדשים ופרטים מורכבים יותר בפוסטים הבאים. עכשיו, ניסוי זה בשירותים שלך וננסה לשחק עם מקבלים ואקספורטרים אחרים באוסף otel-contrib.

Source:
https://dzone.com/articles/tracing-with-opentelemetry-and-jaeger