Monitor Log 101 — เก็บ request ไว้ดูย้อนหลังโดยไม่เก็บความลับ
request log ต่างจาก log ที่เราเขียนเองยังไง · เก็บอะไรบ้าง · ทำไมมี record แต่ไม่มี body · ของอ่อนไหวถูกปิดยังไง · และทำไมมันไม่ทำให้ API ช้าลง
อัปเดต: 2026-09-07
Monitor Log 101 — เก็บ request ไว้ดูย้อนหลังโดยไม่เก็บความลับ
เล่มนี้สำหรับคนที่เคยเขียนแต่
_logger.LogInformation(...)แล้วสงสัยว่า “request log” ที่ทีม security พูดถึง มันคนละอย่างกันยังไง · ขั้นตอนติดตั้งอยู่ที่ ติดตั้ง F
TL;DR — อ่าน 30 วิ
- log ที่เราเขียนเองตอบว่า “โค้ดคิดอะไรอยู่” · request log ตอบว่า “ใครเรียกอะไร ได้อะไรกลับไป”
- เก็บทั้ง metadata (ใคร · เส้นไหน · status · ใช้เวลาเท่าไหร่) และ เนื้อ request/response
- ช่องที่แปะ
[EncryptedField]ไว้จะถูกแทนด้วย***ก่อนออกจาก service — ไม่มีเลขบัตรใครไหลไปถึงปลายทาง - มี record ไม่ได้แปลว่ามี body — มี 4 กรณีที่ body หายไปโดยตั้งใจ
- ไม่ทำให้ API ช้าลง เพราะไม่ได้ส่งตอนนั้น แต่หย่อนลงคิวแล้วมีตัวส่งเบื้องหลังทยอยส่ง
1. มันคนละอย่างกับ log ที่เราเขียนเอง
_logger.LogInformation(...) | request log | |
|---|---|---|
| ใครเขียน | เราเขียนเองทีละบรรทัดในโค้ด | middleware เก็บให้อัตโนมัติ |
| ตอบคำถาม | โค้ดเดินไปถึงไหน คิดอะไรอยู่ | ใครเรียกอะไร ส่งอะไรมา ได้อะไรกลับ |
| ไปอยู่ที่ไหน | stdout ของ pod แล้วหายตาม pod | ส่งเข้า topic → log service → เก็บลงตาราง |
| ใช้ตอนไหน | debug ตอนพัฒนา | ตรวจสอบย้อนหลัง (audit) ว่าเมื่อวานใครทำอะไรไว้ |
⇒ อันแรกเป็นของนักพัฒนา อันหลังเป็นของทีมที่ต้องตอบคำถามว่า “ตกลงวันนั้นใครกดปุ่มนั้น”
2. เก็บอะไรบ้าง
metadata ทุกครั้งที่มี record — เส้นที่เรียก (route template ไม่ใช่ URL ที่มีค่าจริง) · method · status ·
ใช้เวลากี่มิลลิวินาที · correlationId ไว้ตามงานข้าม service · oid ของคนเรียก · ขนาดของ body
เนื้อ body ทั้งขาเข้าและขาออก — เก็บ หลัง ผ่านด่านและถอดรหัสแล้ว ⇒ สิ่งที่เก็บได้คือ body ที่ handler ได้รับจริง ไม่ใช่ก้อน ciphertext ที่อ่านไม่ออก
3. 🔴 มี record ≠ มี body — 4 กรณีที่เนื้อหายไปโดยตั้งใจ
| กรณี | ทำไม |
|---|---|
Content-Type ไม่ใช่ JSON | ไฟล์ดาวน์โหลด · multipart · XML ไม่ได้ตั้งใจให้เก็บ |
| endpoint นอกขอบเขตที่เลือกไว้ | ไม่มี record เลยด้วยซ้ำ ไม่ใช่แค่ไม่มี body |
| endpoint ที่ผนึกทั้ง body ขาออก | สิ่งที่ไหลผ่านสายคือ ciphertext อ่านย้อนไม่ได้อยู่แล้ว |
| route อยู่ในรายการยกเว้น | ตั้งใจให้นับ call แต่ไม่เก็บ payload |
ผลที่กระทบการวางแผนโดยตรง — ถ้าเป้าหมายคือ “เปิดดูย้อนหลังว่า response จริงคืออะไร”
ต้องเลือก endpoint ที่ ไม่ได้ ผนึกทั้ง body ⇒ เส้นที่ใช้การเข้ารหัสรายฟิลด์เก็บได้ปกติ
โดยค่าที่อ่อนไหวถูกปิดเป็น *** ให้อยู่แล้ว
4. ของอ่อนไหวถูกปิดยังไง — และทำไมต้องปิดที่ backend
ช่องที่แปะ [EncryptedField] (ป้ายเดียวกับที่ใช้บอกว่า field ไหนต้องเข้ารหัสตอนเดินทาง)
จะถูกแทนด้วย *** ตอนประกอบ record ⇒ ไม่มีรายชื่อ field ให้ตั้งซ้ำอีกที่ แปะป้ายที่เดียวได้ทั้งสองงาน
ต้องการปิดเพิ่มนอกเหนือจากป้าย ใส่ path ของมันใน config ได้ เช่น $.customer.taxId
🔴 การปิดต้องเกิดก่อนข้อมูลออกจาก service เสมอ — ถ้าเก็บของจริงส่งไปแล้วค่อยไปปิดตอนแสดงผลที่หน้าจอ เท่ากับความลับถูกเก็บถาวรอยู่ในฐาน log แล้ว ใครเข้าถึง log ได้ก็เห็นหมด · การปิดตอนแสดงผลไม่ใช่การปิด
5. ทำไมมันไม่ทำให้ API ช้าลง
request → เก็บ metadata + body → หย่อนลงคิวในหน่วยความจำ → ตอบผู้ใช้ทันที
↓
ตัวส่งเบื้องหลังทยอยส่งเข้า topic → log service
- ผู้ใช้ไม่ต้องรอการบันทึก — response ไหลกลับทันทีที่ handler ทำงานเสร็จ
- คิวเต็มแล้ว record ใหม่ถูกทิ้งและนับไว้ ไม่ใช่หน่วง request ให้รอที่ว่าง
- ปลายทางล่มก็ไม่กระทบ API — คำขอยังทำงานปกติ ความล้มเหลวไปโผล่ใน log ของ service เอง
⇒ กลไกนี้ออกแบบให้ “ยอมเสีย log ดีกว่าทำให้ผู้ใช้พัง” · ถ้าต้องการรับประกันว่าไม่มี record ตกหล่นเลย นั่นเป็นคนละโจทย์ ต้องใช้ transaction ของ business ไม่ใช่กลไกนี้
6. เลือกเก็บเฉพาะบางเส้น — และ 2 วิธีที่ไม่เหมือนกัน
ค่าตั้งต้นคือ เก็บเฉพาะเส้นที่แปะป้ายไว้ (opt-in) เส้นที่ไม่แปะไม่มี record เลย
สองวิธีคัดออกที่ห้ามใช้แทนกัน
ป้าย [SkipRequestLog] | รายการ ExcludedRouteTemplates | |
|---|---|---|
| มี record ไหม | ไม่มีเลย | มี — route, status, เวลา, ขนาด |
| เก็บ body ไหม | ไม่มี record ก็ไม่มี body | ไม่เก็บ เก็บแต่ metadata |
| ใช้เมื่อ | การมีอยู่ของ call นั้นเองคือสิ่งที่ห้ามบันทึก | call ต้องถูกนับ แต่เนื้อห้ามเก็บ |
ศัพท์ที่เจอบ่อย
| คำ | แปลเป็นภาษาคน |
|---|---|
| request log | บันทึกว่าใครเรียกอะไรได้อะไรกลับ — คนละอย่างกับ log ที่เราเขียนเอง |
| audit trail | ร่องรอยที่เอาไว้ตอบย้อนหลังว่าเกิดอะไรขึ้น ใครทำ |
| mask | ปิดค่าจริงด้วย *** ก่อนบันทึกหรือก่อนส่งออก |
| correlation id | รหัสที่ติดไปกับคำขอเดียวกันข้าม service ไว้ตามงานว่าเรื่องนี้วิ่งไปไหนบ้าง |
| route template | รูปแบบของเส้น เช่น users/{id} — เก็บแบบนี้แทน URL จริงเพื่อไม่ให้ค่าจริงหลุดเข้า log |
| queue / background sender | คิวในหน่วยความจำและตัวส่งเบื้องหลัง ทำให้การบันทึกไม่หน่วงผู้ใช้ |
| fire-and-forget | ยิงแล้วไม่รอผล — ถ้าล้มเหลวก็ไม่ทำให้คำขอหลักพัง |
อ่านต่อ
- ติดตั้ง F — Monitor Log — ขั้นตอนจริง DI, pipeline 2 บรรทัดคนละที่, config
- Data Encryption & Masking 101 — กติกาการ mask ฉบับเต็ม และการเข้ารหัสตอนเก็บลง DB
- ของที่ทำเสร็จแล้ว ทำงานยังไง — ภาพลำดับว่ากลไกนี้อยู่ตรงไหนของคำขอเดียวกัน