ข้ามไปยังเนื้อหา

Log Aggregation

ทุกบริการเขียน logs — นิสัยนั้นรอดมาได้อย่างไม่บุบสลายเมื่อย้ายมาสู่ microservices สิ่งที่เปลี่ยนไปคือที่ที่ logs เหล่านั้นอยู่ ใน monolith มีโปรเซสเดียวและ app.log เดียว; grep บนไฟล์นั้นคือ debugger ของคุณ ตอนนี้คำขอเชิงตรรกะเดียวกันแตะห้าบริการ แต่ละบริการรัน replica หลายตัว แต่ละตัวเขียนลง local file หรือ standard output ของตัวเองภายใน container ที่ scheduler อาจทำลายได้ทุกเมื่อ

ลูกค้าแจ้งว่า checkout ล้มเหลวตอน 14:32 บรรทัด log ที่เกี่ยวข้องกระจายอยู่ใน order replica ที่รับคลิกนั้น payment replica ที่ order เรียกต่อ และ inventory replica ที่ถูกเรียกตามมา — สามไฟล์บนสามเครื่อง ซึ่งสองเครื่องโดนรีไซเคิลไปแล้วพร้อม log

ไม่มีที่ไหนให้ดูรวมกันได้ที่เดียว และต่อให้ล็อกอินเข้าทุกเครื่องได้ log ก็ยังต่อกันไม่ติด order service log error ไว้บรรทัดหนึ่ง payment service log timeout ไว้อีกบรรทัด ส่วน inventory service ไม่ได้ log อะไรเลย แต่ไม่มีอะไรในบรรทัดพวกนั้นบอกได้ว่าทั้งหมดมาจาก request เดียวกัน สุดท้ายคุณต้องนั่งเทียบ timestamp แล้วเดาเอา ข้ามไฟล์ที่อาจ rotate หายไปแล้วด้วยซ้ำ

แล้วคุณจะทำให้บรรทัด log จากคำขอเดียว ที่กระจายอยู่ตาม replica ที่อายุสั้นจำนวนมาก ทั้ง ค้นหาเจอได้ในที่เดียว และ เชื่อมโยงถึงกัน ได้อย่างไร

Log aggregation มีสองส่วน และคุณต้องการทั้งสอง

ส่วนแรก pipeline ที่ส่ง log ออกจากเครื่อง แต่ละ service เขียน log ลง standard output หรือให้ local agent มา tail แล้ว collector ส่งต่อทุกบรรทัดไปยัง store กลางที่มี index ให้ค้นหาข้าม service และข้าม replica ได้พร้อมกัน เพราะ log ออกจาก container ทันที จึงอยู่รอดต่อไปแม้ replica ที่เขียนจะหายไปแล้ว

ส่วนที่สอง ทำให้ log มีโครงสร้างและเชื่อมโยงกันได้ แทนที่จะเป็นข้อความอิสระ ให้แต่ละบรรทัดเป็น record ที่อธิบายตัวเองได้ ปกติคือ JSON ที่มี field ชุดเดียวกันเสมอ ได้แก่ timestamp, level, ชื่อ service และที่สำคัญที่สุดคือ correlation id หรือบางทีเรียก request id ซึ่งสร้างขึ้นครั้งเดียวที่ขอบของระบบ แล้วแนบไปกับทุกบรรทัดที่เกิดขึ้นระหว่างจัดการ request นั้น ครบทุก service ที่ request วิ่งผ่าน ค้นหาด้วย id เดียวก็ได้ทุกบรรทัดตลอดเส้นทางของ request กลับมาเรียงตามลำดับ ไม่ว่า replica ไหนหรือ service ไหนจะเป็นคนเขียน

flowchart LR
  subgraph Replicas
    A[Order replica
stdout JSON]
    B[Payment replica
stdout JSON]
    C[Inventory replica
stdout JSON]
  end
  A --> COL[Log Collector / Agent]
  B --> COL
  C --> COL
  COL --> STORE[(Central Indexed
Log Store)]
  STORE --> SEARCH[Search UI
filter by correlationId]
แต่ละ replica ปล่อย structured logs ไปยัง collector ซึ่งส่งต่อไปยัง store กลางที่ค้นหาได้และเชื่อมโยงด้วย request id

ตัวอย่างบรรทัด structured log ที่ payment service เขียนออกมาระหว่างจัดการ checkout หนึ่ง JSON object ต่อหนึ่งบรรทัด ชื่อ field คงที่ และมี correlation id ที่ผูกบรรทัดนี้เข้ากับทุกบรรทัดของ request เดียวกัน:

{
"timestamp": "2026-06-25T14:32:07.512Z",
"level": "error",
"service": "payment-service",
"instance": "payment-7d9f-bk2qx",
"correlationId": "c1f4e9a2-8b3d-4e77-9a01-2f6c5d8e1b04",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"event": "charge_failed",
"orderId": "ord_88213",
"amount": 4999,
"currency": "USD",
"reason": "gateway_timeout",
"durationMs": 3021,
"message": "payment gateway did not respond within 3s"
}

การค้นหา store กลางด้วย correlationId = "c1f4e9a2-8b3d-4e77-9a01-2f6c5d8e1b04" จะคืนค่าบรรทัดนี้พร้อมกับบรรทัดของ order service ที่เริ่มคำขอ และบรรทัดของ inventory service ที่ตามมา — เรื่องราวเต็มของ checkout เดียว ประกอบขึ้นใหม่จากสามบริการ

สิ่งที่คุณได้รับ:

  • ค้นที่เดียวได้ครบทุกอย่าง คุณ query ข้ามทุก service และทุก replica ได้พร้อมกัน แถม log ยังอยู่ต่อแม้ container ที่เขียนจะตายไปแล้ว pod ที่โดนรีไซเคิลจึงไม่พาหลักฐานหายไปด้วยอีกต่อไป
  • คำขออ่านเป็นเรื่องราว การ filter ด้วย correlation id สร้างเส้นทางของคำขอเดียวข้ามขอบเขตบริการขึ้นใหม่ตามลำดับ — กู้คืนมุมมอง end-to-end ที่คุณได้มาฟรี ๆ ใน monolith
  • Field ที่เครื่องอ่านได้ structured logs สามารถถูก filter, aggregate และตั้ง alert ตาม field (level, service, reason) แทนการ match ข้อความที่เปราะบาง

สิ่งที่คุณต้องจ่าย:

  • คุณต้อง propagate correlation id เอง สร้าง id ที่ขอบระบบ ไม่ว่าจะเป็น gateway หรือ service แรก แล้วส่งต่อในทุกการเรียกออก มักจะส่งผ่าน header ถ้าพลาด hop ไหนไป chain ก็ขาดตรงนั้นทันที
  • ปริมาณและต้นทุน การรวมศูนย์ทุกบรรทัดจากทุก replica ผลิตข้อมูลจำนวนมาก คุณจะต้องมี log level ที่สมเหตุสมผล, sampling สำหรับ debug log ที่มีปริมาณสูง และนโยบาย retention ไม่เช่นนั้นทั้งบิลและเสียงรบกวนก็จะโตอย่างไม่มีขอบเขต
  • ระเบียบวินัยเรื่อง field คุณค่ามาจากชื่อ field ที่ สอดคล้องกัน ข้ามบริการ หากไม่มี logging convention ที่ใช้ร่วมกัน userId ที่นี่และ user_id ที่นั่นก็จะทำลาย query ของคุณอย่างเงียบ ๆ
  • Distributed Tracing — trace id คือแนวคิดเดียวกันที่ถูกขยายต่อไป โดยเพิ่ม causal spans และ timing ทับลงบน correlation
  • Audit Logging — record แยกต่างหากที่คงทนสำหรับการกำกับดูแล แตกต่างจาก operational debug logs เหล่านี้
ข้อดีข้อแลกเปลี่ยน
log จากทุก service อยู่ที่เดียว — search ง่ายlog volume สูงมากใน production — storage cost
correlate log ข้าม service ด้วย trace IDlatency จาก log shipping ทำให้ log ไม่ real-time 100%
ค้นหา error pattern ข้าม deployment ได้sensitive data ใน log ต้องจัดการก่อน aggregate
audit และ compliance ใน central locationsetup ELK/Loki stack ซับซ้อน

Log ทุกอย่างด้วย DEBUG level ใน Production อาการ:

  • log volume 100x เกินความจำเป็น
  • search ใน Kibana ช้าเพราะ index ใหญ่มาก
  • cost สูงโดยไม่จำเป็น — ใช้ INFO ใน production, DEBUG เฉพาะเมื่อ debug

Log โดยไม่มี Correlation ID อาการ:

  • ติดตาม request เดียวข้าม 5 service ต้องเดาว่า log ไหนเกี่ยวกัน
  • incident investigation ใช้เวลาชั่วโมงแทนที่จะเป็นนาที
  • inject trace/request ID ทุก log line

💡 ตัวอย่างจากของจริง

Elastic (ELK Stack):

  • Elasticsearch + Logstash + Kibana เป็น stack ที่ใช้แพร่หลายที่สุด
  • Netflix, LinkedIn, Goldman Sachs ล้วนใช้ Elasticsearch สำหรับ log aggregation

Grafana Loki:

  • log aggregation ที่ integrate กับ Prometheus และ Grafana
  • ไม่ index content — ถูกกว่า Elasticsearch สำหรับ high-volume log
  • Grafana, GitLab ใช้ Loki ใน production
correlation id แก้ปัญหาอะไรใน log ที่รวมศูนย์ไว้แล้ว
ทำไมจึงควรใช้ structured log เช่น JSON แทนข้อความรูปแบบอิสระ
โดยทั่วไปควรสร้าง correlation id ที่จุดไหน และส่งต่อไปอย่างไร