13.10 Logging, monitoring, health check
บทนี้สำรวจวิธีที่ระบบเก็บ log, ติดตามสถานะ, และตรวจสอบความพร้อมของ service
ภาพรวม — สิ่งที่ขาด
| มิติ | สถานะ | หมายเหตุ |
|---|---|---|
| Structured logger | มีบางส่วน | wlog.js ของ vtrc-api, I3GatewayLogger ของ centralize-api |
| Log level | ไม่มี | ทุกอย่างไปที่ไฟล์เดียวกัน ไม่กรอง |
| Log aggregation | ไม่มี | ไม่มี ELK, Loki, Datadog |
| APM | ไม่มี | ไม่มี New Relic, AppDynamics |
| Error tracking | ไม่มี | ไม่มี Sentry, Bugsnag, Rollbar |
| Metric | ไม่มี | ไม่มี Prometheus, Grafana |
| Health check endpoint | ไม่มี | ไม่มี /health, /healthz, /ping |
| Alerting | ไม่มี | ไม่มี PagerDuty, Opsgenie |
สรุปคือระบบเก็บ log ในไฟล์ แต่ไม่มีเครื่องมือใด ๆ ที่ช่วยวิเคราะห์, กรอง, หรือแจ้งเตือนเมื่อเกิดปัญหา — ทีมต้อง SSH เข้า server แล้ว tail -f เพื่อ debug
vtrc-api — wlog.js logger ที่กำหนดเอง
vtrc-api/api/src/lib/wlog.js:9-20:
const writeLog = (text, type = '') => {
const date = DateClass.makeDate()
type = (type === '') ? '' : type + '_'
const logPath = path.resolve(FileMethods.getLogPath(), '../')
const logFile = path.resolve(logPath, `${type}${date.yyyy}-${date.mm}-${date.dd}.log`)
if (!fs.existsSync(logPath)) {
fs.mkdirSync(logPath, { recursive: true })
}
return FileMethods.writeFile(logFile, `${date.date.toLocaleString('th-th', { timeZone: TIME_ZONE_TEXT })} - ${text}\n`, {}, true)
}รูปแบบการทำงาน:
- เขียน log ลงไฟล์ตามวันที่ —
2025-07-11.log,error_2025-07-11.log,warning_2025-07-11.log - Path ของ log คำนวณจาก
FileMethods.getLogPath()ซึ่งอ้างถึงLOG_PATHenv var - เขียนแบบ append —
writeFile(..., {}, true)(พารามิเตอร์ตัวที่สามเป็น flag append) - ไม่มี rotation — ไฟล์ log เติบโตเรื่อย ๆ ในแต่ละวัน ต้องลบเอง
API ที่ export:
| Function | หน้าที่ |
|---|---|
info(text) | เขียน log ปกติ |
warning(text) | เขียน warning_*.log |
error(text) | เขียน error_*.log |
query(text) | เขียน query_*.log — log ของ SQL query |
notificationLog(text) | เขียน notificationLog_*.log |
notificationLogError(text) | เขียน notificationLogError_*.log |
slipError(text) | เขียน slipError_*.log — log ของ PDF payslip generation |
verify(text) | เขียน verify_*.log |
forceLogs(text) | เขียน forceLogs_*.log |
มี logTextFormat(code, source, type, text, args) ที่กำหนด format JSON:
{ "CODE": "...", "SOURCE": "...", "TYPE": "INFO", "text": "...", ...args }แต่ format นี้ไม่ได้ถูกใช้ทุกที่ — wlog.js มีทั้งรูปแบบ plain text และ JSON ผสมกัน
vtrc-api — console.log ที่ใช้ทั่วไป
นอกจาก wlog.js, code ยังใช้ console.log อย่างกว้างขวาง:
vtrc-api/api/src/routes.js— 77 ครั้ง (รายงานในdocs/audit/05-tech-debt-register.mdLEG-24)vtrc-api/api/src/resolvers/hrmi.js— 22 ครั้งvtrc-api/api/src/resolvers/wdHospital.js,user.js,post.js,masterData.js— อย่างน้อย 1 ครั้งต่อไฟล์- ไฟล์อื่น ๆ อีกมาก
console.log เหล่านี้เขียนไปที่ stdout ของ container — ที่ Docker เก็บไว้ใน log driver เริ่มต้น (json-file) ที่ไม่มี rotation
vtrc-api — index.js ก็ใช้ console.log
vtrc-api/api/src/index.js:99,105,108,116,119:
console.log(`🚀 Server ready at -> http://localhost:${APP_PORT}${server.graphqlPath}`)
...
console.log('Connection has been established successfully.')
...
console.error('Unable to connect to the database:', err)startup log ใช้ console.log + emoji ทำให้ grep ยาก และไม่ได้บันทึกระดับความสำคัญ
centralize-api — I3GatewayLogger
centralize-api/src/main.ts:13-14:
const app = await NestFactory.create(AppModule, {
logger: new I3GatewayLogger('System'),
});I3GatewayLogger เป็น logger ที่กำหนดเองใน centralize-api/libs/log/logs.middleware.ts — เป็น wrapper ของ NestJS Logger ที่เพิ่ม context name
จุดสังเกต:
- เป็น service เดียวที่ใช้ logger ที่ไม่ใช่
console.log - แต่ก็ไม่ได้ส่ง log ไปที่ aggregation service — แค่เปลี่ยนรูปแบบ
cu-central-api — log ผ่าน PM2 file
cu-central-api/ecosystem.config.js:7-8:
error_file: './logs/carpark-api-error.log',
out_file: './logs/carpark-api.log',ชื่อ log file carpark-api.log บ่งชี้ว่า copy มาจากโครงการอื่น และ PM2 config นี้ไม่ได้ใช้จริง (Docker ใช้ npm start ตรง ๆ)
nginx — access log
nginx config กำหนด access log แยกตาม server block:
vtrc-api/nginx/default.conf:72-73:access_log /var/log/nginx/access-api-443.log; error_log /var/log/nginx/error-api-443.log;vtrc-api/nginx/default.conf:228-229:access_log /var/log/nginx/access-web-443.log; error_log /var/log/nginx/error-web-443.log;
nginx log เขียนไปที่ volume ./nginx-log (vtrc-api/docker-compose.yml:25) ทำให้สามารถเข้าถึงได้จาก host โดยตรง
จุดสังเกต:
- PII ใน URL ติด access log — เช่น
/readfile/prs/[pid]/[empcode]/...ทำให้ access log เต็มไปด้วย identityCard (รายละเอียด Volume 9 บทที่ 1) - ไม่มี log rotation — nginx access log เติบโตเรื่อย ๆ ต้องใช้ logrotate ของ host (ถ้าตั้งไว้)
การตรวจสอบสถานะ — ไม่มี health check
จากการค้นหา /health, /healthz, /ping, /ready ใน code ของทุก service — ไม่พบ endpoint ใดที่รายงานสถานะ
ผลกระทบ:
- Docker ไม่สามารถ restart container ได้ถูกต้อง — ถ้า Node process ยังรันแต่ db connection ขาด, Docker ถือว่า container ยัง "up"
- nginx ไม่สามารถตัด replica ปัญหาออกจาก upstream — ส่ง traffic ไป replica ที่ hang ได้
- ไม่สามารถ monitor ผ่าน external tool — ต้องตรวจสอบที่ละ service ด้วยมือ
การ monitor ผ่าน console.log — ทำไมไม่พอ
| ปัญหา | รายละเอียด |
|---|---|
| ไม่มี correlation ID | แต่ละ request ไม่มี ID ที่เชื่อม log ระหว่าง services |
| ไม่มี log level | ไม่สามารถกรอง info/warn/error ในภายหลัง |
| ไม่มี structured format | log ส่วนใหญ่เป็น plain text ที่ทำให้ grep และ parse ยาก |
| ไม่มี context | log ไม่บอกว่ามาจาก request ใด, user ใด, trace ใด |
| กระจายหลายไฟล์ | console.log ไป stdout, wlog.js ไป *.log, nginx ไป nginx-log/ — ไม่มีจุดรวม |
| ไม่มี retention | ไฟล์ log เติบโตเรื่อย ๆ จน disk เต็ม |
ตัวอย่างปัญหาเชิงปฏิบัติการ
ถ้าผู้ใช้รายงานว่า "ไม่สามารถโหลด payslip ได้" ทีมปฏิบัติการต้อง:
- SSH เข้า server
- ตรวจสอบ
tail -f logs/slipError_2025-07-11.log— แต่ log อาจไม่มี context ว่า user ใด - ตรวจสอบ
tail -f /var/log/nginx/access-api-443.log— grep ด้วย pid ที่อยู่ใน URL - ตรวจสอบ
docker logs vtrc-api-nodejs-1— แต่ log กระจายใน 4 replica - ถ้ามี error ต้องส่ง screenshot ให้ developer เพราะไม่มี error tracking
กระบวนการนี้ใช้เวลานานและต้องอาศัยความเชี่ยวชาญ
ข้อแนะนำเพื่อปรับปรุง
ระยะสั้น
- เพิ่ม
/healthendpoint ในทุก backend service — ตรวจสอบ db, redis, return 200/503 - เปลี่ยน
console.logเป็น logger ที่มี level — ใช้winstonหรือpinoแทนwlog.js - เพิ่ม correlation ID middleware — ส่งต่อ ID ระหว่าง services ผ่าน header
ระยะกลาง
- เพิ่ม log aggregation — ส่ง log ไป ELK, Loki หรือ GCP Logging
- เพิ่ม error tracking — Sentry สำหรับ backend, Sentry หรือ LogRocket สำหรับ frontend
- เพิ่ม metric — Prometheus + Grafana สำหรับ request rate, error rate, latency
ระยะยาว
- เพิ่ม distributed tracing — OpenTelemetry สำหรับติดตาม request ข้าม services
- เปลี่ยนไปใช้ structured logging เต็มรูปแบบ — JSON log ที่ทุก field มี schema
- เพิ่ม alerting — PagerDuty หรือ Slack webhook เมื่อ error rate เกิน threshold
- ลด PII ใน log — อย่า log pid, empcode, identityCard ในรูปแบบที่อ่านได้