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

Distributed tracing ข้าม message bus — ตามออเดอร์​เดียว​เป็น​เทรซ​เดียว

บท​ที่​แล้ว​คุณ​รัน ClickStack แล้ว​ได้ trace แรก จาก endpoint place-order ของ Ordering — server span หนึ่ง​อัน​กับ EF Core DB span ลูก​ของ​มัน ทั้งหมด​นั้น​เกิด​ใน process เดียว แต่ platform Order จริง​ของ #8 ไม่​ได้​อยู่ process เดียว ออเดอร์​หนึ่ง​ใบ confirm ที่ Ordering แล้ว​วิ่ง​ข้าม transactional outbox ผ่าน RabbitMQ ไป​โผล่​ที่ Kitchen (เปิด ticket) และ Delivery (จอง assignment) — และ​หลัง #8 แยก Delivery ออก​เป็น service เดี่ยว การ​เดินทาง​นั้น​ก็​ข้าม 2 process กับ1 broker จริงๆ

นี่​คือ​บท​ที่​เป็น​หัวใจ​ของ​ทั้ง​คอร์ส: จบ​บท​นี้ ออเดอร์ #4821 ที่​ตี​สาม​ใน​บท1 จะ​ไม่ใช่​ความ​ลึกลับ​สี่​ก้อน​ที่​ไม่​โยง​กัน​อีก​ต่อ​ไป — มัน​จะ​เป็น เส้นทาง​เดียว ที่​ไล่​ดู​ได้​ตั้งแต่ confirm จนถึง delivery assignment ใน​หน้า waterfall เดียว

📦 code ตัวอย่าง

code ใน​บท​นี้ instrument platform Order ตัว​เดิม​ที่ #8 สร้าง (Ordering/Kitchen/Delivery/Payment บน WolverineFx outbox + RabbitMQ) — repo kaen-food-ordering (กำลัง​จัด​ทำ) เรา​ไม่​แตะ business logic ของ handler เลย: ConfirmOrderHandler กับ WhenOrderConfirmed เหมือนกันเป๊ะ​ทั้ง​ก่อน​และ​หลัง​แยก Delivery เรา​แค่​เพิ่ม ชั้น​สังเกตการณ์ คร่อม​มัน — manual span หนึ่ง​อัน​บน​ฝั่ง Ordering แล้ว​ปล่อย​ให้ Wolverine ส่ง​ต่อ trace context ข้าม broker ให้​เอง

TraceTraceเส้นทาง​ของ1 request ผ่าน​ระบบ ประกอบ​ด้วยหนึ่งส​แปน​หรือ​มากกว่าArchitecture คือ​เส้นทาง​ของ หนึ่ง request/หนึ่ง​หน่วย​งาน​ที่​ไหล​ผ่าน​ระบบ ประกอบ​ด้วย SpanSpanหนึ่ง​หน่วย​งาน/operation ใน​เทรซ มี name/timestamps/attributes/status/kind และ span contextArchitecture หนึ่ง​อัน​หรือ​มากกว่า​เรียง​เป็นต้นไม้​ตาม​ความ​สัมพันธ์ parent-child 1 span คือ1 operation ที่​มี​จุด​เริ่ม-จบ — เช่น “รับ HTTP request”, “query DB”, “จัดการ integration event หนึ่ง​ใบ”

จุด​สำคัญ​สำหรับ​คน​เขียน .NET: OpenTelemetry ไม่​ได้​บังคับ​ให้​คุณ​เรียน API ใหม่ มัน​แม็ปกับ​ของ​ที่​มี​ใน framework อยู่​แล้ว​ใน System.Diagnostics (S17, S3):

  • ActivitySourceActivitySourceAPI ใน System.Diagnostics ของ .NET ที่​ทำ​หน้าที่​เป็น Tracer สร้าง Activity (=สแปน); ต้อง AddSource ชื่อ​นี้​ไม่​งั้น telemetry ถูก​ทิ้ง​เงียบArchitectureTracer — โรงงาน​ที่​สร้าง span
  • ActivitySpan — ตัว span จริงๆ (ชื่อ class ใน .NET คือ Activity ด้วย​เหตุผล​ทาง​ประวัติศาสตร์ แต่​คิด​ว่า​มัน​คือ span ได้​เลย)
  • ActivityKindSpan Kind

1 span พก field เหล่า​นี้​ติดตัว (S3): name, parent span id, timestamps (เริ่ม/จบ), span context (trace id + span id + trace flags — ตัว​ที่​ทำให้ span รู้​ว่า​ตัวเอง​อยู่​ใน​เทรซ​ไหน), attributes (key-value เชิง domain เช่น order.customer_id), events (จุด​เวลา​ที่​มี​ความหมาย​ภายใน span), links (โยง​ไป span อื่น​ที่​ไม่ใช่ parent ตรงๆ — จำ​คำ​นี้​ไว้ เดี๋ยว​มัน​เป็น​ตัวเอก​ตอน​ท้าย​บท), status, และ kind

2 field สุดท้าย​มี​ค่าที่​ตายตัว จำ​ไว้​ให้​แม่น (S3):

  • Status = Unset (ค่า​เริ่มต้น) · Ok (ยืนยัน​ว่า​สำเร็จ​อย่าง​ชัด​แจ้ง) · Error (พัง) — โดย​ปกติ​ปล่อย​เป็น Unset แล้ว​ตั้ง Error เฉพาะ​ตอน fail
  • Kind = Internal (งาน​ใน process) · Server (รับ request เข้า) · Client (ยิง request ออก เช่น HttpClient ไป AcmePay) · Producer (ส่ง message เข้า queue) · Consumer (รับ message จาก queue) — 3 kind หลัง​คือ​กุญแจ​ของ​การ trace ข้าม RabbitMQ

คำถาม​หัวใจ: Ordering กับ Delivery เป็น​คนละ process (คนละ​เครื่อง​ได้​ด้วย) แล้ว span ที่ Delivery รู้​ได้​ยังไง ว่า​ตัวเอง​เป็น​ลูก​ของ​เทรซ​ที่​เริ่ม​จาก Ordering? คำ​ตอบ​คือ Context PropagationContext Propagationการ​ส่ง trace context (W3C traceparent) ข้าม​ขอบเขต process/broker เช่น​ฝัง​ใน AMQP message properties ของ RabbitMQArchitecture — การ​ส่ง span context (trace id + span id ของ parent) ข้าม​ขอบเขต process โดย​ฝาก​ไป​กับ​ตัว request/message เอง (S7)

มาตรฐาน​ที่ OpenTelemetry ใช้​เป็น propagator เริ่มต้น​คือ W3C Trace Context (บวก Baggage) — ซึ่ง​นิยาม HTTP header ชื่อ traceparent หน้าตา​แบบ​นี้ (S8):

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
version trace-id (32 hex) parent span-id trace-flags
(16 hex) (01 = sampled)

รูปแบบ​คือ 00-<trace-id 32 hex>-<span-id 16 hex>-01 เมื่อ service A ยิง HTTP ไป service B, SDK จะ inject traceparent ลงใน header อัตโนมัติ; ฝั่ง B จะ extract มัน​ออก​มา​แล้ว​สร้าง span ใหม่​ที่​มี parent เป็น span id ตัว​นั้น — 2 span จึง​อยู่ trace id เดียวกัน ต่อ​กัน​เป็น​เส้น​เดียว

แล้ว​ข้าม RabbitMQ ล่ะ? หลักการ​เดียวกันเป๊ะ แต่​แทนที่​จะ​เป็น HTTP header, traceparent/tracestate ถูก​ฝัง​ลง​ใน AMQP message properties ของ message ตอน publish แล้ว​ถูก​ดึง​กลับ​ออก​มา​ตอน consume (S7, S13) message bus จึง​กลาย​เป็น​แค่ “ท่อ” ที่​พา trace context ข้าม​ไป​ด้วย — producer, broker, consumer อยู่​ใน​เทรซ​เดียวกัน​ได้

Auto-instrumentation จาก​บท2 ให้ span ของ HTTP กับ DB มา​ฟรี — แต่​มัน​ไม่รู้จัก​คำ​ว่า “order” นี่​คือ honesty spine เส้น (D): auto-instrumentation เป็น​พื้น ไม่ใช่​เพดาน span ที่​มี​ความหมาย​เชิง domain คุณ​ต้อง​เขียน​เอง

เรา​ใช้ DiagnosticConfig static holder ตัว​เดียว​กับ​ที่​ปู​ไว้ (ActivitySource เป็น static อายุ​ยืน ชื่อ​ต้อง​ตรง​กับ AddSource เป๊ะ ไม่​งั้น telemetry ถูก​ทิ้ง​เงียบ):

using System.Diagnostics;
using System.Diagnostics.Metrics;
public static partial class DiagnosticConfig // partial — บท4 จะเติม instrument ของ metric เข้า class เดียวกันนี้
{
public const string ServiceName = "ordering-service";
public const string ActivitySourceName = ServiceName;
public const string MeterName = ServiceName;
// long-lived statics — สร้างครั้งเดียว ใช้ทั้ง app
public static readonly ActivitySource ActivitySource = new(ActivitySourceName);
public static readonly Meter Meter = new(MeterName);
}

แล้ว​เปิด span รอบ business action ที่​มี​ความหมาย — ตอน​รับคำ​สั่ง place order:

using var activity = DiagnosticConfig.ActivitySource.StartActivity("place-order");
activity?.SetTag("order.customer_id", req.CustomerId);
activity?.SetTag("order.item_count", req.Items.Count);
// ... business logic วางออเดอร์ตามปกติ ...

สังเกต​สอง​อย่าง​ที่​พลาด​ง่าย:

  1. activity?. เป็น null-conditional เสมอStartActivity คืน null ได้​เมื่อ ไม่มี listener (เช่น​ตอน​รัน unit test ที่​ไม่​ได้​ตั้ง OTel หรือ​ตอน span นี้​ถูก sample ทิ้ง) code ที่​เขียน activity.SetTag(...) ตรงๆ จะ​พัง​ด้วย NullReferenceException ใน​บาง environment activity?.SetTag(...) จึง​เป็น idiom ที่​ต้อง รักษา ไว้​ทุก​ที่
  2. tag เป็น customer_id กับ item_count — ไม่ใช่ PII เรา​ใส่ id กับ​ตัว​นับ (debug ได้​ละเอียด, trace รับ cardinality สูง​ได้​สบาย) แต่ ห้าม​ใส่ email/เบอร์​โทร/ที่​อยู่/เลข​บัตร ลง attribute เด็ดขาด — span/log ถูก​ส่ง​ออก​ไป​นอก​ระบบ​ไป​ที่ sink อัน​นี้​คือ​สะพาน​ตรง​ไป​หา​บท​เรื่อง data-leakage ของ #18: id ใส่​ได้​เพื่อ debuggability, ข้อมูล​อ่อนไหว​ดิบ​ใส่​ไม่​ได้
ชื่อ​ต้อง​ตรง ไม่​งั้น​เงียบ

AddSource("ordering-service") ใน​การ wiring ต้อง​ตรง​กับ ActivitySourceName เป๊ะ และ AddSource("Wolverine") ต้อง​มี​เพื่อ​เก็บ span ของ Wolverine ถ้า​ชื่อ​ไม่​ตรง OTel จะ ทิ้ง span นั้น​เงียบๆ ไม่มี error ไม่มี warning — คุณ​จะ​นั่ง​งง​ว่า​ทำไม trace หาย​ไป​ครึ่ง​เส้น นี่​คือ bug อันดับ​หนึ่ง​ของ​มือใหม่ OTel

ข่าวดี​ที่สุด​ของ​บท​นี้: การ trace ข้าม RabbitMQ คุณ​ไม่​ต้อง​เขียน inject/extract เอง เพราะ platform ของ #8 ใช้ WolverineFx ซึ่ง​มี OTel ใน​ตัว สอง​อย่าง​ที่​ต้อง​รู้ (S35, S36):

  • AddSource("Wolverine") ใน​การ wiring ฝั่ง tracing → เก็บ span ของ Wolverine เอง (span ตอน handle/send/receive message)
  • Wolverine ส่ง​ต่อ correlation/parent context ผ่าน Envelope ของ​มัน​เอง​ข้าม transport — ตอน publish OrderConfirmedIntegrationEvent ผ่าน outbox, Wolverine เขียน trace context ลงใน message properties; ตอน consumer อีก process รับ message มัน extract context นั้น​กลับ​มา​แล้ว สร้าง span ที่​ต่อ​เทรซ​เดิม โดย​ไม่​ต้อง​ตั้ง​ค่า​อะไร​เพิ่ม
Wolverine 1.7+: internal-message OTel ปิด​โดย​ค่า​เริ่มต้น

ตั้งแต่ Wolverine 1.7 การ​ปล่อย OTel span สำหรับ message ที่​วิ่ง ภายใน process เดียวกัน (local queue) ถูก ปิด​เป็น​ค่า​เริ่มต้น เพื่อ​ลด noise — ส่วน​ที่​ข้าม transport จริง (เช่น RabbitMQ) ยัง​ทำงาน เป้าหมาย​ของ​บท​นี้​คือ hop ข้าม broker จึง​ไม่​กระทบ แต่​ถ้า​คุณ​อยาก trace hop ใน process ด้วย ต้อง​เปิด flag เพิ่ม (S35)

handler นี้​คือ root ของ​เทรซ​ฝั่ง business — และ​มัน​คือ code #8 เป๊ะ ไม่​แก้​อะไร​เพื่อ observability เลย:

public class ConfirmOrderHandler
{
public async Task Handle(ConfirmOrder cmd, IDbContextOutbox<OrderingDbContext> outbox)
{
var order = await outbox.DbContext.Orders.FindAsync(cmd.OrderId);
order.Confirm();
// event ออกทาง outbox — Wolverine จะแนบ trace context ลง message properties ให้เอง
await outbox.PublishAsync(
new OrderConfirmedIntegrationEvent(order.Id.Value, DateTimeOffset.UtcNow));
// state + event atomic; flush ออก broker หลัง commit สำเร็จ
await outbox.SaveChangesAndFlushMessagesAsync();
}
}

นี่​คือ​จุด​ที่​เทรซ กลับ​มา​ต่อ ใน​อีก process — และ​ย้ำ​อีก​ครั้ง: code นี้ เหมือน​เดิม​ทุก​ตัว​อักษร​ทั้ง​ก่อน​และ​หลัง​แยก Delivery สิ่ง​เดียว​ที่​เปลี่ยน​คือ​ตอน​นี้​เทรซ​พาด​ผ่าน2 process กับ broker หนึ่ง​ตัว:

public class WhenOrderConfirmed
{
public async Task Handle(OrderConfirmedIntegrationEvent evt, DeliveryDbContext db)
{
// idempotent guard ของ #17 — เรา reuse เป็น "บริบทของเทรซ" ไม่ได้สอน resilience ใหม่
if (await db.Assignments.AnyAsync(a => a.OrderId == evt.OrderId)) return;
db.Assignments.Add(Assignment.Create(evt.OrderId));
await db.SaveChangesAsync();
}
}

idempotent guard บรรทัด​แรก​เป็น​ของ #17 (resilience) เรา​ไม่​ได้ derive มัน​ใหม่ — เรา​แค่ สังเกต มัน: ใน​หน้า waterfall คุณ​จะ​เห็น span ของ WhenOrderConfirmed ที่​ถ้า guard คืน​เร็ว (duplicate) span จะ​สั้น​จู๋ ต่าง​จาก​ตัว​ที่​ทำงาน​จริง — observability ทำให้ behavior ของ guard มอง​เห็น​ได้

flowchart TB
  subgraph ORD["process: ordering-service"]
    A["span: place-order<br/>(Internal) — manual<br/>order.customer_id, order.item_count"]
    B["span: ConfirmOrderHandler<br/>(Consumer/Internal) — Wolverine"]
    C["span: publish OrderConfirmed<br/>(Producer) — Wolverine"]
    A --> B --> C
  end

  subgraph BUS["RabbitMQ (broker)"]
    Q["message properties พก<br/>traceparent + tracestate"]
  end

  subgraph DEL["process: delivery-service (แยกออกมา)"]
    E["span: receive OrderConfirmed<br/>(Consumer) — Wolverine"]
    F["span: WhenOrderConfirmed<br/>(Internal)"]
    G["span: EF Core INSERT Assignment<br/>(Client) — auto"]
    E --> F --> G
  end

  C -->|"inject context"| Q
  Q -->|"extract context → trace id เดิม"| E

  classDef ord fill:#0e7490,stroke:#155e75,color:#f0fdff;
  classDef bus fill:#a21caf,stroke:#701a75,color:#fdf4ff;
  classDef del fill:#0f766e,stroke:#134e4a,color:#f0fdfa;
  class A,B,C ord;
  class Q bus;
  class E,F,G del;

คำ​บรรยาย​ภาพ: ออเดอร์​หนึ่ง​ใบ​เดิน​จาก manual span place-order ที่ ordering-service ผ่าน handler ที่ publish event ออก outbox โดย Wolverine แนบ traceparent ลง message properties ของ RabbitMQ ฝั่ง delivery-service (คนละ process หลัง​แยก service) ดึง context กลับ​ออก​มา​แล้ว​สร้าง span ต่อ​ใน​เทรซ trace id เดียวกัน — ผล​คือ waterfall เส้น​เดียว​ที่​ครอบ​ทั้ง2 process กับ broker hop ตรง​กลาง

หัวข้อ​ที่​มีชื่อ​ว่า “⚠️ ความ​จริง​ที่​ต้อง​รัน​จริง​ถึง​จะ​ฟัน​ธง: parent-child หรือ span link?”

นี่​คือ​จุด​ที่​คอร์ส​ต้อง​ซื่อสัตย์​ที่สุด เรา​รัน Docker/ClickStack ใน​บท​นี้​ไม่​ได้ ดังนั้น​เรา​จะ​อธิบาย กลไก ให้​แม่น แต่​จะ ไม่ อ้าง​รายละเอียด​ของ screenshot ที่​ยืนยัน​เอง​ไม่​ได้

สิ่ง​ที่​ยืนยัน​ได้​จาก​เอกสาร:

  • กลไก​ทำงาน​แน่นอน: context ถูก​ส่ง​ข้าม hop จริง (Wolverine Envelope พา parent context ไป​กับ message) → consumer ต่อ​เทรซ​เดิม ได้ ไม่​ใช่​เทรซ​ใหม่​ที่​ขาด​จาก​กัน (S36, S7)

สิ่ง​ที่​ต้อง​ระวัง​การ​ฟัน​ธง — 2 span ฝั่ง consumer (Kitchen + Delivery) จะ​เกาะ​กับ producer แบบ​ไหน? มี​สอง​แบบ​ที่​ต่าง​กัน:

  • แบบ parent-child — consumer span เป็น ลูก​โดยตรง ของ producer span นี่​คือ model ที่ Envelope ของ Wolverine พา parent context ไปให้ตรงๆ → ใน waterfall จะ​เห็น consumer ห้อย​ใต้ producer เหมือน​กิ่ง​เดียวกัน (S36)
  • แบบ span link — consumer span เป็น root ของ​เทรซ​ตัวเอง แต่​แนบ link กลับ​ไป​หา producer span นี่​คือ ค่า​เริ่มต้น​ของ OTel messaging semantic conventions เพราะ message หนึ่ง​ใบ​อาจ​ถูก fan-out/batch ไป​หลาย consumer ความ​สัมพันธ์​แบบ link จึง​สื่อ​ความ async ได้​ตรง​กว่า (S13)
อย่า​เดา — ให้​รัน​แล้ว​ดู

เอกสาร​ของ Wolverine ชี้​ว่า Envelope พา parent context ข้าม transport (โน้ม​ไป​ทาง parent-child) ส่วน OTel messaging convention ตั้ง span link เป็น​ค่า​เริ่มต้น สอง​อย่าง​นี้​ให้ waterfall หน้าตา​ต่าง​กัน และ​ตัว​ไหน​ชนะ​ขึ้น​กับ version Wolverine + การ​ตั้ง​ค่า​จริง คอร์ส​นี้​จึง ไม่ ยืนยัน​ว่า screenshot ของ​คุณ​จะ​ออก​มา​แบบ​ไหน​เป๊ะ ตอน​คุณ​รัน​จริง (บท6 จะ​พา​สำรวจ HyperDX) ให้​เปิด trace ดู​เอง​ว่า consumer span ห้อย​ใต้ producer (parent-child) หรือ แยก​เทรซ + มี link — ทั้ง​สอง​แบบ​คือ “ถูก” แค่​เล่า​เรื่อง​ต่าง​มุม และ​ทั้ง​คู่​ก็​ยัง trace ข้าม broker ได้​ตาม​เป้า (S13, S36)

บท​นี้​เปลี่ยน​ออเดอร์​ที่​เคย​เป็น “ความ​ลึกลับ​สี่​ก้อน” ให้​เป็น เทรซ​เดียว: Distributed TracingDistributed Tracingการ​ตาม​หนึ่ง​เทรซ​ข้าม​หลาย service/process ผ่าน​การ​ส่ง​ต่อ context (ชัด​ที่สุด​หลัง​แยก Delivery ออก​เป็น service เดี่ยว)Process คือ​การ​ตาม​หนึ่ง​เทรซ​ข้าม​หลาย process ด้วย​การ​ส่ง​ต่อ context (context propagation) — trace context (traceparent แบบ W3C 00-<trace-id>-<span-id>-01) ถูก inject ลง AMQP message properties ตอน publish แล้ว extract กลับ​ตอน consume; span บน .NET คือ Activity ที่​สร้าง​จาก ActivitySource (≈ Tracer) พก status/kind/attributes/links; เรา​เขียน manual span place-order ด้วย activity?. null-conditional และ tag เป็น id/count ไม่ใช่ PII; และ Wolverine ส่ง​ต่อ context ข้าม RabbitMQ ให้​เอง​ผ่าน Envelope — handler ConfirmOrderHandler/WhenOrderConfirmed เหมือน​เดิม​เป๊ะ​ทั้ง​ก่อน/หลัง​แยก Delivery เปลี่ยน​แค่​เทรซ​ตอน​นี้​พาด​ข้าม process จริง จุด​ที่​ต้อง​ซื่อสัตย์: consumer span จะ​เกาะ​แบบ parent-child (model Envelope) หรือ span link (ค่า​เริ่มต้น OTel messaging) ต้อง​รัน​จริง​ถึง​จะ​ฟัน​ธง — เรา​ไม่​เดา​แทนคุณ

บท​หน้า​เรา​ออก​จาก​โลก​ของ trace ต่อ​เส้น ไป​สู่ metrics — ตัวเลข​สรุป​รวม​แบบ RED และ​กับดัก cardinality ที่​ทำให้​บิล​ระเบิด (ทำไม order.id ต้อง​เป็น span attribute ไม่ใช่ metric label — บท​นี้​ปู​ไว้​แล้ว บท​หน้า​จะ​อธิบาย​ว่า​ทำไม)


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

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

  • OpenTelemetry — Traces (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — นิยาม span: name/parent-id/span context/attributes/events/links/status/kind; Status = Unset/Ok/Error; Kind = Internal/Server/Client/Producer/Consumer
  • OpenTelemetry — Context Propagation (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — การ​ส่ง span context ข้าม​ขอบเขต process; propagator เริ่มต้น = W3C TraceContext (+Baggage)
  • W3C Trace Context (Recommendation, 2021-11-23; orig. 2020-02-06) — รูปแบบ traceparent 00-<trace-id 32hex>-<span-id 16hex>-01 เช่น 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  • OpenTelemetry Semantic Conventions — Messaging spans (semconv v1.43.0, เข้าถึง 2026-07-22, Status: Development) — messaging span; span link เป็น​ค่า​เริ่มต้น สำหรับ​ความ​สัมพันธ์ producer↔consumer (ยัง​เป็น Development — ถือว่า experimental)
  • ClickHouse blog — Logging, Metrics, and Distributed Tracing in .NET with OpenTelemetry and ClickStack (2026-06-03) — pattern DiagnosticConfig static holder + StartActivity + activity?.SetTag
  • WolverineFx — Logging & OpenTelemetry (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — AddSource("Wolverine") เก็บ handler/send/receive span; internal-message OTel ปิด​โดย​ค่า​เริ่มต้น​ตั้งแต่ 1.7
  • WolverineFx — Message header/correlation propagation (เอกสาร​ทางการ, เข้าถึง 2026-07-22) — Envelope พา correlation/parent context ข้าม transport ให้ consumer ต่อ​เทรซ​เดิม

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

ข้อ 1 / 3

อะไรทำให้เทรซหนึ่งเส้น 'ต่อกันได้' ข้าม RabbitMQ จาก Ordering ไป Delivery ที่เป็นคนละ process?