อ่าน Log ให้เป็น: Structured Logging ที่ใช้งานได้จริง
· 1 min read
ตอน 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 ข้าม serviceserviceและ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