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

Logs, wide events & การ correlate log↔trace

บท3 คุณ​ตาม หนึ่ง​ออเดอร์ เป็น trace เดียว​ข้าม RabbitMQ ได้​แล้ว บท4 คุณ​วัด​ระบบ​ด้วย metric แบบ RED โดย​ไม่​ให้ cardinality ระเบิด แต่​ตอน​ตี​สาม​ที่​ออเดอร์ #4821 ค้าง คุณ​ไม่​ได้​อยาก​ดู​แค่ “รูปทรง​ของ latency” — คุณ​อยาก​อ่าน ข้อความ ว่า​เกิด​อะไร​ขึ้น: “Payment ถูก decline ด้วย​เหตุผล​อะไร?” นั่น​คือ​งาน​ของ log และ​คำถาม​ที่​บท​นี้​ตอบ​คือ: ทำ​ยังไง​ให้ log บรรทัด​นั้น​ใน​บริบท Payment คลิก​เดียว​กระโดด ไป​ที่ trace ของออเดอร์​ที่​พัง​ได้ — ข้าม​บัส โดย​ไม่​ต้อง​เดา​ว่า​เป็น​ออเดอร์​เดียวกัน​ไหม

📦 code ตัวอย่าง

บท​นี้ instrument log ของ platform Order ตัว​เดิม (Ordering/Kitchen/Delivery/Payment บน WolverineFx outbox + RabbitMQ) — repo kaen-food-ordering (กำลัง​จัด​ทำ) เรา​ต่อยอด DiagnosticConfig กับ wiring AddOpenTelemetry() จาก​บท2–บท4 โดยตรง ไม่​เพิ่ม business logic ใหม่ แค่​ทำให้ log ของ​ทุก context สัมพันธ์​กับ trace และ enrich span ที่​มี​อยู่​แล้ว​ให้​เล่า​เรื่อง​ได้​ครบ

ใน​โลก​เดิม log คือ​บรรทัด​ข้อความ​ที่​ปะ​เข้า file แต่ OpenTelemetryOpenTelemetryมาตรฐาน​เปิด​ที่​เป็นกลาง​ต่อ vendor สำหรับ​สร้าง​และ​ส่ง telemetry; ตัว​มัน​เอง​ไม่ใช่ backend — storage/UI ปล่อย​ให้​เครื่องมือ​อื่นArchitecture มอง log เป็น LogRecord — โครงสร้าง​ที่​มี field ชัดเจน ไม่ใช่ string ก้อน​เดียว เอกสาร​ทางการ​ของ OTel นิยาม field หลัก​ไว้​ว่า (S5):

  • Timestamp — เวลา​ที่​เหตุการณ์​เกิด
  • SeverityText / SeverityNumber — ระดับ​ความ​รุนแรง (Information, Warning, Error …)
  • Body — ตัว​ข้อความ
  • TraceId / SpanId / TraceFlagsตัว​เชื่อม กับ trace ที่​กำลัง​ทำงาน​อยู่ (หัวใจ​ของ​บท​นี้)
  • Attributes — คู่ key-value ที่​ค้นหา​ได้
  • Resource — บริบท​ของ​แหล่ง​กำเนิด (เช่น service.name = payment-service)

จุด​สำคัญ​คือ field สาม​ตัว TraceId / SpanId / TraceFlags — พวก​มัน​คือ​สาย​ที่​ผูก log บรรทัด​หนึ่ง​กลับ​ไป​ที่ span ที่​ปล่อย​มัน​ออก​มา นี่​คือ​ความ​ต่าง​ระหว่าง log ที่​ลอย​เดี่ยวๆ กับ log ที่​เป็น​ส่วน​หนึ่ง​ของ เรื่อง​เล่า​เดียว

Structured vs unstructured: log ที่ Body เป็น​ข้อความ​ดิบ​ล้วน ("order 4821 failed") ต้อง​ใช้ regex แงะ​ทีหลัง — version ดิบ​ที่​ค้น​ยาก ส่วน Structured LoggingStructured Logginglog ที่​เป็น key-value ค้นหา​ได้ (เช่น {OrderId}) ไม่ใช่​ข้อความ​ดิบArchitecture เก็บ​ข้อมูล​เป็น​คู่ key-value ที่ query ได้ตรงๆ (order.id = "4821", outcome = "declined") OTel แนะนำ structured และ​เรา​จะ​ทำ​แบบ​นั้น

Logs Bridge เป็น​ของ​นัก​เขียน library — code app ยัง​ใช้ ILogger เหมือน​เดิม

หัวข้อ​ที่​มีชื่อ​ว่า “Logs Bridge เป็น​ของ​นัก​เขียน library — code app ยัง​ใช้ ILogger เหมือน​เดิม”

คุณ​อาจ​กังวล​ว่า​ต้อง​เรียนรู้ API log ใหม่​ทั้ง​ชุด — ไม่​ต้อง OTel มี​สิ่ง​ที่​เรียก​ว่า Logs Bridge API แต่​เอกสาร​ทางการ​ระบุ​ชัด​ว่า​มัน​มี​ไว้​ให้ นัก​เขียน logging library ใช้​เชื่อม library ของ​ตัวเอง​เข้า​กับ OTel เท่านั้น (S5) — code application ของ​คุณ​ไม่​ควร​เรียก​มัน​โดยตรง

สำหรับ .NET แปล​ว่า: คุณ​เขียน ILogger เหมือน​ที่​เขียน​มา​ตลอด แล้ว OTel เชื่อม (bridge) output ของ ILogger เข้า​ระบบ​เอง เงื่อนไข​เดียว​คือ​ต้อง​เปิด provider ฝั่ง logging:

// Program.cs — เปิด OTel logging (ยังจำเป็นแม้จะมี UseOtlpExporter() แล้ว)
builder.Logging.AddOpenTelemetry(o =>
{
o.IncludeFormattedMessage = true; // เก็บข้อความที่ประกอบเสร็จลง Body
o.IncludeScopes = true; // ดึง scope (คู่ key-value ที่ครอบอยู่) เข้าเป็น attribute
});
IncludeFormattedMessage / IncludeScopes คุม​อะไร​กัน​แน่ (อย่า​สับสน)

ค่า default ของ​ทั้ง​คู่​คือ false — แต่​ระวัง​ความ​เข้าใจ​ผิด​ที่​พบ​บ่อย: parameter ใน message template ({PaymentId}) ถูก​เก็บ​เป็น attribute ให้​อัตโนมัติ​จาก log state อยู่​แล้ว ไม่​ได้​ขึ้น​กับ​สอง​สวิตช์​นี้ สอง​สวิตช์​นี้​คุม​คนละ​เรื่อง: IncludeFormattedMessage คุม​ว่า​จะ​เก็บ ข้อความ​ที่​ประกอบ​เสร็จ (Body) ที่​อ่าน​ออก​ไหม — ถ้า​ไม่​เปิด Body อาจ​เหลือ​แค่ template ดิบ ("Payment {PaymentId} declined") โดย​ไม่มี​ค่าที่​แทน​เข้าไป; ส่วน IncludeScopes คุม​ว่า​คู่ key-value ที่​คุณ​ใส่​ผ่าน BeginScope(...) จะ​ถูก​เก็บ​เป็น attribute ไหม เปิด​ทั้ง​คู่​ไว้​เป็น good practice — แต่​ค่า​จาก template คุณ​ได้​เป็น attribute ที่​ค้นหา​ได้​มา​แต่​แรก​โดย​ไม่​ต้อง​พึ่ง​มัน

นี่​คือ​ส่วน​ที่​ทำให้ observability คุ้ม: Log-Trace CorrelationLog-Trace Correlationการ​ประทับ TraceId/SpanId/TraceFlags ลง​ทุก LogRecord อัตโนมัติ​จาก span context ปัจจุบัน เพื่อ​กระโดด​จาก log ไป​เทรซArchitecture เกิด​ขึ้น​เอง เมื่อ SDK ทำงาน​อยู่​และ​มี span active (Activity.Current ไม่​เป็น null) OTel จะ ประทับ TraceId, SpanId, TraceFlags ปัจจุบัน​ลง​บน​ทุก LogRecord โดย​อัตโนมัติ (S5) คุณ​ไม่​ต้อง​ส่ง trace id เข้า log เอง ไม่​ต้อง​เขียน code ผูก​อะไร​เพิ่ม​เลย

// ภายใน handler ที่มี span active อยู่แล้ว (จากบท3):
_logger.LogInformation(
"Payment {PaymentId} declined for order {OrderId}",
paymentId, orderId);
// LogRecord ที่ออกไปมี TraceId/SpanId ของ span ปัจจุบันติดไปด้วย — โดยที่เราไม่ได้เขียนบรรทัดผูกเอง

ผลลัพธ์​เชิง​ปฏิบัติ: ใน HyperDXHyperDXUI ของ ClickStack สำหรับ search (Lucene)/SQL/dashboard/alert/trace waterfall เหนือ store เดียวกัน — บท​เดียว​ที่​ผูก​กับ sink โดย​ตั้งใจArchitecture คุณ​คลิก log บรรทัด “Payment … declined” แล้ว pivot ไป​ที่ trace ของออเดอร์​นั้น​ได้​ทันที เพราะ​ทั้ง2 share TraceId เดียวกัน — และ​เนื่องจาก​บท3 ทำให้ trace เดิน​ข้าม RabbitMQ ต่อ​เนื่อง log ที่​เกิด​ใน Payment จึง​ผูก​กลับ​ไป​ที่​ออเดอร์​ต้นทางใน Ordering ได้ ข้าม​บัส นี่​คือ​คำ​ตอบ​ของ​คำถาม​ตอน​ตี​สาม​ที่​เปิด​บท​นี้

นอกจาก​นี้ {PaymentId} และ {OrderId} ใน template ยัง​กลาย​เป็น attribute ที่ query ได้ (ถูก​เก็บ​ให้​อัตโนมัติ​จาก log state — ไม่​ได้​ขึ้น​กับ IncludeFormattedMessage/IncludeScopes ตาม​ที่ callout ข้าง​บน​ย้ำ) — คุณ​จึง​กรอง log ทั้งหมด​ของออเดอร์​หนึ่ง​ด้วย OrderId:"4821" ได้ นี่​คือ​เหตุผล​จริง​ที่​ต้อง​เขียน​แบบ structured ({PaymentId}) แทน string interpolation ดิบ ($"Payment {paymentId} declined" — version ดิบ​ที่​ทำให้​ค่า​จม​หาย​ไป​ใน Body ค้น​แยก​ไม่​ได้)

log กับ trace correlate กัน​แล้ว แต่​ยัง​มี​อีก​ก้าว: แทนที่​จะ​กระจาย​ข้อเท็จจริง​เชิง domain ไป​ใน​หลาย log บรรทัด ให้ เลื่อน มัน​ขึ้น​ไป​เป็น attribute บน span เอง จน span หนึ่ง​พก​ทั้ง​เรื่อง​ไว้​ใน​ตัว — นี่​คือ​แนวคิด wide event ที่​บท1 พูด​ถึง (span ที่​มี attribute กว้างๆ) ทำให้​จับ​ต้อง​ได้:

// enrich span ที่ active อยู่ให้กลายเป็น wide event ที่เล่าเรื่องได้ครบ
Activity.Current?.SetTag("payment.outcome", "declined");
Activity.Current?.SetTag("payment.decline_reason", "insufficient_funds");
Activity.Current?.SetTag("order.id", orderId);

สังเกต Activity.Current?. — null-conditional เพราะ​ถ้า​ไม่มี listener หรือ​ไม่มี span active Activity.Current เป็น null (หลักการ​เดียว​กับ activity?. ตอน StartActivity คืน null ในบท3) เมื่อ​ทำ​แบบ​นี้ ทีม on-call ดู span ใบ​เดียว ก็​เห็น “ออเดอร์​ไหน จ่าย​เงิน​ผล​อะไร เพราะ​อะไร” โดย​ไม่​ต้อง​ไล่ log หลาย​บรรทัด​มา​ต่อ​กัน

นี่​คือ Observability 2.0Observability 2.0แนวคิด 'แหล่ง​ความ​จริง​เดียว' เก็บ​เป็น wide structured event แล้ว​อนุมาน metrics/logs/traces ตอน​อ่าน (frame ที่ vendor คิด อยู่​ใน​ดีเบต​ที่​ยัง​ไม่​จบ)Process / wide-event ที่​บท1 วาง​ไว้​เป็น ดีเบต​ที่​ยัง​ไม่​จบ — เก็บ​ข้อเท็จจริง​เป็น event กว้าง​ชุด​เดียว​แล้ว​อนุมาน metric/log/trace ตอน​อ่าน (S46) เรา​ทำ กลไก นี้​ให้​เห็น​จริง​ใน​บท​นี้ แต่​ย้ำ​จุดยืน​เดิม: เรา​ไม่​ประกาศ​ว่า​ฝ่าย wide-event ชนะ​กรอบ​สาม​เสา — เรา​แค่​ให้​คุณ​เห็น​ว่า​มัน​หน้าตา​เป็น​ยังไง​ใน code

อย่า​ใส่ PII ดิบ​ลง attribute (สะพาน​ไป #18)

order.id, payment.outcome, customer_id เป็น attribute ที่​ยอดเยี่ยม​สำหรับ debug — trace รับ cardinality สูง​ได้​สบาย แต่ ห้าม​ใส่ PII ดิบ (อีเมล, เบอร์​โทร, ที่​อยู่, เลข​บัตร/PAN) ลง attribute ของ span หรือ log เด็ดขาด attribute เหล่า​นี้​ไหล​ไป​ถึง sink และ​ค้นหา​ได้ ทำให้​มัน​เป็น​ช่อง​รั่ว​ข้อมูล​ได้​ทันที (เชื่อม​กับ​บทเรียน data-leakage ของ #18) ใช้ id ที่​อ้างอิง​กลับ​ได้ ไม่ใช่ ตัว​ข้อมูล​ส่วน​บุคคล เอง

บางที span ที่อยู่ลึกๆ ใน​บริบท Payment อยาก​รู้ order.id ที่​ตั้ง​ไว้​ตอน​ต้นทางใน Ordering — แต่​มัน​อยู่​คนละ process หลัง​บัส วิธี​หิ้ว​ค่า​แบบ​นี้​ข้าม​ขอบเขต​คือ BaggageBaggageคู่ key-value ที่​ส่ง​ไป​พร้อม context ข้าม service; ต้อง​อ่าน​แล้ว​คัด​ลอก​เป็น attribute เอง และ​ห้าม​ใส่​ข้อมูล​อ่อนไหว (ไม่มี​การ​ตรวจ integrity)Architecture: คู่ key-value ที่​เดินทาง​ไป​พร้อม trace context ผ่าน header (W3C baggage บน HTTP, และ​ฝัง​ใน AMQP message properties ของ RabbitMQ)

// ต้นทาง (Ordering) — แนบ order.id เข้า baggage เพื่อให้เดินทางข้าม context
Activity.Current?.SetBaggage("order.id", order.Id.Value.ToString());
baggage จะ​ข้าม process ไป​จริง​ไหม — ต้อง​รัน​จริง​ถึง​จะ​ยืนยัน

ว่า SetBaggage/GetBaggageItem ผ่าน Activity.Current จะ propagate ข้าม HTTP และ โดย​เฉพาะ​ข้าม Wolverine/RabbitMQ ได้​จริง​หรือ​ไม่ ขึ้น​กับ propagator ที่​ตั้ง​ไว้ + version ของ transport ใน stack ของ​คุณ — เอกสาร​รับประกัน​แค่ กลไก W3C baggage ไม่​ได้​รับประกัน​ว่า hop ข้าม broker ใน​ระบบ​ของ​คุณ​จะ​พา​มัน​ไป​ให้​อัตโนมัติ ตรง​นี้​เป็น build-time-verify: เปิด trace ดู​จริง​ว่า baggage โผล่​ที่​ปลายทาง​ไหม อย่า​ถือว่า​มัน​ข้าม​ไป​แน่​โดย​ยัง​ไม่​ทดสอบ

แต่​มี​ความ​จริง​สอง​ข้อ​ที่ ต้อง​แม่น เกี่ยว​กับ baggage:

1. Baggage ไม่​กลาย​เป็น attribute ให้​เอง — คุณ​ต้อง​อ่าน​แล้ว​แปะ​เอง. ต่าง​จาก correlation ของ log ที่​ประทับ TraceId ให้​ฟรี baggage แค่ เดินทาง​ไป​ถึง ปลายทาง มัน​ไม่​ถูก​เขียน​ลง span ปลายทาง​โดย​อัตโนมัติ คุณ​ต้อง อ่าน​มัน​แล้ว SetTag เอง​อย่าง​ชัด​แจ้ง:

// ปลายทาง (Payment consumer) — baggage มาถึงแล้ว แต่ต้องอ่าน + แปะเป็น attribute เอง
var orderId = Activity.Current?.GetBaggageItem("order.id");
Activity.Current?.SetTag("order.id", orderId); // ← งานที่ต้องเขียนเอง ไม่ฟรี

2. Baggage ไม่มี​การ​ตรวจ integrity และ​รั่ว​ออก​นอก​ได้. เอกสาร​ทางการ​เตือนตรงๆ ว่า baggage เดิน​ทางใน HTTP/AMQP header โดย ไม่มี​การ​ตรวจสอบ​ความ​ถูกต้อง (no integrity check) และ​อาจ รั่ว​ไป​ยัง third party ที่​รับ request ต่อ​ได้ (S6) กฎ​เหล็ก​จึง​เป็น:

ห้าม​ใส่​ข้อมูล​อ่อนไหว​ลง baggage — เด็ดขาด

baggage คือ header ที่​วิ่ง​ไป​กับ​ทุก hop ปลายทาง (รวม​ถึง​บริการ​ภายนอก​ที่​คุณ​เรียก​ต่อ) มอง​เห็น​ได้ และ​ไม่มี​อะไร​รับประกัน​ว่า​มัน​ไม่​ถูก​แก้​ระหว่าง​ทาง อย่า​ใส่ token, ข้อมูล​ส่วน​บุคคล, หรือ​ความ​ลับใดๆ ลง baggage ใส่​ได้​เฉพาะ id ที่​ไม่​อ่อนไหว​อย่าง order.id เพื่อ ความ​สะดวก​ใน​การ debug เท่านั้น

เส้น​แบ่ง​ที่​ต้อง​แม่น: อะไร​ฟรี อะไร​ต้อง​เขียน​เอง

หัวข้อ​ที่​มีชื่อ​ว่า “เส้น​แบ่ง​ที่​ต้อง​แม่น: อะไร​ฟรี อะไร​ต้อง​เขียน​เอง”

บท​นี้​มี​สอง​กลไก​ที่​หน้าตา​คล้าย​กัน​แต่ ต้นทุน​ต่าง​กัน​คนละ​ขั้ว — สับสน​เมื่อไหร่​จะ instrument ผิด:

กลไกเกิด​ขึ้น​ยังไงต้นทุน
ประทับ TraceId/SpanId ลง log (correlation)อัตโนมัติ — SDK ทำให้​จาก Activity.Currentฟรี (แค่​เปิด provider)
{PaymentId} → attribute ที่ query ได้อัตโนมัติ​จาก log stateฟรี (ไม่​ขึ้น​กับ IncludeFormattedMessage/IncludeScopes)
enrich span เป็น wide event (SetTag)เขียน​เอง ต่อ spanงาน​ที่​ต้อง​ลงมือ
promote baggage → attribute ปลายทางเขียน​เอง — อ่าน GetBaggageItem แล้ว SetTagงาน​ที่​ต้อง​ลงมือ

ย้ำ honesty precision ของ​บท​นี้: การ​ผูก log เข้า​กับ trace เป็น​ของ​ฟรี แต่​การ​เลื่อน​ข้อเท็จจริง​เชิง domain ขึ้น span — และ​การ​หิ้ว baggage มา​แปะ​เป็น attribute — เป็น code ที่​คุณ​ต้อง​เขียน​เอง นี่​คือ​หน้าตา​จริง​ของ​เส้น​ความ​ซื่อสัตย์​ข้อ (D) ที่​ว่า คุณ debug สิ่ง​ที่​ไม่​ได้ instrument ไม่​ได้: auto-correlation เป็น พื้น ส่วน attribute ที่​มี​ความหมาย​เชิง domain เป็น เพดาน ที่​ต้อง​ปีน​เอง

แผนภาพ: จาก log บรรทัด​เดียว สู่ trace ทั้ง​เส้น​ข้าม​บัส

หัวข้อ​ที่​มีชื่อ​ว่า “แผนภาพ: จาก log บรรทัด​เดียว สู่ trace ทั้ง​เส้น​ข้าม​บัส”
flowchart TD
  subgraph PAY["บริบท Payment (คนละ process หลังบัส)"]
    L["_logger.LogInformation<br/>Payment {PaymentId} declined for order {OrderId}"]
    SDK["OTel SDK: อ่าน Activity.Current"]
    LR["LogRecord<br/>Body + Attributes<br/>TraceId + SpanId ← ประทับอัตโนมัติ"]
    L --> SDK
    SDK -->|"ประทับ TraceId/SpanId ให้ฟรี"| LR
  end

  LR -->|"ส่งออกผ่าน OTLP"| HX["HyperDX: คลิก log → pivot ไป trace"]

  subgraph TR["trace เดียวกัน (TraceId ตรงกัน)"]
    O["Ordering: place-order"]
    P["Payment: charge (HttpClient)"]
    O -->|"traceparent ข้าม RabbitMQ"| P
  end

  HX -->|"match TraceId เดียวกัน"| TR

  classDef free fill:#0e7490,stroke:#155e75,color:#f0fdff;
  classDef store fill:#1e293b,stroke:#0f172a,color:#94a3b8;
  class LR,SDK free;
  class O,P store;

คำ​บรรยาย​ภาพ: log บรรทัด​หนึ่ง​ใน​บริบท Payment ถูก OTel SDK อ่าน Activity.Current แล้ว​ประทับ TraceId/SpanId ของ span ปัจจุบัน​ลง LogRecord โดย​อัตโนมัติ (ส่วน​สี​ฟ้า = ฟรี) เมื่อ LogRecord ถึง HyperDX การ​มี TraceId ทำให้​คลิก​เดียว pivot ไป​ที่ trace ทั้ง​เส้น​ของออเดอร์​นั้น​ได้ — ซึ่ง​เดิน​ข้าม RabbitMQ จาก Ordering มา​ถึง Payment ต่อ​เนื่อง​ตั้งแต่​บท3 log ก้อน​โดดเดี่ยว​จึง​กลาย​เป็น​จุด​เข้า​สู่​เรื่อง​เล่า​ทั้ง​เส้น

บท​นี้​ทำให้ log เลิก​ลอย​เดี่ยว: log ใน OTel คือ LogRecord ที่​มี TraceId/SpanId/TraceFlags เป็น field ใน​ตัว; Logs Bridge เป็น​ของ​นัก​เขียน library ส่วน code app ใช้ ILogger เหมือน​เดิม แล้ว OTel bridge ให้; correlation เกิด​อัตโนมัติ — SDK ประทับ trace id จาก Activity.Current ให้​ฟรี ทำให้ HyperDX pivot จาก log ไป trace ได้ ข้าม​บัส; structured logging ({PaymentId}) ทำให้​ค่า​กลาย​เป็น attribute ที่ query ได้ โดย​อัตโนมัติ​จาก log state (ไม่​ขึ้น​กับ IncludeFormattedMessage/IncludeScopes — สอง​สวิตช์​นั้น​แค่​เพิ่ม Body ที่​ประกอบ​เสร็จ กับ​คู่ scope); enrich span ด้วย SetTag ให้​เป็น wide event ที่​เล่า​เรื่อง​ครบ; และ baggage หิ้ว order.id ข้าม context ได้ — แต่​ต้อง อ่าน​แล้ว​แปะ​เป็น attribute เอง (ไม่​ฟรี) และ ห้าม​ใส่​ข้อมูล​อ่อนไหว เพราะ​มัน​ไม่มี​การ​ตรวจ integrity และ​รั่ว​ออก​นอก​ได้ เส้น​แบ่ง ฟรี vs ต้อง​เขียน​เอง คือ​สิ่ง​ที่​ต้อง​แม่น​ที่สุด​จาก​บท​นี้

ตอน​นี้​คุณ​มี​ครบ​สาม​สัญญาณ​ที่​สัมพันธ์​กัน​แล้ว — trace (บท3), metric (บท4), log (บท​นี้) บท​หน้า​เรา​จะ​เลิก​ดู​ที​ละ​สัญญาณ แล้ว​เข้า HyperDX เพื่อ ตั้ง​คำถาม​ที่​ไม่​ได้​เตรียม​ไว้​ล่วงหน้า (unknown-unknowns) ด้วย Lucene search, ClickHouse SQL และ trace waterfall — บท​เดียว​ของ​คอร์ส​ที่​ผูก​กับ sink โดย​ตั้งใจ


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้

บท​นี้​อิง​ต้นทาง​ที่​ลง​วัน​ที่​กำกับ อ่าน​ต่อ​ได้​โดยตรง:

  • OpenTelemetry — Logs (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — LogRecord มี field TraceId/SpanId/TraceFlags; correlation กับ trace เกิด​อัตโนมัติ​เมื่อ SDK active; Logs Bridge API มี​ไว้​ให้​นัก​เขียน logging library ไม่ใช่ code app
  • OpenTelemetry — Baggage (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — baggage หิ้ว​คู่ key-value ข้าม service; คำ​เตือน​ความ​ปลอดภัย: ไม่มี​การ​ตรวจ integrity, รั่ว​ไป third party ได้ → ห้าม​ใส่​ข้อมูล​อ่อนไหว; ต้อง​อ่าน​แล้ว​คัด​ลอก​เป็น attribute เอง
  • ClickHouse blog — Logging, Metrics, and Distributed Tracing in .NET with OpenTelemetry and ClickStack (2026-06-03) — grounding ฝั่ง .NET: builder.Logging.AddOpenTelemetry(IncludeFormattedMessage/IncludeScopes) + ILogger structured logging + Activity.Current?.SetTag
  • Honeycomb — “Time to version observability? Signs point to yes” (2024-12-02) — wide events: เก็บ​ข้อเท็จจริง​เป็น structured event กว้าง​ชุด​เดียว แล้ว​อนุมาน metric/log/trace ตอน​อ่าน (แนวคิด Obs 2.0 — ดีเบต​ที่​ยัง​ไม่​จบ)

เช็กความเข้าใจ — บทที่ 5

ข้อ 1 / 3

อะไรทำให้ log บรรทัดหนึ่งกระโดดไปที่ trace ของมันได้ใน HyperDX (log↔trace correlation)?