Metrics, Tracing, Logging qua ví dụ cụ thể giúp ích bạn được những gì
Hôm nay rãnh rỗi ngồi chém gió về Metrics, Tracing, Logging qua ví dụ cụ thể, qua đây sẽ giúp hình dung ra nó là cái gì, điều này sẽ giúp ích cho bạn khá nhiều.
Nói về ba phương thức này thực sự nó rất là nhiều, xem như nó là một keyword để bạn tìm hiểu sau, mục đích của bài viết này giúp bạn có cái nhìn khái quát hơn, hình dung ra một bức tranh khái quát và tổng thể hơn.
Metrics :
Như câu chuyện xóm tôi mùa UEFA Euro 2024, bọn trộm chó và trộm gà đá có một chiếc xe Exciter dùng đi ăn trộm. Bọn trộm muốn biết chiếc xe tụi nó chạy ok không, nó sẽ theo dõi các chỉ số như:
Tốc độ xe khi chạy bao nhiêu, sau một cuốc thì tốn bao nhiêu xăng, khi bị người dân rượt đuổi thì chạy nhanh động cơ, nhông sên đĩa sẽ co giãn nhiều không, máy như thế nào...
Tương tự trong phần mềm cũng vậy, Metrics giúp theo dõi số lượng request lớn hay nhỏ, CPU xử lý nhanh hay chậm, thời gian phản hồi bao lâu, hệ thống sẽ sữ lý như thế nào...
Đó là nhiệm vụ cơ bản của Metrics, giúp bạn theo dõi các metrics để phát hiện một Metrics nào đó bất thường, bạn có thể biết rằng có ổn không và cần phải kiểm tra hay không. Thông qua đó bạn có thể xác định được nguyên nhân và đưa ra chách khắc phục.
Thông thường theo dõi metrics theo thời gian, bạn có thể xác định các điểm tắt nghẽn và tối ưu hóa hệ thống để hoạt động hiệu quả hơn. Dữ liệu metrics giúp bạn dự đoán nhu cầu trong tương lai và lên kế hoạch nâng cấp hệ thống phù hợp.
Tracing :
Như ví dụ trên, ngôi nhà của bạn bao gồm hàng rào xung quanh bao bọc kỹ càng lưới B40 ... như một hệ thống của bạn. Xung quanh nhà bạn setup các chuồng gà đá, chuồng chó, khu vực chó đi tới đi lui, những khu vực này xem như một service trong một hệ thống.
Bọn trộm chó, trộm gà khi nhảy vào hàng rào nha bạn trước tiên bọn nó sẽ đi tìm từng ngõ ngách xung quanh nhà để đi tìm chuồng nào nhốt gà, chuồng nào nhốt chó ... Khi nó đi tới nơi nào giống như người dùng tiếp cận một service, tracing cũng giống như bạn gắn camera để giám sát biết rằng thằng trộm nó đi tới chuồng gà lúc mấy giờ, tới chuồng chó lúc mấy giờ, lúc nó đi, nó bắt con gà, con chó nào ...
Thông qua đây bạn sẽ biết được thời gian, hành vi nó như thế nào, lục lọi tìm những gì ... Thông qua tracing ta có thể phân tích được hành vì như thằng này nó thích tập trung bắt nhiều gà trước hay bắt chó trước... Tương tự như phần mềm ta sẽ biết được service nào dùng nhiều nhất, service nào xử lý lâu nhất hoặc cái nào có vấn đề... Tóm lại Tracing sẽ giúp bạn theo dõi toàn bộ hành trình của request yêu cầu về hệ thống, từ dich vụ này đến dịch vụ khác trên hệ thống và ghi lại chi tiết điều đó.
Logging :
Có nhiệm vụ lưu lại các sự kiện xảy ra trong hệ thống của bạn, giống như bọ trộm gà trộm chó đến chuồng gà , chuôngg chó là một sự kiện, cạy khoá chuồng gà, chuồng chó là một sự kiện nó sẽ lưu lại thông tin cụ thể. Mọi thứ được ghi lại cụ thể theo từng tiến trình, quá trình thao tác nhằm mục đích xác định các hành vi, thao tác đã xảy ra...
Logging là quá trình ghi lại các sự kiện xảy ra trong hệ thống phần mềm để hỗ trợ giám sát, phân tích, và khắc phục sự cố. Thông qua bon trộm gà trômh chó chúng ta có thể thấy cách logging giúp theo dõi các cách thức hoạt động và cung cấp dữ liệu cần thiết để duy trì và cải thiện hệ thống
OK, bạn cứ hiểu nôm na như vậy, hôm nay sẽ dùng những thứ sau để thực hiện bài này :
Code: Tôi sẽ dùng nestjs để tạo ra api mẫu để client gởi request
Metrics: Sẽ dùng Prometheus Server và thư viện @willsoto/nestjs-prometheus cho nestjs
Tracing: Sẽ dùng thư viện OpenTelemetry và Jaeger xem thông tin ở local
Logging: Sẽ dùng Loki server để lưu logs, và loki có api hỗ trợ post metho, quá đây sẽ post logs đẩy lên Loki.
Monitoring : Tổng hợp mọi thứ lại để xem trực quan trên Grafana, thông qua Grafana sẽ chia sẽ mọi người sử dụng tổng hợp mọi thứ ở trên để trực quan hơn.
Phần code :
Tối có tạo project nestjs đơn giản sau, gọi phương thức Get với 2 api /tromcho và /tromga
Trong code tôi sử dụng libs opentelemetry(Otel) để tracing và exporter thông tin đến jaeger
"@opentelemetry/exporter-jaeger": "^1.25.0", "@opentelemetry/instrumentation-express": "^0.40.1", "@opentelemetry/instrumentation-http": "^0.52.0", "@opentelemetry/instrumentation-nestjs-core": "^0.38.0", "@opentelemetry/resources": "^1.25.0", "@opentelemetry/sdk-node": "^0.52.0", "@opentelemetry/sdk-trace-base": "^1.25.0", "@opentelemetry/semantic-conventions": "^1.25.0",
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; import { NodeSDK } from "@opentelemetry/sdk-node"; import * as process from "process"; import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express"; import { NestInstrumentation } from "@opentelemetry/instrumentation-nestjs-core"; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; import { JaegerExporter } from "@opentelemetry/exporter-jaeger"; require("dotenv").config(); const jaegerExporter = new JaegerExporter({ endpoint: `http://${process.env.JAEGER_EXPORT_URL}/api/traces`, }); const traceExporter = jaegerExporter; export const otelSDK = new NodeSDK({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: `nestjs-otel`, }), spanProcessor: new SimpleSpanProcessor(traceExporter), instrumentations: [ new HttpInstrumentation(), new ExpressInstrumentation(), new NestInstrumentation(), ], }); process.on("SIGTERM", () => { otelSDK .shutdown() .then( () => console.log("SDK shut down successfully"), (err) => console.log("Error shutting down SDK", err) ) .finally(() => process.exit(0)); });
Sử dụng libs nestjs-prometheus để tạo metrics theo dõi server app để đẩy lên Prometheus
"@willsoto/nestjs-prometheus": "^6.0.1",
import { Controller, Get, Res } from "@nestjs/common"; import { PrometheusController } from "@willsoto/nestjs-prometheus"; @Controller("metrics") export class PromController extends PrometheusController { @Get() metrics(@Res({ passthrough: true }) response: Response) { return super.index(response); } }
Trên Loki có một api cho sử dụng post method và sử dụng axios để đẩy logs lên Loki, ở đây sẽ bắt 2 sự kiện, thông tin của controller, có nghĩa mọi thông tin của controller sẽ bắt logs khi gọi controller và đẩy nó vào Loki.
Cái thứ 2 sẽ custom logs, dùng nó định nghĩa cho một logs nào đó cần dùng, thường như service bắt lỗi hay gì đó cần dùng lưu logs sẽ dùng nó.
import axios from "axios";
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import { v4 as uuidv4 } from "uuid";
require("dotenv").config();
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
httpService: any;
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
if (context.getType() === "http") {
return this.logHttpCall(context, next);
}
}
private logHttpCall(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const userAgent = request.get("user-agent") || "";
const { ip, method, url, body, query, params } = request;
const correlationKey = uuidv4();
const userId = request.user?.id || 'anonymous';
const className = context.getClass().name;
const handlerName = context.getHandler().name;
this.logger.log(
`[${correlationKey}] ${userAgent} ${ip} ${method} ${url} ${JSON.stringify(body)} ${JSON.stringify(query)} ${JSON.stringify(params)} ${userId}`
);
const now = Date.now();
return next.handle().pipe(
tap(async () => {
const response = context.switchToHttp().getResponse();
const { statusCode } = response;
const logData = `[${correlationKey}] ${userAgent} ${ip} ${method} ${url} ${JSON.stringify(body,)} ${JSON.stringify(query)} ${JSON.stringify(params)} ${userId} ${Date.now() - now}ms`
await this.pushToLoki(logData,className,handlerName);
})
);
}
private async pushToLoki(logData: any,contextStream: any, traceStream: any) {
try {
const lokiData = {
streams: [
{
stream: {
env: "nestjs",
context: contextStream,
trace: traceStream,
},
values: [[(Date.now() * 1e6).toString(), logData]],
},
],
};
await axios.post(`http://${process.env.LOKI_SERVER_URL}/loki/api/v1/push`, lokiData);
this.logger.log("Log sent to Loki server successfully.");
} catch (error) {
this.logger.error("Error sending log to Loki server:", error);
}
}
}
import { Injectable, Logger } from "@nestjs/common";
import axios from "axios";
require("dotenv").config();
@Injectable()
export class LokiLogger {
constructor(private readonly logger: Logger) {}
error(message: any, trace?: string, context?: string) {
this.sendToLoki("error", message, trace, context);
}
warn(message: any, context?: string) {
this.sendToLoki("warn", message, undefined, context);
}
log(message: any, context?: string) {
this.sendToLoki("info", message, undefined, context);
}
private async sendToLoki(
level: string,
message: any,
trace?: string,
context?: string
) {
try {
const lokiData = {
streams: [
{
stream: {
env: "nestjs",
level: level,
context: context,
trace: trace,
},
values: [[(Date.now() * 1e6).toString(), message]],
},
],
};
await axios.post(`http://${process.env.LOKI_SERVER_URL}/loki/api/v1/push`, lokiData);
this.logger.log(`[${level}] Log sent to Loki server successfully.`);
} catch (error) {
this.logger.error("Error sending log to Loki server:", error);
}
}
}
Tất cả ở trên từ Jarger đến prometheus mục đích test local để xem logic đúng không, giờ cần phải đẩy lên Grafana để quản lý tập trung, vì nó bao gồm cả user/pass đăng nhập nên việc này sẽ thuận tiện hơn.
Data sources: Chọn đúng loại data sources cần dùng, như bài này sẽ dùng như sau, cứ add và cung cấp đúng thông tin sẽ nhận
Dashboards: Hiện tại sử dụng Dashboards ID : 11159 có tên NodeJS Application Dashboard thấy cũng khá ok.
Sau khi đã tích hợp Logging và Tracing thì Explore sẽ như sau
Và Tracing nó sẽ như sau
Đây là link project demo, có tham khảo link sau:
https://github.com/tieukhachngao/Metrics-Tracing-Logging
Nhãn: Reference