Monitor Log with Grafana loki
สวัสดีครับ เพื่อนๆ ชาว Developer บทความนี้ จะมาพูดถึงเรื่อง Log
คงไม่ดีแน่ ถ้าเกิดว่าระบบ หรือ Application เกิด Bug และ Error ขึ้นมาและไม่สามารถตรวจสอบได้ ดังนั้นการเก็บ Log ไว้จึงมีความสำคัญมาก เพื่อให้เราสามรถ รับรู้ ป้องกัน ปัญหา ที่จะเกิดขึ้น และ สามารถแก้ไขได้รวดเร็ว หากเกิดความผิดพลาด
ในบทความนี้ จะมาแนะนำ Tool ที่ช่วยให้การเก็บและดู Log นั้นง่ายขึ้น กว่าเดิม โดยใช้ winston ที่เป็นตัว package ที่ใช้ในการเขียน Log ถ้าพร้อมแล้วไปลุยกันเลย !!!
Loki คืออะไร
Loki คือ logging system ที่มีความสามารถ เก็บข้อมูล log และนำมาแสดงผล มีความยืดหยุ่น ใช้งานง่าย ประสิทธิภาพสูง และ สามารถ Scale ในรูปแบบ ของ horizontal โดยทำงานร่วมกับ Grafana Dashboard เพื่อนำข้อมูล Log ที่จัดเก็บไว้มาแสดงผล
Loki ยังสามารถรองรับการทำงานผ่าน REST API ผ่าน Endpoint ที่ทาง loki จัดเตรียมไว้ให้ สามรถดูรายละเอียด เพิ่มเติมได้ ที่นี่
https://grafana.com/docs/loki/latest/reference/loki-http-api
Step 1: Create a new project folder
Create a new folder for your project and navigate to it using the terminal.
mkdir winston-loki-nodejs
cd winston-loki-nodejs
Step 2: Initialize a new Node.js Project
Run the following command to initialize a new Node.js project and create a package.json
file.
npm init -y
npx tsc --init
Step 3: Config Dependencies And Install
config dependencies in this project package.json
file.
{
"name": "winston-loki-nodejs",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.5.4",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
},
"dependencies": {
"express": "^4.19.2",
"winston": "^3.14.2",
"winston-loki": "^6.1.2"
}
}
Install Package
npm install
Step 4: Install Grafana Loki
create docker-compose.yml file.
version: "3"
services:
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
networks:
- winston-loki
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
volumes:
- ./loki-data:/loki
networks:
- winston-loki
networks:
winston-loki:
driver: bridge
open terminal and run command.
docker-compose up
เมื่อทำการ ติดตั้งแล้ว ให้ตรวจสอบ docker จะพบว่ามี Contrainer ถูก Run ขั้นมาตามรูป
ตรวจสอบใน project จะมีการสร้าง folder เพื่อเก็บข้อมูลของตัว grafana loki
Step 5: Config Log
create folder “src” and create file “logger.ts”
import { createLogger, transports, LoggerOptions, format } from "winston";
import LokiTransport from "winston-loki";
const options: LoggerOptions = {
transports: [
//#section 1 (send logs to loki)
new LokiTransport({
host: "http://localhost:3100",
labels: {
service: "winston-service",
env: "dev",
},
interval: 5,
json: true,
format: format.json(),
onConnectionError: (err) => console.error(err),
}),
// #section 2 (write logs to console)
new transports.Console({
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.colorize(),
format.printf(
(info) =>
`[${info.level}]: ${info.timestamp} ${info.message} ${
info.stack || ""
}`
)
),
}),
//#section 3 (write logs to files)
new transports.File({
filename: "error.log",
level: "error",
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(
(info) => `${info.timestamp} ${info.level}: ${info.message}`
)
),
}),
new transports.File({
filename: "info.log",
level: "info",
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(
(info) => `${info.timestamp} ${info.level}: ${info.message}`
)
),
}),
],
};
export const logger = createLogger(options);
Section 1 (send logs to loki)
เป็นการตั้งค่า LokiTransport เพื่อทำการส่งข้อมูลที่ datasource ของ Loki
- host = คือการระบุที่อยู่ของตัว datasource
- label = ป้ายกำกับ หรือ field ต่างๆ ที่เราจะเก็บไว้ใน log
- interval = ระยะเวลา ที่จะส่งข้อมูล Log ไปที่ datasource ของ Loki (หน่วยเป็น วินาที)
- format = รูปแบบของ log (ตัวอย่าง จะเป็น รูปแบบของ json)
- onConnectionError = callback event ที่จะส่งข้อมูลกลับมา ในกรณีที่เชื่อมต่อไม่ได้
Section 2 (write logs to console)
ตั้งค่าให้ข้อมูลที่มีการ log แสดงผลที่หน้า console
Section 3 (write logs to files)
ตั้งค่าให้ทำการสร้างไฟล์ และเก็บข้อมูล log ไว้ที่ไฟล์ ที่ทำการระบุ
Step 6: Create Express Server
create file “index.ts” in folder src
// section 1
import express, { Express, Request, Response } from "express";
import { logger } from "./logger";
// section 2
const port: number = 4000;
const app: Express = express();
// section 3
app.use(express.json());
// section 4
app.get("/info", (req: Request, res: Response) => {
const respSuccess = {
statusCode: 200,
statusText: "success",
message: "Info Message",
};
logger.info({
message: `Info Message`,
labels: {
...respSuccess,
path: req.url,
method: req.method,
url: req.hostname,
},
});
return res.status(200).send(respSuccess);
});
app.get("/error", (req: Request, res: Response) => {
const respError = {
statusCode: 500,
statusText: "Internal Server Error",
message: "Error Message",
};
logger.error({
message: `Error Message`,
labels: {
...respError,
severity: "critical",
},
});
return res.status(500).send(respError);
});
// section 5
app.listen(port);
logger.warn(`Server is running on http://localhost:${port}`);
Section 1 (Import)
import express และ ไฟล์ log ที่ได้ทีการสร้างไว้
import express, { Express, Request, Response } from "express";
import { logger } from "./logger";
Section 2 ,3 (variable)
กำหนด port ที่จะใช้ในการ Run Server และ สร้าง variable ของ express
//section 2
const port: number = 4000;
const app: Express = express();
//section 3
app.use(express.json());
Section 4 (api endpoint)
สร้าง controller ที่เป็น method GET
- path “/info” ตัวอย่าง log ประเภท info
app.get("/info", (req: Request, res: Response) => {
const respSuccess = {
statusCode: 200,
statusText: "success",
message: "Info Message",
};
logger.info({
message: `Info Message`,
labels: {
...respSuccess,
path: req.url,
method: req.method,
url: req.hostname,
},
});
return res.status(200).send(respSuccess);
});
- path “/error” ตัวอย่าง log ประเภท error
app.get("/error", (req: Request, res: Response) => {
const respError = {
statusCode: 500,
statusText: "Internal Server Error",
message: "Error Message",
};
logger.error({
message: `Error Message`,
labels: {
...respError,
severity: "critical",
},
});
return res.status(500).send(respError);
});
Section 5 (Server Run)
กำหนดให้ Server Run ที่ port ที่การการตั้งไว้ และ แสดง log เมื่อ Server ทำงาน
app.listen(port);
logger.warn(`Server is running on http://localhost:${port}`);
Run command เพื่อ Start Sever
npm run dev
เมื่อ server ถูก Run ในหน้า Console จะแสดงข้อมูล log และ บันทึก log ที่ไฟล์ “info.log”
ทำการเปิด Broswer เข้าไปที่ URL ที่ได้ทำการสร้างไว้
ในหน้า Console จะแสดงข้อมูล log และ บันทึก log ที่ไฟล์ “info.log”
Step 7: Open Log on Grafana
เมื่อมี log เกิดขึ้นจากตัวระบบแล้ว ข้อมูลจะถูกส่งไปที่ datasource ของตัว loki ซึ่งวิธีการจะดู log นั้น จะใช้ Grafana ในการเปิดข้อมูลดุ log
เข้า Grafana ที่ URL: http://localhost:3000 และเข้าไปที่ เมนู Connections > Datasource ค้นหาคำว่า loki
กำหนด URL : http://loki:3100 และทำการ Save
ไปที่ เมนู Explore และทำการเลือก datasouce > loki
Click Label browser จะ ปรากฎหน้าต่าง ขึ้นมา และจะพบกับข้อมูลชื่อ label ที่เราส่งไปพร้อมกับ log ให้ทำการระบุ เลือก label ที่ต้องการจะดู และ กดปุ่ม “Show log”
หน้าจอจะแสดงผล ข้อมูล log ตาม label ที่ทำการระบุไว้
เมื่อคลิกที่ รายการ log จะเป็นการแสดง Field Tags ของ label ที่มีการส่งข้อมูลมาใน log
เมื่อปรับแต่ง ได้ตามที่ต้องการแล้ว ให้ทำการ Add to dashboard เพื่อเป็นการ Save ไว้ครับ
ถ้าไม่ได้ Save เป็น Dashboard ไว้ การเข้า เมนู Explore นั้นจะเป็นแค่การค้นหา และ ดูข้อมูลเท่านั้น
ตัว log ยังสามารถนำข้อมูลไปทำ Dashboard ในรูปแบบอื่นๆได้ เช่น Bar chart , Line chart โดยการปรับแต่งที่ Grafana Dashboard สามารถศึกษาเพิ่มเติม ได้ ที่นี่
https://grafana.com/docs/grafana/latest/explore/logs-integration/
Download Source : https://github.com/W0rasalid/winston-loki-nodejs.git