Monitor Log with Grafana loki

Worasalid Juicharoen
6 min readSep 12, 2024

--

สวัสดีครับ เพื่อนๆ ชาว 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”

ข้อมูล log แสดงที่หน้า console
ข้อมูล log ที่บันทึกเข้า file

ทำการเปิด Broswer เข้าไปที่ URL ที่ได้ทำการสร้างไว้

http://localhost:4000/info
http://localhost:4000/error

ในหน้า 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

http://localhost:3000

กำหนด URL : http://loki:3100 และทำการ Save

ไปที่ เมนู Explore และทำการเลือก datasouce > loki

Click Label browser จะ ปรากฎหน้าต่าง ขึ้นมา และจะพบกับข้อมูลชื่อ label ที่เราส่งไปพร้อมกับ log ให้ทำการระบุ เลือก label ที่ต้องการจะดู และ กดปุ่ม “Show log”

ระบุ label ที่อยากจะ filter ข้อมูล

หน้าจอจะแสดงผล ข้อมูล 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

--

--

Worasalid Juicharoen
Worasalid Juicharoen

No responses yet