Buggy
← บทความทั้งหมด

อ่าน Log ให้เป็น: Structured Logging ที่ใช้งานได้จริง

· 1 min read

#observability#devops

ตอน service ยังมีเครื่องเดียว การไล่ log ด้วยตาก็พอไหว แต่พอขึ้นเป็นหลาย instance แล้ว log ที่เป็นข้อความอิสระจะกลายเป็นกองข้อความที่ค้นอะไรไม่ได้เลย ทางออกคือเขียน log ให้เป็นโครงสร้างตั้งแต่แรก

Log ที่ query ไม่ได้ คือ log ที่ไม่มีประโยชน์

console.log(`user ${userId} failed to checkout: ${err.message}`)

บรรทัดนี้อ่านรู้เรื่องสำหรับคน แต่ถ้าอยากรู้ว่า "checkout พังกี่ครั้งในชั่วโมงที่ผ่านมา แยกตาม error code" ต้องมานั่งเขียน regex จับข้อความ ซึ่งจะพังทันทีที่มีคนแก้ข้อความ log

เขียนเป็น JSON แทน แล้วให้ระบบเก็บ log ทำ index ให้

logger.error({
  event: 'checkout.failed',
  userId,
  orderId,
  errorCode: err.code,
  durationMs: Date.now() - startedAt,
})

ฟิลด์ที่ควรมีติดทุก log

  • timestamp เป็น ISO 8601 พร้อม timezone เสมอ
  • level ใช้ค่ามาตรฐาน (debug / info / warn / error)
  • event เป็นชื่อคงที่แบบ domain.action ไม่ใช่ประโยคภาษาคน
  • requestId หรือ traceId สำหรับร้อยเรียง log ข้าม service
  • service และ version เพื่อบอกว่า log มาจาก deploy ไหน

ตัวที่คนลืมบ่อยที่สุดคือ requestId ทั้งที่มันคือฟิลด์ที่ทำให้ debug ปัญหา production เร็วขึ้นมากที่สุด เพราะมันเปลี่ยนคำถามจาก "เกิดอะไรขึ้นบ้างช่วงนั้น" เป็น "request นี้เดินทางผ่านอะไรมาบ้าง"

อย่า log ของที่ไม่ควร log

password, token, เลขบัตร, เบอร์โทร และ header Authorization ต้องถูกกรองออกที่ตัว logger ไม่ใช่ฝากความหวังไว้กับวินัยของคนเขียนโค้ด ตั้ง redact list ไว้ที่เดียวแล้วบังคับใช้ทั้งระบบ

const logger = pino({
  redact: ['req.headers.authorization', 'req.body.password', '*.creditCard'],
})

ระดับ log ที่เหมาะกับความเป็นจริง

ใช้ error เฉพาะกับสิ่งที่ต้องมีคนเข้ามาดู ถ้า log ระดับ error ขึ้นวันละพันบรรทัดโดยไม่มีใครทำอะไรกับมัน สุดท้ายทีมจะเลิกสนใจ alert ทั้งหมด — ปัญหานี้แก้ที่การจัดระดับ log ไม่ใช่ที่การตั้งค่า alert