3 นาที

Correlation IDs แบบครบวงจร: ติดตามการกระทำของผู้ใช้ในล็อก

Correlation IDs แบบครบวงจรอธิบายวิธีสร้าง ID ใน frontend ส่งผ่าน APIs และรวมไว้ในล็อก เพื่อให้ฝ่ายซัพพอร์ตติดตามปัญหาได้อย่างรวดเร็ว

Correlation IDs แบบครบวงจร: ติดตามการกระทำของผู้ใช้ในล็อก

ทำไม correlation IDs ถึงสำคัญสำหรับฝ่ายซัพพอร์ต

ฝ่ายซัพพอร์ตแทบจะไม่มีรายงานบั๊กที่ชัดเจน ผู้ใช้อาจบอกว่า "กด Pay แล้วล้มเหลว" แต่การคลิกเดียวอาจผ่านมาเบราว์เซอร์ เกตเวย์ API เซอร์วิสชำระเงิน ฐานข้อมูล และงานแบ็คกราวด์ แต่ละส่วนบันทึกแค่ชิ้นของเรื่อง ณ เวลาและเครื่องที่ต่างกัน หากไม่มีป้ายร่วมเดียว คุณจะต้องเดาว่าแถวล็อกไหนเกี่ยวข้องกัน

correlation ID คือป้ายร่วมนี้ มันเป็น ID เดียวที่แนบกับการกระทำของผู้ใช้หนึ่งรายการ (หรือเวิร์กโฟลว์เชิงตรรกะหนึ่งรายการ) และถูกส่งผ่านทุกคำขอ การ retry และการเรียกข้ามเซอร์วิส เมื่อมีการครอบคลุมแบบ end-to-end จริง ๆ คุณสามารถเริ่มจากคำร้องเรียนของผู้ใช้แล้วดึงไทม์ไลน์เต็มข้ามระบบได้

คนมักสับสนกับ ID ที่คล้ายกัน นี่คือการแยกที่ชัดเจน:

  • Correlation ID: รวมทุกอย่างที่เกี่ยวข้องกับการกระทำหนึ่งรายการ (เช่น "บันทึกการตั้งค่า").
  • Request ID: ระบุ HTTP request เดียว การ retry จะสร้าง request ID ใหม่.
  • Trace ID: ใช้โดยเครื่องมือ distributed tracing; เป้าหมายคล้ายกัน มักสร้างโดยไลบรารี tracing.
  • Session ID: ระบุ session ของผู้ใช้ข้ามหลายการกระทำ; กว้างเกินไปสำหรับการดีบักเหตุการณ์เดียว.

สิ่งที่ "ดี" ดูตรงไปตรงมา: ผู้ใช้รายงานปัญหา คุณขอ correlation ID ที่แสดงใน UI (หรือในหน้าซัพพอร์ต) แล้วใครก็ได้ในทีมค้นหาเรื่องเต็มได้ในไม่กี่นาที คุณจะเห็นคำขอจาก frontend การตอบจาก API ขั้นตอน backend และผลจากฐานข้อมูล ทั้งหมดถูกเชื่อมด้วย ID เดียวกัน

ตัดสินใจคอนเวนชันสำหรับ correlation ID

ก่อนจะสร้างอะไร ให้ตกลงกฎไม่กี่ข้อ ถ้าทีมแต่ละทีมเลือกชื่อ header หรือฟิลด์ล็อกต่างกัน ฝ่ายซัพพอร์ตก็ยังต้องเดา

เริ่มจากชื่อหลักชื่อเดียวและใช้มันทุกที่ ตัวเลือกที่พบบ่อยคือ HTTP header อย่าง X-Correlation-Id พร้อมฟิลด์ล็อกเชิงโครงสร้างอย่าง correlation_id เลือกการสะกดและตัวพิมพ์ตัวเดียว จดเอกสาร และตรวจสอบว่า reverse proxy หรือเกตเวย์จะไม่เปลี่ยนชื่อหรือทิ้ง header นั้น

เลือกฟอร์แมตรที่สร้างง่ายและแชร์ในตั๋วหรือแชทได้ปลอดภัย UUID ทำงานได้ดีเพราะไม่ชนและเรียบง่าย เก็บให้สั้นพอที่จะคัดลอก แต่ไม่สั้นจนเสี่ยงชน ความสม่ำเสมอสำคัญกว่าความฉลาดเฉพาะตัว

นอกจากนี้ ให้กำหนดว่าต้องแสดง ID ที่ไหนเพื่อให้มนุษย์ใช้งานได้จริง ในทางปฏิบัติ นั่นหมายความว่ามันต้องปรากฏในคำขอ ล็อก และผลลัพธ์ข้อผิดพลาด และค้นหาได้ในเครื่องมือที่ทีมใช้

กำหนดระยะเวลาที่ ID หนึ่งตัวควรมีชีวิต ค่าเริ่มต้นที่ดีคือหนึ่งการกระทำของผู้ใช้ เช่น "กด Pay" หรือ "บันทึกโปรไฟล์" สำหรับเวิร์กโฟลว์ยาวที่ข้ามเซอร์วิสและคิว ให้เก็บ ID เดิมจนกว่าเวิร์กโฟลว์จะจบ แล้วเริ่มใหม่สำหรับการกระทำถัดไป หลีกเลี่ยงการใช้ "ID เดียวสำหรับทั้ง session" เพราะการค้นหาจะมีเสียงดังเร็ว

กฎแข็งข้อหนึ่ง: ห้ามใส่ข้อมูลส่วนบุคคลใน ID เลย ไม่มีอีเมล เบอร์โทร รหัสผู้ใช้ หรือเลขคำสั่ง ถ้าต้องการบริบทนั้น ให้ล็อกในฟิลด์แยกต่างหากที่มีการควบคุมความเป็นส่วนตัว

สร้าง ID ใน frontend (แนวปฏิบัติ)

ตำแหน่งที่ง่ายที่สุดในการเริ่ม correlation ID คือช่วงที่ผู้ใช้เริ่มการกระทำที่คุณสนใจ: กด "บันทึก" ส่งฟอร์ม หรือลูปที่กระตุ้นหลายคำขอ ถ้ารอให้ backend สร้าง คุณมักจะเสียส่วนแรกของเรื่อง (ข้อผิดพลาด UI, retry, คำขอที่ยกเลิก)

ใช้ฟอร์แมตรันดอมและไม่ซ้ำ UUID v4 เป็นตัวเลือกทั่วไปเพราะสร้างง่ายและโอกาสชนต่ำ เก็บให้ทึบ (ไม่ใส่ชื่อผู้ใช้ อีเมล หรือ timestamp) เพื่อไม่รั่วข้อมูลส่วนตัวลงใน header และล็อก

สร้างและเก็บ ID สำหรับหนึ่งเวิร์กโฟลว์

ถือว่า "เวิร์กโฟลว์" เป็นการกระทำของผู้ใช้หนึ่งรายการที่อาจกระตุ้นหลายคำขอ: ตรวจสอบ, อัปโหลด, สร้างเรคอร์ด แล้วรีเฟรชลิสต์ สร้าง ID หนึ่งตัวเมื่อเวิร์กโฟลว์เริ่ม แล้วเก็บมันจนกว่าเวิร์กโฟลว์จะจบ (สำเร็จ ล้มเหลว หรือผู้ใช้ยกเลิก) รูปแบบง่าย ๆ คือเก็บไว้ใน state ของคอมโพเนนต์หรือออบเจกต์ context คำขอขนาดเล็ก

ถ้าผู้ใช้เริ่มการกระทำเดียวกันสองครั้ง ให้สร้าง correlation ID ใหม่สำหรับครั้งที่สอง จะช่วยให้ซัพพอร์ตแยกความต่างระหว่าง "คลิกเดียวแล้ว retry" กับ "ส่งสองครั้งแยกกัน"

แนบมันไปกับทุกคำขอของเวิร์กโฟลว์นั้น

เพิ่ม ID ไปกับทุกการเรียก API ที่เวิร์กโฟลว์นั้นกระตุ้น โดยปกติผ่าน header เช่น X-Correlation-ID ถ้าคุณใช้ไคลเอนต์ API ร่วม (fetch wrapper, Axios instance ฯลฯ) ให้ส่ง ID หนึ่งครั้งแล้วให้ไคลเอนต์ฉีดเข้าไปในทุกคำขอ

// 1) when the user action starts
const correlationId = crypto.randomUUID(); // UUID v4 in modern browsers

// 2) pass it to every request in this workflow
await api.post('/orders', payload, {
  headers: { 'X-Correlation-ID': correlationId }
});

await api.get('/orders/summary', {
  headers: { 'X-Correlation-ID': correlationId }
});

ถ้า UI ทำคำขอแบ็คกราวด์ที่ไม่เกี่ยวข้องกับการกระทำ (polling, analytics, auto-refresh) อย่าใช้ ID เดียวกับเวิร์กโฟลว์นั้น เก็บ correlation IDs ให้เป็นจังหวะเดียว เรื่องเดียวต่อ ID

ส่งต่อ ID ผ่าน API ของคุณอย่างน่าเชื่อถือ

เมื่อคุณสร้าง correlation ID ในเบราว์เซอร์ งานของคุณคือ: มันต้องออกจาก frontend กับทุกคำขอและมาถึงไม่เปลี่ยนแปลงในทุกขอบเขตของ API นี่คือจุดที่มักพังเมื่อทีมเพิ่ม endpoint ใหม่, client ใหม่, หรือ middleware ใหม่

ค่าเริ่มต้นที่ปลอดภัยที่สุดคือ header HTTP บนทุกการเรียก (เช่น X-Correlation-Id) Header เพิ่มได้ง่ายที่จุดเดียว (fetch wrapper, Axios interceptor, เลเยอร์เครือข่ายมือถือ) และไม่ต้องเปลี่ยน payload

ถ้าคุณมีคำขอข้ามต้นกำเนิด (cross-origin) ให้แน่ใจว่า API อนุญาต header นั้น มิฉะนั้นเบราว์เซอร์อาจบล็อกอย่างเงียบ ๆ และคุณจะคิดว่ากำลังส่งอยู่ทั้งที่ไม่ได้ส่ง

ถ้าจำเป็นต้องใส่ ID ใน query string หรือ request body (บางเครื่องมือภายนอกหรือการอัปโหลดไฟล์บังคับ) ให้ทำให้สม่ำเสมอและเขียนลงเอกสาร เลือกชื่อฟิลด์เดียวและใช้ทุกที่ อย่าใช้สลับกันเช่น correlationId, requestId, และ cid ขึ้นอยู่กับ endpoint

การ retry เป็นกับดักอีกอย่างหนึ่ง ถ้าเป็นการ retry ของการกระทำเดียวกัน ให้เก็บ correlation ID เดิม เช่น: ผู้ใช้กด "บันทึก" แล้วเน็ตล่ม client retry การ POST ซัพพอร์ตควรเห็นเส้นทางเชื่อมเดียว ไม่ใช่สามเส้นที่ไม่เกี่ยวข้อง การคลิกใหม่ของผู้ใช้ (หรืองานแบ็คกราวด์ใหม่) ควรได้ ID ใหม่

สำหรับ WebSockets ให้ใส่ ID ในซองข้อความไม่ใช่แค่ในการจับมือเริ่มต้น การเชื่อมต่อหนึ่งอาจพกการกระทำของผู้ใช้หลายรายการ

ถ้าต้องการตรวจสอบความน่าเชื่อถืออย่างรวดเร็ว ให้ทำง่าย ๆ:

  • helper ของ client เดียวเพิ่ม header ในทุกคำขอ
  • retry ใช้ ID เดิมสำหรับการกระทำเดียวกัน
  • ทางเลือกใน body/query ใช้ชื่อฟิลด์เดียวที่จดไว้
  • ข้อความ WebSocket รวม correlationId ชัดเจน

ตั้งค่าพฤติกรรมที่ entry point ของ API

Prototype a checkout flow safely
Model retries and timeouts while keeping one ID per user action.

ขอบระบบของ API ของคุณ (gateway, load balancer, หรือบริการเว็บแรกที่รับทราฟฟิก) คือที่ที่ correlation IDs จะเชื่อถือได้หรือกลายเป็นการเดา ปฏิบัติต่อ entry point นี้เป็นแหล่งความจริง

รับ ID ขาเข้าถ้าคลไคลเอนต์ส่งมา แต่ไม่ควรสมมติว่ามันมีตลอด ให้สร้างใหม่ทันทีถ้ามันขาดหายเพื่อให้ทำงานต่อได้เมื่อบาง client เก่าเกินไปหรือคอนฟิกผิด

ทำการตรวจสอบแบบเบา ๆ เพื่อไม่ให้ค่าที่ไม่ดีมาปนเปื้อนล็อก เก็บให้ยืดหยุ่น: ตรวจความยาวและอักขระที่อนุญาต แต่หลีกเลี่ยงรูปแบบเข้มงวดที่ปฏิเสธทราฟฟิกจริง ตัวอย่าง: อนุญาต 16-64 ตัวอักษรและตัวอักษร ตัวเลข ขีด และขีดล่าง หากค่าไม่ผ่าน ให้แทนด้วย ID ใหม่แล้วทำต่อ

ทำให้ ID มองเห็นได้สำหรับผู้เรียก แสดงมันใน response headers เสมอ และรวมไว้ใน bodies ของ error ด้วย วิธีนี้ผู้ใช้คัดลอกจาก UI ได้ หรือซัพพอร์ตสามารถขอแล้วหาทางแทรซได้ทันที

นโยบาย edge เชิงปฏิบัติจะเป็นแบบนี้:

  • อ่าน X-Correlation-ID (หรือ header ที่เลือก) จากคำขอ
  • ถ้าขาดหรือไม่ถูกต้อง ให้สร้าง ID ใหม่และแนบกับ context ของคำขอ
  • เพิ่ม X-Correlation-ID ในทุกการตอบกลับ รวมถึงข้อผิดพลาด
  • เมื่อส่ง JSON error ให้สะท้อน ID นั้นใน payload

ตัวอย่าง payload ของข้อผิดพลาด (สิ่งที่ซัพพอร์ตควรเห็นในตั๋วและภาพหน้าจอ):

{
  "error": {
    "code": "PAYMENT_FAILED",
    "message": "We could not confirm the payment.",
    "correlation_id": "c3a8f2d1-9b24-4c61-8c4a-2a7c1b9c2f61"
  }
}

ส่งต่อ ID ข้ามบริการ backend

เมื่อคำขอเข้าถึง backend ให้ถือว่า correlation ID เป็นส่วนหนึ่งของ context ของคำขอ ไม่ใช่สิ่งที่เก็บในตัวแปร global ตัวแปร global จะพังเมื่อคุณจัดการคำขอสองคำขอพร้อมกัน หรือเมื่องานแบบอะซิงก์ยังทำต่อหลังการตอบ

กฎที่ขยายได้: ทุกฟังก์ชันที่อาจล็อกหรือเรียกเซอร์วิสอื่นควรรับ context ที่มี ID ลงไปด้วย ในบริการที่เขียนด้วย Go นั่นมักหมายถึงการส่ง context.Context ผ่าน handler โลจิกธุรกิจ และโค้ด client

เมื่อ Service A เรียก Service B ให้คัดลอก ID เดิมเข้าไปในคำขอขาออก อย่าสร้าง ID ใหม่กลางทาง เว้นแต่ว่าคุณเก็บของเดิมไว้เป็นฟิลด์แยกต่างหาก (เช่น parent_correlation_id) หากเปลี่ยน ID ซัพพอร์ตจะเสียเส้นเดียวที่เชื่อมเรื่องทั้งหมด

การส่งต่อมักถูกลืมในจุดที่คาดไว้: งานแบ็คกราวด์ที่ถูก kick off ระหว่างคำขอ, retry ในไลบรารี client, webhook ที่เรียกทีหลัง, และการเรียกแบบ fan-out ข้อความอะซิงก์ใด ๆ (คิว/งาน) ควรพก ID และ logic retry ทุกตัวควรเก็บมันไว้

ล็อกควรถูกโครงสร้างด้วยชื่อฟิลด์คงที่อย่าง correlation_id เลือกการสะกดหนึ่งแบบและใช้ให้ทั่ว หลีกเลี่ยงการผสม requestId, req_id, และ traceId เว้นแต่คุณมีแผนที่ชัดเจน

ถ้าเป็นไปได้ ให้รวม ID ในการมองเห็นฐานข้อมูลด้วย วิธีปฏิบัติที่ใช้งานได้คือเพิ่มมันลงใน comment ของ query หรือ metadata ของ session เพื่อให้ slow query logs แสดงมัน เมื่อมีคนรายงานว่า "ปุ่มบันทึกค้าง 10 วินาที" ซัพพอร์ตสามารถค้นหา correlation_id=abc123 แล้วเห็นล็อก API การเรียกเซอร์วิสถัดไป และคำสั่ง SQL ที่ช้า

รวม ID ในล็อกที่มนุษย์ใช้งานได้

correlation ID มีประโยชน์เมื่อคนสามารถหาและตามมันได้ ทำให้มันเป็นฟิลด์ล็อกชั้นหนึ่ง (ไม่ฝังในสตริงข้อความ) และรักษารูปแบบบรรทัดล็อกให้สม่ำเสมอข้ามเซอร์วิส

ฟิลด์ล็อกที่ทำให้ ID ใช้งานได้

จับคู่ correlation ID กับชุดฟิลด์เล็ก ๆ ที่ตอบคำถาม: เมื่อไหร่ ที่ไหน อะไร ใคร (ในแบบปลอดภัยต่อผู้ใช้) สำหรับทีมส่วนใหญ่ นั่นคือ:

  • timestamp (มี timezone)
  • service และ env (api, worker, prod, staging)
  • route (หรือชื่อ operation) และ method
  • status และ duration_ms
  • ตัวระบุผู้ใช้ที่ปลอดภัย (เช่น account_id หรือ user id แบบ hash ไม่ใช่อีเมล)

ด้วยสิ่งนี้ ซัพพอร์ตสามารถค้นหาตาม ID ยืนยันว่ากำลังดูคำขอที่ถูกต้อง และเห็นว่าเซอร์วิสไหนจัดการมัน

ควรล็อกอะไรเมื่อเริ่ม สำเร็จ และล้มเหลว

มุ่งหา breadcrumb ที่มีประโยชน์ต่อคำขอ ไม่ใช่ทรานสคริปต์ทั้งหมด

  • เริ่ม: correlation ID, route, ตัวระบุผู้ใช้ที่ปลอดภัย, และอินพุตสำคัญ (สรุป)
  • สำเร็จ: correlation ID, status, duration, และผลลัพธ์สั้น ๆ (เช่น rows=12)
  • ล้มเหลว: correlation ID, ประเภทข้อผิดพลาด, บริบทปลอดภัย, และตำแหน่งที่เกิด (handler, dependency)

เพื่อหลีกเลี่ยงล็อกเสียงดังเกินไป ให้เก็บรายละเอียดระดับ debug นอกค่าเริ่มต้นและโปรโมตเฉพาะเหตุการณ์ที่ช่วยตอบคำถามว่า "มันล้มเหลวที่ไหน?" ถ้าบรรทัดไหนไม่ช่วยหาจุดบกพร่องหรือวัดผลกระทบ มันอาจไม่ควรเป็น info-level

การ redaction สำคัญเท่ากับโครงสร้าง ห้ามใส่ PII ใน correlation ID หรือล็อก: ไม่มีอีเมล ชื่อ เบอร์ ที่อยู่เต็ม หรือโทเคนดิบ หากต้องระบุตัวผู้ใช้ให้ล็อก internal ID หรือ hash ทางทางเดียว

ตัวอย่าง: แทรซ์รายงานผู้ใช้จาก UI ถึงฐานข้อมูล

Set a reliable API entry policy
Generate gateway logic that accepts, validates, and echoes X-Correlation-ID automatically.

ผู้ใช้ส่งข้อความถึงซัพพอร์ตว่า "เช็คเอาต์ล้มเหลวเมื่อกด Pay" คำถามติดตามที่ดีที่สุดคือ: "ช่วยวาง correlation ID ที่แสดงบนหน้าข้อผิดพลาดให้หน่อยได้ไหม?" พวกเขาตอบด้วย cid=9f3c2b1f6a7a4c2f

ซัพพอร์ตมีด้ามจับเดียวที่เชื่อม UI, API, และงานฐานข้อมูล เป้าหมายคือต้นทางทุกบรรทัดล็อกสำหรับการกระทำนั้นมี ID เดียวกัน

ซัพพอร์ตค้นหาในล็อก 9f3c2b1f6a7a4c2f แล้วเห็นการไหล:

frontend  INFO  cid=9f3c2b1f6a7a4c2f event="checkout_submit" cart=3 items
api       INFO  cid=9f3c2b1f6a7a4c2f method=POST path=/api/checkout user=1842
api       ERROR cid=9f3c2b1f6a7a4c2f msg="payment failed" provider=stripe status=502

จากนั้นวิศวกรตาม ID เดียวกันต่อไปยัง hop ถัดไป จุดสำคัญคือต้องให้การเรียกเซอร์วิส backend (และงานคิวใด ๆ) ส่งต่อ ID ด้วย

payments  INFO  cid=9f3c2b1f6a7a4c2f action="charge" amount=49.00 currency=USD
payments  ERROR cid=9f3c2b1f6a7a4c2f err="timeout" upstream=stripe timeout_ms=3000
db        INFO  cid=9f3c2b1f6a7a4c2f query="insert into failed_payments" rows=1

ตอนนี้ปัญชัดเจน: เซอร์วิสชำระเงิน timeout หลัง 3 วินาที และบันทึกข้อผิดพลาดถูกเขียน วิศวกรสามารถตรวจดูดีพลอยล่าสุด ยืนยันว่าการตั้งค่า timeout เปลี่ยนหรือไม่ และดูว่ากำลังเกิด retry หรือไม่

เพื่อปิดวง ให้ทำสี่อย่างตรวจสอบ:

  • แก้สาเหตุ (เช่นปรับ timeout แล้วเพิ่ม retry ปลอดภัย)
  • ทำให้ข้อผิดพลาดที่ผู้ใช้เห็นรวม correlation ID
  • มอนิเตอร์หาล็อกรูปแบบข้อผิดพลาดเดียวกันกับ ID ต่าง ๆ
  • ยืนยันว่า ID อยู่รอดในทุก hop (รวม worker และข้อความคิว)

ความผิดพลาดทั่วไปและวิธีหลีกเลี่ยง

วิธีที่เร็วที่สุดที่จะทำให้ correlation IDs ไร้ประโยชน์คือการทำให้สายเชื่อมขาด ความล้มเหลวส่วนใหญ่เกิดจากการตัดสินใจเล็ก ๆ ที่ดูไม่เป็นไรตอนสร้าง แต่กลับเป็นปัญหาเมื่อซัพพอร์ตต้องการคำตอบ

ความผิดพลาดคลาสสิกคือการสร้าง ID ใหม่ทุก hop ถ้าเบราว์เซอร์ส่ง ID เกตเวย์ API ควรเก็บไว้ ไม่ใช่แทนที่มัน ถ้าคุณต้องมี ID ภายในด้วยจริง ๆ (สำหรับข้อความคิวหรืองานแบ็คกราวด์) ให้เก็บของเดิมไว้เป็น parent field เพื่อให้เรื่องยังเชื่อมกัน

ช่องว่างที่พบบ่อยอีกอย่างคือล็อกไม่ครบ ทีมเพิ่ม ID ที่ API แรกแต่ลืมใน worker, scheduled job, หรือเลเยอร์การเข้าถึงฐานข้อมูล ผลคือทางตัน: คุณเห็นคำขอเข้าระบบแต่ไม่เห็นมันไปที่ไหนต่อ

หลีกเลี่ยงปัญหา "ความสับสนเรื่องชื่อ"

แม้ ID จะมีทุกที่ ก็ยากค้นถ้าทุกเซอร์วิสใช้ชื่อฟิลด์หรือฟอร์แมตต่างกัน เลือกชื่อเดียวและยึดมันข้าม frontend, API, และล็อก (เช่น correlation_id) เลือกฟอร์แมตหนึ่ง (มักเป็น UUID) และถือว่ามัน case-sensitive เพื่อการคัดลอกวางทำงาน

อย่าสูญเสีย ID เมื่อเกิดข้อผิดพลาด ถ้า API ส่ง 500 หรือ validation error ให้รวม correlation ID ในการตอบข้อผิดพลาด (และถ้าเป็นไปได้ใน response header) เพื่อให้ผู้ใช้สามารถวางในแชทซัพพอร์ตแล้วทีมคุณจะติดตามทันที

การทดสอบรวดเร็ว: คนซัพพอร์ตเริ่มจาก ID เดียวแล้วตามมันผ่านทุกบรรทัดล็อกที่เกี่ยวข้องไหม รวมถึงความล้มเหลว?

เช็คลิสต์ด่วนเพื่อยืนยันการครอบคลุมแบบ end-to-end

Make logs searchable by default
Add a consistent correlation_id field to every log line across services.

ใช้เป็นการตรวจพื้นฐานก่อนบอกซัพพอร์ตว่า "แค่ค้นหาในล็อก" วิธีนี้ใช้ได้เมื่อทุก hop ปฏิบัติตามกฎเดียวกัน

ข้อที่ต้องผ่าน

  • คุณมีฟอร์แมต ID เดียวและชื่อ header เดียวที่ใช้ทุกที่ (frontend, gateway, API, worker)
  • frontend สร้าง (หรือรับ) ID เมื่อการกระทำของผู้ใช้เริ่ม และเก็บมันจนการกระทำนั้นจบ
  • entry point ของ API สร้าง ID ถ้าขาด และส่งมันกลับใน response headers เสมอ
  • ทุก backend service ใส่ correlation_id ในล็อกที่เกี่ยวข้องกับคำขอเป็นฟิลด์เชิงโครงสร้าง
  • ทีม on-call สามารถวาง ID หนึ่งตัวในช่องค้นหาล็อกแล้วเห็นเส้นทางทั้งหมดในไม่กี่นาที: edge request, auth, การเรียกเซอร์วิส, การดำเนินการฐานข้อมูล, และ retry

ถ้าตรวจพบข้อใดข้อหนึ่งล้มเหลว ให้แก้แบบนี้

เลือกการเปลี่ยนแปลงที่เล็กที่สุดที่จะทำให้สายเชื่อมไม่ขาด

  • ถ้า ID เปลี่ยนกลางทาง หยุดสร้าง ID ใหม่ในเซอร์วิสภายใน เก็บ correlation_id เดิมและเพิ่ม span_id แยกถ้าต้องการรายละเอียด
  • ถ้าล็อกขาดฟิลด์ ให้เพิ่ม logging middleware เพื่อไม่ให้วิศวกรต้องจำใส่เอง
  • ถ้าซัพพอร์ตขอ ID ไม่ได้ ให้แน่ใจว่า UI แสดงมันในหน้าข้อผิดพลาดและเกตเวย์สะท้อนมันกลับในทุกการตอบ

การทดสอบที่จับช่องว่างได้: เปิด devtools, เริ่มการกระทำหนึ่งครั้ง, คัดลอก correlation ID จากคำขอแรก, แล้วยืนยันว่าเห็นค่าเดียวกันในคำขอ API ที่เกี่ยวข้องทั้งหมดและในทุกบรรทัดล็อกที่เกี่ยวข้อง

ขั้นตอนถัดไป: ผนวกมันเข้าในกระบวนการ build

Correlation IDs จะช่วยเมื่อทุกคนใช้เหมือนกันทุกครั้ง ถือพฤติกรรม correlation ID เป็นส่วนที่ต้องมีของการปล่อยงาน ไม่ใช่แค่การปรับปรุงล็อกเล็กน้อย

เพิ่มขั้นตอน traceability เล็กน้อยใน definition of done ของคุณสำหรับ endpoint ใหม่หรือการกระทำ UI ครอบคลุมว่ามันสร้าง (หรือ reuse) อย่างไร อยู่ที่ไหนใน flow header ไหนพาไป และแต่ละบริการทำอย่างไรเมื่อ header ขาด

เช็คลิสต์น้ำหนักเบามักเพียงพอ:

  • Frontend: สร้างหรือ reuse ID หนึ่งตัวต่อการกระทำของผู้ใช้ และแนบมันกับทุกการเรียก API ของการกระทำนั้น
  • API entry point: รับ header, ตรวจหรือสร้าง, แล้ว echo มันกลับใน response
  • Backend: ส่งต่อไปยังเซอร์วิสลงล่างและงาน และใส่มันในล็อก
  • Logging: รักษาชื่อฟิลด์ให้สอดคล้องกัน (เช่น correlation_id) ข้ามแอปและเซอร์วิส
  • Reviews: ปฏิเสธ PR ที่เพิ่ม endpoint โดยไม่มีเทสต์ยืนยันว่า ID ปรากฏในล็อก

ซัพพอร์ตควรมีสคริปต์ง่าย ๆ เพื่อให้การดีบักเร็วและทำซ้ำได้ ตัดสินใจว่า ID แสดงให้ผู้ใช้เห็นที่ไหน (เช่นปุ่ม "Copy debug ID" บน dialog ข้อผิดพลาด) และจดว่าซัพพอร์ตควรถามอะไรและค้นหาที่ไหน

ก่อนจะพึ่งพามันใน production ให้รัน flow แบบ staged ที่เหมือนการใช้งานจริง: กดปุ่ม, กระตุ้น validation error, แล้วทำการกระทำให้เสร็จ ยืนยันว่าคุณตาม ID เดียวกันจากคำขอเบราว์เซอร์ ผ่านล็อก API เข้า worker ใด ๆ และถึงล็อกคำสั่งฐานข้อมูลถ้าคุณบันทึกไว้

ถ้าคุณสร้างแอปบน Koder.ai จะช่วยให้เขียน header และคอนเวนชันล็อกของ correlation ID ลงใน Planning Mode เพื่อให้ React frontend และ Go services ที่สร้างขึ้นเริ่มสอดคล้องกันโดยดีตั้งแต่ต้น

คำถามที่พบบ่อย

What is a correlation ID in plain terms?

A correlation ID คือรหัสตัวเดียวที่ใช้แท็กทุกอย่างที่เกี่ยวข้องกับการกระทำหรือเวิร์กโฟลว์ของผู้ใช้หนึ่งรายการ ตั้งแต่เบราว์เซอร์ APIs เซอร์วิส และงานแบ็คกราวด์ ช่วยให้ฝ่ายซัพพอร์ตเริ่มจาก ID เดียวแล้วดึงไทม์ไลน์เต็มได้ แทนการเดาว่าแถวล็อกไหนเกี่ยวข้องกัน

When should I use a correlation ID instead of a session ID or request ID?

ใช้ correlation ID เมื่อต้องการดีบักกรณีเฉพาะแบบ end-to-end เช่น “กด Pay แล้วล้มเหลว” เพราะ session ID กว้างเกินไป (ครอบหลายการกระทำ) และ request ID แคบเกินไป (ครอบเพียง HTTP request เดียวและเปลี่ยนเมื่อ retry)

Where should the correlation ID be created: frontend or backend?

สร้างที่ frontend โดยดีที่สุดคือเมื่อต้นของการกระทำของผู้ใช้เริ่มขึ้น (เช่น กดยืนยัน ส่งฟอร์ม หรือเริ่ม flow หลายขั้น) การสร้างที่ฝั่งไคลเอนต์ช่วยเก็บส่วนแรกของเรื่องไว้ เช่น ข้อผิดพลาดใน UI การ retry หรือตัดการร้องขอ

What format should a correlation ID use?

ใช้ค่าที่ทึบและไม่ระบุตัวบุคคล เช่น UUID v4 ที่คัดลอกได้ง่ายและปลอดภัย อย่าใส่ข้อมูลส่วนตัว (อีเมล เบอร์ หรือเลขคำสั่ง) ลงใน ID ให้เก็บบริบทเหล่านั้นในฟิลด์ล็อกที่แยกต่างหากและมีการควบคุมความเป็นส่วนตัว

What header and log field name should we standardize on?

กำหนดชื่อ header เดียวและใช้ให้ทั่ว เช่น X-Correlation-ID แล้วล็อกภายใต้ฟิลด์ที่สอดคล้องกันอย่าง correlation_id ความสม่ำเสมอสำคัญกว่าชื่อที่เฉพาะเจาะจง

Should a retry reuse the same correlation ID or generate a new one?

ถ้ามันยังเป็นการกระทำเดิม (เช่น client retry ของ POST เดิม) ให้ใช้ correlation ID เดิม เพื่อให้ล็อกเชื่อมต่อกัน สร้าง ID ใหม่เมื่อเป็นการกระทำใหม่ของผู้ใช้จริง ๆ เช่นกดอีกครั้งในเวลาถัดไป

What should the API gateway or first service do if the header is missing?

API entry point ควรรับ ID ที่มาพร้อมคำขอ ถ้ามี และถ้าไม่มีหรือไม่ถูกต้องให้สร้างใหม่ทันที แล้ว echo ค่า ID นั้นกลับใน response (รวมถึง error) เพื่อให้ผู้ใช้หรือซัพพอร์ตคัดลอกได้จาก UI

How do we propagate the correlation ID across microservices and background jobs?

ใส่ลงใน context ของการร้องขอและคัดลอกไปกับทุกการเรียกถัดไป รวมทั้ง HTTP/gRPC ภายในและงานคิว หลีกเลี่ยงการสร้าง correlation ID ใหม่กลางทาง หากต้องการรายละเอียดเพิ่มให้สร้างตัวระบุภายในแยกต่างหากโดยไม่ทำลายสายเชื่อมต่อเดิม

What needs to be in logs for correlation IDs to actually help support?

ล็อกเป็นฟิลด์เชิงโครงสร้างตัวแรก เช่น correlation_id เพื่อให้ค้นหาได้ง่าย จับคู่กับฟิลด์สำคัญอื่น ๆ เช่น ชื่อเซอร์วิส เส้นทาง สถานะ เวลา และตัวระบุผู้ใช้ที่ไม่เปิดเผยข้อมูล เพื่อให้แทรซ์ไม่จบที่เหตุการณ์ล้มเหลว

How can we quickly verify we have true end-to-end coverage?

ทดสอบโดยกดการกระทำหนึ่งครั้ง คัดลอก correlation ID จากคำขอแรกหรือหน้าข้อผิดพลาด แล้วยืนยันว่า ID เดียวกันปรากฏใน header ของคำขอที่เกี่ยวข้องทั้งหมดและบรรทัดล็อกของทุกเซอร์วิสที่เกี่ยวข้อง ถ้า ID หายไปใน worker, retry หรือ response ของ error แก้จุดนั้นก่อน

Related posts