Skip to content

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:

javascript
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)
}

รูปแบบการทำงาน:

  1. เขียน log ลงไฟล์ตามวันที่ — 2025-07-11.log, error_2025-07-11.log, warning_2025-07-11.log
  2. Path ของ log คำนวณจาก FileMethods.getLogPath() ซึ่งอ้างถึง LOG_PATH env var
  3. เขียนแบบ append — writeFile(..., {}, true) (พารามิเตอร์ตัวที่สามเป็น flag append)
  4. ไม่มี 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:

javascript
{ "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.md LEG-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:

javascript
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:

typescript
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:

javascript
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 ใดที่รายงานสถานะ

ผลกระทบ:

  1. Docker ไม่สามารถ restart container ได้ถูกต้อง — ถ้า Node process ยังรันแต่ db connection ขาด, Docker ถือว่า container ยัง "up"
  2. nginx ไม่สามารถตัด replica ปัญหาออกจาก upstream — ส่ง traffic ไป replica ที่ hang ได้
  3. ไม่สามารถ monitor ผ่าน external tool — ต้องตรวจสอบที่ละ service ด้วยมือ

การ monitor ผ่าน console.log — ทำไมไม่พอ

ปัญหารายละเอียด
ไม่มี correlation IDแต่ละ request ไม่มี ID ที่เชื่อม log ระหว่าง services
ไม่มี log levelไม่สามารถกรอง info/warn/error ในภายหลัง
ไม่มี structured formatlog ส่วนใหญ่เป็น plain text ที่ทำให้ grep และ parse ยาก
ไม่มี contextlog ไม่บอกว่ามาจาก request ใด, user ใด, trace ใด
กระจายหลายไฟล์console.log ไป stdout, wlog.js ไป *.log, nginx ไป nginx-log/ — ไม่มีจุดรวม
ไม่มี retentionไฟล์ log เติบโตเรื่อย ๆ จน disk เต็ม

ตัวอย่างปัญหาเชิงปฏิบัติการ

ถ้าผู้ใช้รายงานว่า "ไม่สามารถโหลด payslip ได้" ทีมปฏิบัติการต้อง:

  1. SSH เข้า server
  2. ตรวจสอบ tail -f logs/slipError_2025-07-11.log — แต่ log อาจไม่มี context ว่า user ใด
  3. ตรวจสอบ tail -f /var/log/nginx/access-api-443.log — grep ด้วย pid ที่อยู่ใน URL
  4. ตรวจสอบ docker logs vtrc-api-nodejs-1 — แต่ log กระจายใน 4 replica
  5. ถ้ามี error ต้องส่ง screenshot ให้ developer เพราะไม่มี error tracking

กระบวนการนี้ใช้เวลานานและต้องอาศัยความเชี่ยวชาญ

ข้อแนะนำเพื่อปรับปรุง

ระยะสั้น

  1. เพิ่ม /health endpoint ในทุก backend service — ตรวจสอบ db, redis, return 200/503
  2. เปลี่ยน console.log เป็น logger ที่มี level — ใช้ winston หรือ pino แทน wlog.js
  3. เพิ่ม correlation ID middleware — ส่งต่อ ID ระหว่าง services ผ่าน header

ระยะกลาง

  1. เพิ่ม log aggregation — ส่ง log ไป ELK, Loki หรือ GCP Logging
  2. เพิ่ม error tracking — Sentry สำหรับ backend, Sentry หรือ LogRocket สำหรับ frontend
  3. เพิ่ม metric — Prometheus + Grafana สำหรับ request rate, error rate, latency

ระยะยาว

  1. เพิ่ม distributed tracing — OpenTelemetry สำหรับติดตาม request ข้าม services
  2. เปลี่ยนไปใช้ structured logging เต็มรูปแบบ — JSON log ที่ทุก field มี schema
  3. เพิ่ม alerting — PagerDuty หรือ Slack webhook เมื่อ error rate เกิน threshold
  4. ลด PII ใน log — อย่า log pid, empcode, identityCard ในรูปแบบที่อ่านได้