אחת השיחות הנוכחיות שלי מתמקדת בפעילות הניטור באופן כללי ובמעקב מרוחק בפרט, עם יישום OpenTelemetry. בדמו, אני מראה איך אפשר לראות את המעקבים של מערכת מרוחקת פשוטה המורכבת משער ה-API של Apache APISIX, אפליקציה Kotlin עם Spring Boot, אפליקציה Python עם Flask ואפליקציה Rust עם Axum.
מוקדם יותר בשנה, דיברתי והשתתפתי בחדר הניטור ב-FOSDEM. אחת השיחות הפגינה את ערכת Grafana: Mimir למדדים, Tempo למעקבים ו-Loki ליומנים. הופתעתי באופן נעים כיצד אפשר לעבור מאחד לאחר. לכן, רציתי להשיג את אותו הדבר בדמו שלי אך דרך OpenTelemetry כדי להימנע מקשר לערכת Grafana.
בפוסט הבלוג הזה, אני רוצה להתמקד ביומנים וב-Loki.
יסודות Loki ותוכנית הראשונה שלנו
בבסיסה, Loki היא מנוע אחסון יומנים:
Loki היא מערכת איסוף יומנים מתוחכמת, מתקצרת, ומרובת שותפים המושרשת בפרומתאוס. מתוכננת להיות יעילה מאוד כמותית וקלה לתפעול. היא אינה מאפשרת את תוכן היומנים, אלא מערכת של קיטורות לכל זרם יומנים.
Loki מספקת API רסטפול לאחסון וקריאת יומנים. בואו נדחוף יומן מאפליקציה Java. Loki מצפה למבנה התפוקה הבא:
I'll use Java, but you can achieve the same result with a different stack. The most straightforward code is the following:
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
var template = "'{' \"streams\": ['{' \"stream\": '{' \"app\": \"{0}\" '}', \"values\": [[ \"{1}\", \"{2}\" ]]'}']'}'"; //1
var now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
var nowInEpochNanos = NANOSECONDS.convert(now.getEpochSecond(), SECONDS) + now.getNano();
var payload = MessageFormat.format(template, "demo", String.valueOf(nowInEpochNanos), "Hello from Java App"); //1
var request = HttpRequest.newBuilder() //2
.uri(new URI("http://localhost:3100/loki/api/v1/push"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); //3
}
- ככה עשינו התחלקות מחרוזת בימים העבריים
- יצירת הבקשה
- שלח אותה
ה原型 עובד, כפי שניתן לראות ב-Grafana:
עם זאת, הקוד מכיל מספר מוגבלויות:
- התווית מקודדת קשה. אפשר וצריך לשלוח תווית יחידה
- הכל מקודד קשה; שום דבר אינו ניתן להגדרה, למשל, ה-URL
- הקוד שולח בקשה אחת עבור כל יומן; זה מאוד לא יעיל מאחר ואין מאגר
- לקוח HTTP הוא סינכרוני, ובכך חוסמת את התור בזמן שמחכה ל-Loki
- אין שום עיכוב של שגיאות
- Loki מציע גם דחיסה gzip וגם Protobuf; אף אחד מהם אינו נתמך עם הקוד שלי
-
לבסוף, זה לגמרי לא קשור לאופן שבו אנו משתמשים ביומנים, למשל:
Javavar logger = // Obtain logger logger.info("My message with parameters {}, {}", foo, bar);
יומון רגיל על גלאי
כדי להשתמש בטענה לעיל, עלינו לבחור יישום יומן. מכיוון שאני מכיר אותו יותר, אשתמש ב-SLF4J וב-Logback. אל תדאגו; אותה גישה עובדת גם עבור Log4J2.
עלינו להוסיף את ההסמכות הרלוונטית:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <!--1-->
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <!--2-->
<version>1.4.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId> <!--3-->
<version>1.4.0</version>
<scope>runtime</scope>
</dependency>
- SLF4J היא הממשק
- Logback היא היישום
- מאגר לוגבק למוקד SLF4J
עכשיו, אנו מוסיפים מאגר לוקי ספציפי:
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender"> <!--1-->
<http>
<url>http://localhost:3100/loki/api/v1/push</url> <!--2-->
</http>
<format>
<label>
<pattern>app=demo,host=${HOSTNAME},level=%level</pattern> <!--3-->
</label>
<message>
<pattern>l=%level h=${HOSTNAME} c=%logger{20} t=%thread | %msg %ex</pattern> <!--4-->
</message>
<sortByTime>true</sortByTime>
</format>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
- מאגר הלוקי
- כתובת לוקי
- כמה תגים שרוצים
- תבנית Logback רגילה
התכנית שלנו הפכה הרבה יותר פשוטה:
var who = //...
var logger = LoggerFactory.getLogger(Main.class.toString());
logger.info("Hello from {}!", who);
גרפנה מציגה את הבא:
כניסה לדוקר
I'm running most of my demos on Docker Compose, so I'll mention the Docker logging trick. When a container writes on the standard out, Docker saves it to a local file. The docker logs
command can access the file content.
עם זאת, יש אפשרויות נוספות מאשר שמירה בקובץ מקומי, למשל, syslog
, גוגל קלאוד, ספלוק, וכו'. לבחירת אפשרות אחרת, מגדירים נהג יכולת. אפשר להגדיר את הנהג ברמה הכללית של דוקר או לפי מכל.
לוקי מציע את התוסףשלו. כדי להתקין אותו:
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
בשלב זה, אפשר להשתמש בו באפליקציית המכל:
services:
app:
build: .
logging:
driver: loki #1
options:
loki-url: http://localhost:3100/loki/api/v1/push #2
loki-external-labels: container_name={{.Name}},app=demo #3
- נהג כניסה לוקי
- כתובת להעברה
- תגים נוספים
התוצאה היא הבאה. שים לב לתגים הברירת מחדל.
מסקנה
מנקודת מבט של ציפור, לוקי איננו משהו יוצא דופן: זה מנוע אחסון פשוט עם API RESTful מעליו.
ישנם מספר גישות לשימוש ב-API. מעבר לגישה הנאיבית, ראינו מאגר יכולת לוגינג ב-Java ו-Docker. גישות נוספות כוללות גרישת קבצי הלוג, למשל, Promtail, דרך משלוח קרבי של Kubernetes. גם ניתן להוסיף קולקטור OpenTelemetry בין האפליקציה שלך ל-Loki לביצוע הפיכות.
האפשרויות הן כמעט אינסופיות. היזהר לבחור את האחת שמתאימה להקשר שלך הכי טוב.
להתקדם עוד יותר: