OpenTelemetry Learning
Telemetry signals

Spans

Hiểu span như một operation trong OpenTelemetry, từ SpanContext, kind, attributes, events và links đến status, lifecycle và cách xác minh dữ liệu.

Mental model

Span là bản ghi của một operation có điểm bắt đầu và kết thúc. Trace là toàn bộ đường đi; span là từng bước có ý nghĩa trên đường đi đó. Hãy tạo span để trả lời câu hỏi về latency, lỗi hoặc dependency, không phải để sao chép mọi function call của ứng dụng.

Mục lục

Span là một operation

Trong OpenTelemetry, span đại diện cho một operation đơn lẻ trong trace. Operation là một công việc có ranh giới rõ ràng, ví dụ:

  • nhận và xử lý POST /checkout;
  • gọi payment-service;
  • chạy truy vấn ghi order;
  • publish message order.created;
  • xử lý một batch messages từ queue;
  • thực hiện bước nghiệp vụ validate cart.

Một span tốt cho biết operation nào đã diễn ra, bắt đầu và kết thúc lúc nào, nằm ở đâu trong causal path, kết quả ra sao và có metadata nào cần để điều tra. Causal path là chuỗi quan hệ nhân quả: operation B xảy ra vì operation A đã gọi, tạo hoặc lên lịch cho B.

Một function call rất ngắn như formatCurrency() thường không cần span. Ngược lại, một lời gọi database hoặc một bước giữ lock có thể đáng có span vì nó giải thích latency và failure.

Span khác trace như thế nào

Khái niệmPhạm viVí dụ trong checkout
SpanMột operationMột lần gọi payment-service
TraceTập các span có cùng trace ID, mô tả đường đi end-to-endToàn bộ request từ gateway đến database và queue
SpanContextPhần định danh và trace state cần truyền qua boundaryTrace ID, span ID, flags và tracestate của outgoing call

Trang Traces giải thích cách backend ghép nhiều span thành cây và cách đọc critical path. Trang này tập trung vào cấu tạo và lifecycle của một span.

Một span chứa những gì

Một span thường có các trường sau:

Thành phầnCâu hỏi nó trả lời
NameOperation này thuộc lớp công việc nào?
SpanContextSpan và trace này được định danh thế nào?
ParentOperation trực tiếp nào dẫn đến operation này?
SpanKindĐây là công việc nội bộ, request vào, call ra hay deferred work?
Start và end timestampOperation bắt đầu, kết thúc lúc nào?
AttributesMetadata ổn định của operation là gì?
EventsNhững thời điểm đáng chú ý nào xảy ra trong operation?
LinksOperation còn liên quan nhân quả tới context nào ngoài parent?
StatusKết quả đã được đánh dấu UNSET, OK hay ERROR?

Duration thường được backend tính bằng end_time - start_time. Nó mô tả thời gian trôi qua của operation, không phải luôn là CPU time và cũng không phải tổng duration của các child spans.

SpanContext giữ định danh cho propagation

SpanContext là phần bất biến của span được dùng để định danh và propagation. Nó không chứa name, attributes, events hay status. Khi một request đi qua HTTP, RPC hoặc messaging boundary, propagator serialize phần context cần thiết vào carrier rồi phía nhận extract nó.

Chi tiết về traceparent, inject và extract nằm tại Context propagation. Ở đây chỉ cần nắm những field ảnh hưởng trực tiếp đến span.

Năm thành phần cần biết

Thành phầnÝ nghĩaĐiều cần nhớ
Trace IDĐịnh danh toàn bộ trace16 byte; dạng hex có 32 ký tự; không được toàn số 0
Span IDĐịnh danh span hiện tại8 byte; dạng hex có 16 ký tự; không được toàn số 0
Trace flagsCác cờ chung của traceCó cờ sampled; specification hiện hành còn định nghĩa cờ random
TracestateDanh sách key-value dành cho trạng thái theo tracing systemĐể SDK và propagator xử lý; không dùng như business metadata
RemoteBoolean cho biết context có được nhận từ process khác hay khôngContext do propagator extract có IsRemote = true

Ví dụ rút gọn:

SpanContext
├── trace_id:    4bf92f3577b34da6a3ce929d0e0e4736
├── span_id:     00f067aa0ba902b7
├── trace_flags: sampled
├── tracestate:  vendorname=opaque-value
└── remote:      true

sampled là tín hiệu cho biết span được chọn để export theo quyết định sampling. Nó không có nghĩa là mọi span mong đợi chắc chắn đã đến backend. Export failure, Collector filtering hoặc backend limits vẫn có thể làm trace thiếu. Xem Sampling để hiểu head sampling, tail sampling và tính nhất quán theo parent.

Trace context không phải dữ liệu xác thực

Trace ID, span ID và tracestate giúp correlation, không chứng minh caller đã đăng nhập hoặc được cấp quyền. Hãy coi context extract từ network là input không tin cậy và áp dụng policy tại trust boundary.

Trace ID và span ID được sinh như thế nào

Instrumentation yêu cầu Tracer bắt đầu một span. Tracing SDK phía sau TracerProvider mới là thành phần chọn hoặc sinh IDs. Collector và tracing backend thường chỉ nhận, lưu và lập chỉ mục các IDs đã có; chúng không sinh lại ID cho span do ứng dụng gửi tới. Propagator cũng không phải ID generator: nó chỉ serialize và deserialize context qua carrier.

Quy tắc cốt lõi là:

  • trace mới sinh một trace ID mới;
  • mọi span, kể cả root và child, đều sinh một span ID mới;
  • child giữ trace ID của valid parent nhưng không dùng lại span ID của parent.

Thứ tự SDK tạo một span

Theo OpenTelemetry Tracing SDK specification, SDK phải hành xử theo thứ tự logic sau:

  1. Resolve parent context từ context được truyền rõ ràng hoặc active context.
  2. Nếu parent có valid trace ID, giữ trace ID đó. Nếu không, gọi IdGenerator để sinh trace ID mới.
  3. Đưa trace ID vào Sampler.ShouldSample(...) cùng span name, kind, initial attributes và initial links.
  4. Gọi IdGenerator để sinh span ID mới, độc lập với sampling decision.
  5. Tạo local SpanContext với trace ID, span ID, trace flags và tracestate đã được quyết định.
  6. Tùy kết quả sampling, tạo recording span hoặc non-recording span.

Trace ID phải có trước sampling vì sampler có thể dùng chính trace ID để đưa ra quyết định nhất quán. Span ID vẫn được sinh khi sampler trả DROP. Nhờ vậy, propagation, logging hoặc exception handling vẫn có một SpanContext hợp lệ để correlate, dù span không ghi attributes/events và không được export.

Sampling không phải ID generation

Sampling quyết định có record hoặc export span hay không. Nó không quyết định span có được cấp ID hay không. Vì vậy, ứng dụng có thể đọc được valid trace ID và span ID nhưng không tìm thấy span tương ứng trong tracing backend.

Root child và remote parent

Ví dụ một request đi qua gateway rồi tới checkout service:

Gateway tạo root span G1
trace_id = T1  ← mới
span_id  = G1  ← mới
parent   = none

Gateway tạo client span G2
trace_id = T1  ← giữ từ G1
span_id  = G2  ← mới
parent   = G1

Checkout extract G2 rồi tạo server span C1
trace_id = T1  ← giữ từ remote parent G2
span_id  = C1  ← mới
parent   = G2

Nếu checkout không nhận được valid traceparent, SDK không có valid parent trace ID. Server span của checkout trở thành root mới, sinh T2 và một span ID mới. Đây là cách lost propagation làm một request xuất hiện thành hai traces.

Bảng quyết định ngắn:

Parent tại lúc tạo spanTrace ID của span mớiSpan ID của span mới
Không có parentSinh mớiSinh mới
Parent không hợp lệSinh mớiSinh mới
Valid local parentGiữ trace ID của parentSinh mới
Valid remote parentGiữ trace ID của parentSinh mới
Tạo root có chủ đích dù đang có active spanSinh mớiSinh mới

Một server span ở Backend không nhất thiết sinh trace ID. Nếu Browser, API Gateway hoặc caller đã gửi valid context, Backend tiếp tục trace ID đó và chỉ sinh span ID cho operation của mình.

IdGenerator và tính ngẫu nhiên

IdGenerator thuộc cấu hình của TracerProvider. SDK mặc định phải sinh ngẫu nhiên cả trace ID và span ID. SDK cũng phải cung cấp cơ chế thay generator cho cả hai loại ID, dù API cấu hình cụ thể phụ thuộc ngôn ngữ.

Giá trịKích thướcDạng hex chuẩnPhạm vi nhận diện
Trace ID16 byte, 128 bit32 ký tự lowercase hexToàn bộ distributed trace
Span ID8 byte, 64 bit16 ký tự lowercase hexMột span trong trace

Ít nhất một byte phải khác 0; ID toàn số 0 là invalid. SDK phải tránh trả ID invalid, thường bằng cách sinh lại nếu nguồn random tạo ra toàn zero.

Random IDs không tạo bảo đảm toán học rằng collision là bất khả thi. Chúng làm xác suất hai ID trùng nhau đủ nhỏ khi generator có phân phối và entropy phù hợp. Entropy là lượng bất định trong giá trị được sinh. Generator kém, seed bị lặp sau restart hoặc counter chỉ unique trong một process có thể làm xác suất collision tăng mạnh.

W3C Trace Context Level 2 còn định nghĩa random trace flag. Khi SDK sinh root trace ID đáp ứng yêu cầu randomness của W3C Level 2, SDK nên đặt flag này. Custom IdGenerator nên cho SDK biết IDs của nó có đáp ứng yêu cầu đó hay không. Đây là khả năng phụ thuộc phiên bản SDK; không tự bật flag chỉ vì nhìn ID có vẻ ngẫu nhiên.

Random không có nghĩa là secret

Trace ID và span ID là correlation identifiers, không phải credential hoặc access token. Không dùng độ khó đoán của ID để bảo vệ dữ liệu và không cấp quyền chỉ vì caller biết một trace ID.

Khi nào mới dùng custom IdGenerator

Custom generator chỉ nên dùng khi có contract tương thích cụ thể, chẳng hạn phải tham gia một tracing protocol có quy tắc ID riêng, hoặc khi test cần IDs xác định. Khi thay generator trong production, phải giữ các invariant:

  • đúng 16 byte cho trace ID và 8 byte cho span ID;
  • không sinh ID toàn zero;
  • xác suất collision phù hợp với lưu lượng và phạm vi triển khai;
  • thread-safe và không lặp sequence sau process restart hoặc fork;
  • không mã hóa PII, tenant ID, hostname hoặc business data vào ID;
  • khai báo randomness capability đúng nếu SDK hỗ trợ W3C random flag;
  • có concurrency, restart và statistical tests phù hợp.

Không nên tự tạo trace/span ID trong business code bằng:

  • UUID string có dấu gạch nối rồi chép thẳng vào field;
  • timestamp hoặc timestamp cộng process ID;
  • counter chỉ tăng trong một instance;
  • hash của user ID, order ID, URL hoặc request body;
  • copy span ID của parent sang child;
  • tự nối hoặc parse traceparent thay cho propagator.

Các cách này dễ sai format, giảm entropy, tạo collision hoặc làm lộ quan hệ với dữ liệu nghiệp vụ. Trong ứng dụng thông thường, hãy để default SDK IdGenerator sở hữu việc sinh ID.

Local context và remote context

Giả sử gateway có client span G2 rồi gọi checkout service:

Gateway process                         Checkout process

G2 SpanContext
span_id = aaaaaaaaaaaaaaaa
remote  = false
        │
        │ inject vào HTTP carrier
        ▼
extracted parent SpanContext
span_id = aaaaaaaaaaaaaaaa
remote  = true
        │
        └── tạo checkout SERVER span C1
            span_id = bbbbbbbbbbbbbbbb
            remote  = false

remote mô tả context cha đã được nhận từ nơi khác, không nói span hiện tại đang chạy trên remote machine. Sau khi checkout service tạo C1, SpanContext của C1 là local (remote = false). Khi C1 được inject sang service tiếp theo, phía nhận lại extract nó thành remote parent.

SpanContext là immutable. Nếu cần thay đổi tracestate khi propagate hoặc export, thành phần tương ứng tạo một bản sao hợp lệ thay vì sửa context đang gắn với span.

Tên thời gian và quan hệ cha con

Name start end và duration

Name nên mô tả lớp operation ổn định, dễ nhóm và dễ đọc. Với HTTP server, POST /checkout là tên tốt. POST /checkout?cart_id=8f27... là tên xấu vì mỗi cart có thể tạo một giá trị khác.

Start timestamp tương ứng với lúc operation thực sự bắt đầu. End timestamp tương ứng với lúc operation hoàn tất. Nếu code chỉ phát hiện operation sau khi nó đã bắt đầu, nhiều SDK cho phép truyền start timestamp trong quá khứ. Không tự truyền timestamp khi API được gọi đúng lúc bắt đầu; hãy để SDK lấy thời gian hiện tại.

start_time                  event                     end_time
    │                          │                          │
    ▼                          ▼                          ▼
    [ nhận request ─ parse ─ validate ─ gọi DB ─ gửi response ]
    <--------------------- duration --------------------->

Với request-response, span nên bao phủ toàn bộ công việc mà operation tuyên bố đo: nhận và parse request, middleware liên quan, business logic, tạo response và gửi response. Nếu kết thúc server span trước khi response được gửi, duration không còn trả lời đúng câu hỏi về server-side request latency.

Root span và child span

Mỗi span có không hoặc một parent trực tiếp và có thể có nhiều children.

  • Root span không có parent. SDK tạo trace ID mới cho root span.
  • Child span dùng cùng trace ID với parent và có span ID mới.
  • Parent có thể local hoặc remote.
  • Các child có cùng parent là siblings.

Parent-child mô tả quan hệ nhân quả, không bắt buộc mọi khoảng thời gian phải lồng tuyệt đối. Trong async code, parent có thể kết thúc trước child. Gọi End trên parent cũng không tự động end children.

Span đầu tiên trong giao diện chưa chắc là root thật

Backend có thể không nhận được upstream parent do sampling, retention, quyền truy cập hoặc export failure. Hãy kiểm tra parent_span_id; đừng chỉ dựa vào vị trí trên cùng của waterfall.

Năm loại SpanKind

SpanKind cho backend biết operation nằm ở phía nào của một boundary và kiểu giao tiếp là request-response hay deferred execution. Nếu không chỉ định, kind mặc định là INTERNAL.

SpanKindHướngKiểu giao tiếpVí dụ
INTERNALKhông biểu diễn remote boundaryNội bộValidate cart, tính discount
SERVERIncomingRequest-responseNhận HTTP POST /checkout
CLIENTOutgoingRequest-responseGọi HTTP payment hoặc database
PRODUCEROutgoing hoặc schedulingDeferredPublish order.created
CONSUMERIncoming hoặc processingDeferredXử lý message order.created

Kind không thay thế attributes và không tự tạo propagation. Một span có CLIENT kind nhưng không inject context vẫn có thể làm trace bị đứt.

INTERNAL

INTERNAL đại diện cho operation bên trong ứng dụng, không trực tiếp mô tả việc nhận hoặc gửi remote request. Ví dụ:

  • validate cart;
  • calculate discount;
  • một middleware có latency đáng điều tra;
  • một bước của thuật toán chạy trong cùng process.

Đừng dùng INTERNAL cho mọi function. Chỉ tạo nó khi operation có duration, failure hoặc business meaning cần quan sát riêng.

SERVER và CLIENT

SERVER bao phủ việc xử lý incoming remote request trong mô hình request-response. CLIENT bao phủ outgoing remote call mà caller chờ kết quả.

checkout-service                           payment-service

CLIENT "POST /charges" ── context ───────► SERVER "POST /charges"
       caller chờ response                  xử lý request phía nhận

Một logical remote call có thể có cả client span và server span. Hai duration này chồng lấp phần lớn thời gian. Chênh lệch có thể gồm network, proxy, queueing hoặc clock skew, vì vậy không cộng chúng như hai bước tuần tự.

Database client call thường dùng CLIENT, dù bạn không instrument database server. Nếu database driver bên trong dùng HTTP và cả hai lớp đều được instrument, có thể xuất hiện nested client spans hợp lệ cho logical database operation và HTTP transport.

PRODUCER và CONSUMER

PRODUCER mô tả việc khởi tạo hoặc lên lịch deferred work. Producer thường kết thúc trước consumer và không chờ consumer trả kết quả. CONSUMER mô tả việc xử lý công việc đó.

async/await không quyết định kind. Một HTTP call dùng await vẫn là request-response nên outgoing span là CLIENT. Một message được xử lý vài milliseconds sau trong cùng process vẫn có thể là deferred work nên dùng PRODUCER và CONSUMER theo semantic convention phù hợp.

Mỗi span nên có một mục đích. Không dùng cùng server span để đại diện luôn cho outgoing payment call. Hãy tạo client span mới trước khi inject context của outgoing call.

Attributes và semantic conventions

Attributes là các cặp key-value mô tả operation. Ví dụ checkout server span có thể mang:

{
  "http.request.method": "POST",
  "http.route": "/checkout",
  "http.response.status_code": 201,
  "checkout.item_count": 3,
  "error.type": "timeout"
}

Các tên trên chỉ nên được dùng khi đúng với version semantic conventions của SDK và instrumentation trong dự án. OpenTelemetry có conventions riêng cho HTTP, database, RPC, messaging, exceptions và nhiều domain khác.

Thay vì tự đặt httpMethod, statusCode hoặc db_type, hãy ưu tiên field chuẩn. Cách chọn convention, theo dõi stability và migration được trình bày tại Semantic conventions.

Đặt attributes ở đâu và khi nào

Đặt attributes quan trọng đã biết ngay lúc tạo span. Head sampler chỉ có thể xem name, kind, initial attributes và initial links có sẵn tại thời điểm quyết định. Attribute thêm sau không thay đổi quyết định sampling đã xảy ra.

Attributes phù hợp khi:

  • giá trị mô tả toàn operation;
  • timestamp riêng của giá trị không quan trọng;
  • giá trị giúp filter, group hoặc điều tra span;
  • kiểu và ý nghĩa của key ổn định.

Nếu cùng key được set nhiều lần, giá trị mới thường ghi đè giá trị cũ. Đừng dùng cách này để lưu lịch sử thay đổi. Khi từng thời điểm thay đổi có ý nghĩa, hãy dùng events.

SDK áp dụng limits cho số lượng và độ dài attributes. Specification khuyến nghị mặc định tối đa 128 attributes trên một record và không giới hạn độ dài value, nhưng SDK, Collector hoặc backend có thể cấu hình khác. Dữ liệu vượt limit có thể bị discard hoặc truncate.

Kiểm soát cardinality và dữ liệu nhạy cảm

Cardinality là số lượng giá trị khác nhau của một field. http.route thường có cardinality thấp. user.id, order.id, request ID và URL đầy đủ thường có cardinality cao.

Cardinality cao trên trace attributes có thể làm index lớn, query và aggregation đắt hơn. Cardinality cao trong span name còn chia cùng một operation thành vô số nhóm khó đọc. Với metrics, ảnh hưởng tới số time series còn trực tiếp hơn.

Dữ liệuCách làm nên chọnCách nên tránh
HTTP endpointName POST /orders/{order_id} và attribute route templateName POST /orders/981742
Lỗierror.type = "timeout" khi convention yêu cầuDùng nguyên stack trace làm attribute để group
Order IDChỉ ghi khi có use case, policy và retention rõ ràngLuôn ghi mọi ID vào mọi span
Request bodyGhi field đã allowlist và redact nếu thật sự cầnGhi toàn body, token hoặc thông tin thẻ
SQLOperation hoặc statement đã sanitize theo conventionGhi SQL có dữ liệu người dùng hoặc secret

Semantic convention không thay thế data governance

Một key chuẩn vẫn có thể chứa PII hoặc secret. Không ghi password, access token, cookie, API key, dữ liệu thẻ hoặc baggage chưa allowlist vào span.

Events ghi lại thời điểm đáng chú ý

Span event là một annotation có tên, timestamp và zero hoặc nhiều attributes. Event phù hợp với một điểm thời gian đáng chú ý bên trong operation.

Ví dụ checkout span kéo dài 180 ms có thể có:

0 ms      span start: POST /checkout
12 ms     event "cart.validated"       item_count=3
98 ms     event "payment.retry"        attempt=2 reason="timeout"
171 ms    event "order.persisted"      storage="orders-db"
180 ms    span end

Dùng event khi timestamp của sự việc có giá trị điều tra. Ví dụ:

  • retry được lên lịch;
  • cache miss xảy ra;
  • state chuyển sang payment_authorized;
  • exception được throw hoặc caught;
  • response bắt đầu được stream.

Nếu chỉ cần nói checkout.item_count = 3 cho toàn operation, dùng span attribute. Nếu cần biết lúc nào cart được validate, dùng event cart.validated kèm item_count = 3.

Event không có duration riêng. Nếu cần biết validate cart mất bao lâu, hãy tạo child span thay vì hai events validation.started và validation.finished, trừ khi chi phí tạo span không phù hợp và bạn chủ động chấp nhận cách mô hình đó.

Events có count limit và attribute limit riêng. Một event cho từng item của cart lớn hoặc từng vòng lặp có thể nhanh chóng chạm limit. Hãy aggregate hoặc chỉ ghi những state transition có giá trị.

Mỗi span chỉ có tối đa một parent. Link cho phép span tham chiếu thêm tới một hoặc nhiều SpanContext trong cùng trace hoặc trace khác. Mỗi link có thể có attributes mô tả quan hệ.

Links không biến span được link thành parent và không làm hai trace dùng chung trace ID. Chúng bổ sung causal relationship khi cây một-parent không đủ biểu diễn.

Đặt links đã biết ngay lúc tạo span để sampler có thể xem chúng. Links thêm sau khi span đã bắt đầu có thể vẫn được record, nhưng không thể ảnh hưởng head sampling decision đã hoàn tất.

Queue và batch processing

Giả sử worker nhận một batch gồm ba messages. Mỗi message được publish từ một trace khác nhau:

Chọn producer P1 làm parent sẽ làm P2 và P3 biến mất khỏi mô hình nhân quả. Một cách biểu diễn phù hợp là tạo consumer span cho batch rồi thêm ba links tới ba message creation contexts. Link attributes có thể cho biết message position hoặc loại quan hệ, nếu semantic convention cho messaging cho phép.

Nếu cần trace từng message riêng, specification khuyến nghị tạo một producer span cho từng message. Khi xử lý từng message, hãy làm theo messaging semantic conventions của phiên bản đang dùng để chọn parent hay link. Với deferred job kéo dài rất lâu, hệ thống cũng có thể chủ động bắt đầu trace mới rồi dùng link tới trace đã enqueue job.

Links cần backend hỗ trợ hiển thị và truy vấn

OpenTelemetry có thể export links nhưng trải nghiệm điều hướng khác nhau giữa các backend. Hãy kiểm tra dữ liệu OTLP hoặc Collector output, không chỉ dựa vào việc UI có vẽ mũi tên hay không.

Câu hỏiEventChild spanLink
Có duration riêng không?KhôngCóLink không tạo duration; target span có thể có
Có một parent trực tiếp không?Thuộc span hiện tạiCóKhông thay parent
Dùng cho một thời điểm đáng chú ý?CóThường khôngKhông
Dùng cho sub-operation cần đo latency hoặc lỗi?Không phù hợp nếu cần durationCóChỉ khi liên hệ tới operation khác
Dùng cho nhiều nguồn nhân quả?KhôngMột child vẫn chỉ có một parentCó
Ví dụ checkoutpayment.retrycharge paymentBatch consumer link tới từng producer context

Quy tắc thực dụng:

  1. Nếu cần đo một khoảng thời gian độc lập, dùng child span.
  2. Nếu cần đánh dấu một thời điểm trong operation hiện tại, dùng event.
  3. Nếu cần liên hệ với operation không phải parent trực tiếp, dùng link.

Status và exception

Status là kết luận có chủ đích về kết quả operation. Nó không thay thế HTTP status code, gRPC status, exception event hoặc error attributes. Protocol result được ghi bằng attributes theo semantic conventions; span status được suy ra hoặc đặt theo quy tắc của convention đó.

UNSET OK và ERROR

StatusÝ nghĩaCách dùng
UNSETChưa đặt status rõ ràng; đây là mặc địnhGiữ nguyên cho phần lớn operation không lỗi theo convention
OKDeveloper hoặc operator xác nhận operation thành côngChỉ đặt khi cần kết luận thành công rõ ràng
ERROROperation có lỗi theo semantic convention hoặc convention của instrumentationCó thể kèm description ổn định và dự đoán được

Instrumentation libraries thường không cần đặt OK cho mọi thành công. UNSET không có nghĩa là telemetry bị thiếu; nó cho phép backend diễn giải kết quả từ protocol attributes và conventions. OK là kết luận cuối rõ ràng và có thể làm công cụ phân tích bỏ qua lỗi mà nó lẽ ra suy ra.

Chỉ dùng status description với ERROR. Không nhét stack trace, payload hoặc error message biến thiên mạnh vào description. Hãy dùng exception event và các field chuẩn cho chi tiết lỗi.

Ví dụ payment attempt đầu timeout:

name:        POST /charges
kind:        CLIENT
status:      ERROR
status desc: timeout
attributes:  error.type="timeout", retry.attempt=1

Payment attempt thứ hai thành công thường có thể giữ UNSET, tùy quy tắc của instrumentation. Checkout root cuối cùng cũng có thể giữ UNSET nếu trả 201 và HTTP semantic convention không yêu cầu status khác.

Ghi exception đúng cách

RecordException là biến thể chuyên biệt của AddEvent. Nó ghi exception dưới dạng event theo exception semantic conventions, thường gồm type, message và stack trace khi SDK hỗ trợ.

Điểm dễ nhầm: record exception không tự động đảm bảo status chuyển thành ERROR. Nếu exception làm operation thất bại, instrumentation phải đặt status theo semantic convention tương ứng. Nếu exception đã được xử lý và operation vẫn thành công, không nhất thiết đánh dấu parent span lỗi.

try:
    charge()
catch err:
    paymentSpan.recordException(err)
    paymentSpan.setStatus(ERROR, "payment timeout")
    retry()

Trong ví dụ này, span của attempt lỗi là ERROR. Parent checkout có thể vẫn thành công sau retry. Điều đó không mâu thuẫn: status mô tả kết quả của từng operation, không phải tự động lan từ child lên toàn bộ tổ tiên.

Đừng đánh dấu toàn trace ERROR một cách máy móc

Child lỗi đã được retry thành công vẫn là dữ liệu quan trọng. Giữ child đó là ERROR, nhưng đặt status của parent theo kết quả mà caller thực sự nhận và theo semantic convention của operation cha.

Ba lớp metadata không nên trộn lẫn

Một exported span được đọc cùng Resource, InstrumentationScope và attributes riêng của span. Ba lớp trả lời ba câu hỏi khác nhau.

LớpCâu hỏiVí dụ
ResourceEntity nào phát telemetry?service.name, version, environment, host, container
Instrumentation scopeCode instrumentation nào tạo telemetry?Tên package instrumentation, version, schema URL
Span attributesOperation cụ thể này có đặc điểm gì?HTTP route, status code, database operation, item count

Resource

Resource mô tả entity phát telemetry và được gắn logic với nhiều spans, metrics và logs. Ví dụ:

{
  "service.name": "checkout-service",
  "service.version": "2026.03.0",
  "deployment.environment.name": "production"
}

Không copy service.name vào từng span attribute custom. Cấu hình nó ở Resource để mọi telemetry từ cùng entity nhất quán và backend nhận diện service đúng.

Instrumentation scope

Instrumentation scope nhận diện thành phần đã tạo span. Khi lấy một Tracer, code thường cung cấp tên scope và có thể cung cấp version, schema URL hoặc scope attributes.

scope.name    = com.example.checkout.instrumentation
scope.version = 2.4.1

Scope không phải tên service. Hai instrumentation libraries trong cùng checkout-service dùng cùng Resource nhưng có scope khác nhau. Scope rất hữu ích khi tìm duplicate spans do auto-instrumentation và manual instrumentation cùng instrument một boundary.

Span attributes

Span attributes chỉ mô tả execution hiện tại:

{
  "http.request.method": "POST",
  "http.route": "/checkout",
  "http.response.status_code": 201,
  "checkout.item_count": 3
}

Không đặt metadata triển khai ổn định như service.version lên từng span nếu nó thuộc Resource. Không dùng scope attributes để lưu cart ID hoặc response code. Đặt đúng lớp giúp query nhất quán và giảm dữ liệu lặp.

Lifecycle của một span

Lifecycle thực dụng gồm create → activate → record → end. Activate là bước riêng và có thể không cần trong một số API, nhưng phải hiểu rõ để child spans và logs nhận đúng context.

Sơ đồ mô tả thứ tự an toàn phổ biến. SDK có thể cho phép record trước hoặc không activate span. Tên API cụ thể khác nhau giữa Java, Go, Python, JavaScript và .NET.

Create

Lấy Tracer từ TracerProvider, sau đó tạo span với:

  • name bắt buộc;
  • parent context hoặc tùy chọn tạo root;
  • SpanKind;
  • initial attributes;
  • initial links;
  • start timestamp tùy chọn khi logical start đã xảy ra.

Tạo span không mặc định làm span đó active theo Trace API specification. Đây là nguyên nhân phổ biến khiến child span vô tình gắn vào parent cũ.

Nếu tạo root, SDK sinh trace ID mới. Nếu parent context hợp lệ, child giữ trace ID của parent, sinh span ID mới và kế thừa tracestate theo mặc định.

Activate

Đưa span vào current context để instrumentation bên trong operation nhìn thấy nó là active span. Khi tạo child mà không truyền parent rõ ràng, SDK thường dùng active span trong current context làm parent.

Activation phải đi cùng detach hoặc restore context trước. Context API của ngôn ngữ có thể dùng scope, context manager, callback hoặc with block.

previous context
    └── attach span C1
          ├── child spans dùng C1 làm parent
          └── logs có thể correlate với C1
    └── detach và restore previous context

Async scheduler, thread pool hoặc callback library có thể làm mất active context. Hãy test propagation qua từng async boundary thay vì giả định framework luôn giữ context.

Record

Trong lúc operation chạy, instrumentation có thể:

  • set hoặc update attributes;
  • add events;
  • add links nếu API và SDK hỗ trợ;
  • record exception;
  • set status;
  • update name khi final route chỉ được biết sau lúc tạo span.

Kiểm tra IsRecording trước khi tính giá trị rất đắt, ví dụ serialize một object lớn để tạo event. IsRecording = false nghĩa dữ liệu attributes, events và status sẽ bị bỏ ngay. Nó không nên được dùng làm điều kiện cho business logic.

End

End ghi end timestamp và báo operation đã hoàn tất. Sau End, các thay đổi name, attributes, events và status phải bị bỏ qua. Gọi End lần nữa cũng nên bị bỏ qua.

Hai chi tiết quan trọng:

  • End không tự detach span khỏi active context;
  • End không tự end child spans.

Vì vậy, lifecycle context và lifecycle span phải được đóng đúng bằng API idiomatic. Quên end có thể giữ tài nguyên, khiến span không được export hoặc tạo một duration kéo dài sai. Process dừng quá nhanh cũng có thể làm batch chưa được flush; dùng shutdown hook và graceful shutdown theo SDK.

Mẫu instrumentation an toàn

Đây là pseudocode trung lập ngôn ngữ. Tên method thực tế phụ thuộc SDK:

span = tracer.startSpan(
  name = "checkout.process",
  parent = currentContext,
  kind = INTERNAL,
  attributes = {
    "checkout.item_count": cart.itemCount
  }
)

scope = context.activate(span)
try:
    span.addEvent("cart.validated")
    processCheckout()
catch err:
    span.recordException(err)
    span.setStatus(ERROR, "checkout failed")
    throw err
finally:
    scope.close()  // restore context trước
    span.end()     // luôn end operation

Một số SDK cung cấp context manager tự activate, restore và end. Hãy ưu tiên idiom chính thức của SDK để xử lý exception và cancellation đúng.

Ví dụ checkout từ đầu đến cuối

Giả sử người dùng gọi POST /checkout. Gateway gọi checkout service. Checkout validate cart, giữ tồn kho, charge payment, ghi order và publish order.created. Fulfillment worker xử lý message sau đó.

Cây operation

Cây này cố ý tách mỗi boundary theo purpose. Checkout server span không thay thế client spans gọi inventory, payment hay database. Producer không thay consumer.

Đọc từng span

SpanKindParentĐiều nó đo
Gateway POST /checkoutSERVERKhông có nếu request vào không mang valid parentLatency gateway nhận từ client
Gateway gọi checkoutCLIENTGateway serverOutgoing request-response call
Checkout POST /checkoutSERVERRemote parent là gateway clientXử lý request tại checkout service
validate cartINTERNALCheckout serverBước nghiệp vụ nội bộ cần đo
reserve inventoryCLIENTCheckout serverRemote inventory call
Mỗi charge paymentCLIENTCheckout serverMột attempt riêng biệt
INSERT orderCLIENTCheckout serverDatabase call
publish order.createdPRODUCERCheckout serverEnqueue deferred work
process order.createdCONSUMERParent hoặc link theo topology và conventionXử lý message sau đó

Tất cả synchronous spans từ gateway đến producer có thể dùng cùng trace ID nếu propagation đúng. Consumer có thể tiếp tục trace cũ hoặc bắt đầu trace mới có link, tùy topology, thời gian trì hoãn và messaging semantic conventions.

Dữ liệu của checkout server span

Output dưới đây là mô hình dễ đọc, không phải OTLP JSON chính xác:

{
  "name": "POST /checkout",
  "context": {
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "span_id": "b7ad6b7169203331",
    "trace_flags": "sampled",
    "tracestate": "vendorname=opaque-value",
    "remote": false
  },
  "parent_span_id": "00f067aa0ba902b7",
  "kind": "SERVER",
  "start_time": "2026-03-20T10:15:30.100Z",
  "end_time": "2026-03-20T10:15:30.282Z",
  "duration_ms": 182,
  "status": "UNSET",
  "attributes": {
    "http.request.method": "POST",
    "http.route": "/checkout",
    "http.response.status_code": 201,
    "checkout.item_count": 3
  },
  "events": [
    {
      "name": "payment.retry",
      "timestamp": "2026-03-20T10:15:30.198Z",
      "attributes": {
        "retry.attempt": 2,
        "error.type": "timeout"
      }
    }
  ],
  "resource": {
    "service.name": "checkout-service",
    "service.version": "2026.03.0"
  },
  "instrumentation_scope": {
    "name": "com.example.checkout.instrumentation",
    "version": "2.4.1"
  }
}

parent_span_id trỏ tới gateway client span. Context cha đã extract là remote, nhưng SpanContext của checkout server span mới tạo là local nên remote của span hiện tại là false.

Status UNSET là hợp lý khi request cuối cùng thành công và convention không yêu cầu đặt OK. HTTP response code vẫn nằm trong attribute chuẩn để backend diễn giải.

Retry payment và kết quả cuối

Attempt đầu timeout nên có span riêng:

name="POST /charges" kind=CLIENT status=ERROR
attributes: retry.attempt=1, error.type="timeout"
events: exception hoặc timeout event theo instrumentation

Attempt thứ hai thành công:

name="POST /charges" kind=CLIENT status=UNSET
attributes: retry.attempt=2, http.response.status_code=200

Checkout server trả 201, vì vậy nó không cần bị đánh ERROR. Event payment.retry trên checkout span giúp thấy quyết định retry tại một thời điểm. Hai payment client spans cung cấp duration và kết quả riêng của từng attempt.

Nếu chỉ ghi một event payment.timeout mà không có client span, bạn mất duration của attempt. Nếu dùng link giữa hai attempts, bạn mô hình sai vì attempt thứ hai là sub-operation trực tiếp của cùng checkout operation, không phải một causal source ngoài cây.

Quy tắc đặt tên và best practices

Tên ổn định theo lớp operation

Specification yêu cầu name là chuỗi ngắn gọn đại diện cho một lớp spans có ý nghĩa thống kê, không phải từng instance.

TênĐánh giáLý do
getQuá chungKhông biết resource hoặc operation nào
GET /orders/981742Quá cụ thểOrder ID làm cardinality tăng
GET /orders/{order_id}TốtRoute template ổn định và dễ group
checkout.validate_cartTốt cho operation customCó namespace và business meaning rõ
processQuá chungKhông phân biệt được workload
orders.process order.createdTốt nếu khớp messaging conventionCho biết system hoặc destination và operation

Một số instrumentations bắt đầu span trước khi framework resolve route. Khi biết route cuối, có thể update name theo semantic convention. Lưu ý sampler đã quyết định từ lúc tạo span nên tên cập nhật sau không thay đổi quyết định đó.

Không tự phát minh quy tắc name cho HTTP, RPC, database hoặc messaging nếu semantic convention tương ứng đã quy định. Pin version dependency và migration query đồng bộ khi convention thay đổi.

Chỉ tạo span có giá trị quan sát

  • Tạo span cho remote boundary, database call, queue operation và business step có latency hoặc failure cần điều tra.
  • Không trace mọi getter, formatter hoặc loop iteration.
  • Tách retry attempts thành các spans khi cần duration và kết quả từng attempt.
  • Dùng event cho state transition tức thời.
  • Dùng link cho nhiều causal sources hoặc quan hệ qua trace khác.
  • Đặt initial attributes và links trước sampling decision khi có thể.
  • Dùng try/finally, scope hoặc context manager để end span.
  • Extract context trước khi tạo SERVER hoặc CONSUMER span.
  • Tạo outgoing CLIENT hoặc PRODUCER span trước khi inject context.
  • Không dùng telemetry IDs làm business ID, idempotency key hoặc security token.
  • Theo dõi dropped attributes, events, links và exporter failures.

Một span tốt không phải span có nhiều metadata nhất. Nó là span có ranh giới, tên và quan hệ đủ chính xác để trả lời câu hỏi vận hành với overhead chấp nhận được.

Failure modes thường gặp

Triệu chứngNguyên nhân thường gặpCách xác minh và sửa
Span không xuất hiệnQuên End, non-recording span, sampling hoặc export failureKiểm tra IsRecording, sampled flag, shutdown và exporter logs
Span có duration rất dàiEnd quá muộn hoặc quên end ở exception pathBọc operation bằng try/finally hoặc context manager
Child gắn sai parentTạo span nhưng không activate, hoặc async context bị mấtLog trace/span IDs tại boundary và test active context
Child có cùng span ID với parentCustom ID generator lỗi hoặc code tự tạo/copy IDDùng default SDK generator; test uniqueness và parent reference
ID sai độ dài hoặc toàn số 0Custom generator, parser hoặc mapping saiValidate 16-byte trace ID, 8-byte span ID và non-zero invariant
Mỗi service tạo root mớiKhông inject hoặc extract contextCapture carrier và so sánh traceparent tại từng hop
Duplicate HTTP hoặc DB spansAuto và manual instrumentation cùng ghi một boundarySo sánh name, kind, scope, timestamps rồi bỏ lớp trùng
Kind luôn INTERNALKhông truyền kind hoặc instrumentation sai boundaryĐối chiếu năm kinds và semantic convention của domain
Span name có hàng triệu giá trịDùng URL, user ID hoặc order ID trong nameChuyển sang route template; giữ ID ở attribute chỉ khi được phép
Exception có event nhưng UI không đánh lỗiRecordException không tự set statusĐặt ERROR theo semantic convention khi operation thật sự thất bại
Mọi span đều OKInstrumentation đánh dấu success quá mứcGiữ UNSET cho success thông thường; chỉ dùng OK có chủ đích
Parent bị ERROR dù retry thành côngLan status từ child lên parent máy mócĐặt status theo kết quả từng operation và kết quả caller nhận
Batch consumer chỉ liên hệ một messageChọn một producer làm parent và bỏ các nguồn khácThêm links tới từng message context theo messaging convention
Links có trong payload nhưng UI không hiệnBackend không render hoặc không index linksKiểm tra Collector debug exporter hoặc OTLP data
Attributes hoặc events biến mấtVượt SDK span limits hoặc Collector filterKiểm tra dropped counts, limits và processor config
Resource và span metadata lẫn lộnGhi service.name như custom span attributeChuyển identity của entity sang Resource
End span nhưng logs sau đó vẫn correlate saiEnd không detach active contextRestore context riêng trước hoặc sau end theo idiom của SDK
Timeline child vượt parentAsync work, end boundary sai hoặc clock skewXác minh execution model và đồng bộ clock trước khi kết luận

Không sửa trace đứt bằng cách ghép timestamp

Hai spans gần nhau về thời gian và có cùng order ID chưa chứng minh chúng có quan hệ parent-child. Sửa propagation để giữ trace ID và parent context đúng; dùng link khi mô hình cố ý tách trace.

Checklist xác minh

Dùng backend hoặc tạm bật Collector debug exporter trong môi trường kiểm soát. Output chi tiết có thể chứa dữ liệu nhạy cảm, vì vậy không để verbosity cao lâu dài trong production.

Kiểm tra cấu trúc span

  • Span name ổn định và không chứa user ID, order ID hoặc URL động.
  • SpanKind đúng một trong năm loại và đúng purpose của operation.
  • Trace ID hợp lệ, có 32 ký tự hex và không toàn số 0.
  • Span ID hợp lệ, có 16 ký tự hex và không toàn số 0.
  • Root không có parent; mỗi non-root có parent context mong đợi.
  • Child giữ cùng trace ID với parent và có span ID mới.
  • Root sinh trace ID mới; mọi span đều có span ID mới, kể cả non-recording span.
  • Custom ID generator, nếu có, giữ đúng kích thước, non-zero, uniqueness và randomness contract.
  • Context extract từ remote boundary có IsRemote = true trước khi tạo child.
  • Span hiện tại sau khi tạo có local SpanContext.
  • Start và end timestamps bao phủ đúng operation; duration không âm.
  • Client và server spans cùng remote call không bị hiểu là hai bước tuần tự.
  • Producer và consumer phản ánh deferred execution, không dựa vào cú pháp async/await.
  • Batch consumer có links tới đủ causal message contexts khi mô hình yêu cầu.

Kiểm tra dữ liệu và pipeline

  • Resource có service.name đúng và nhất quán.
  • Instrumentation scope chỉ đúng package hoặc module đã tạo span.
  • Span attributes dùng semantic conventions đúng version.
  • Initial attributes và links cần cho sampler được đặt lúc tạo span.
  • Không có secret, token, cookie, PII hoặc payload chưa redact.
  • Events chỉ ghi các thời điểm có ý nghĩa và không vượt count limit.
  • Sub-operation cần duration được mô hình bằng child span, không chỉ event.
  • Exception thất bại operation có event và status ERROR khi convention yêu cầu.
  • Operation thành công thông thường không bị buộc thành OK nếu không cần.
  • Mọi recording span đều được end trên success, error và cancellation path.
  • Active context được detach hoặc restore; End không bị nhầm là detach.
  • Không có duplicate spans từ auto và manual instrumentation.
  • Dropped attribute, event và link counts nằm trong dự kiến.
  • SDK exporter và Collector không báo queue overflow, retry hoặc drop.
  • Sampling policy giải thích được việc span có hoặc không đến backend.

Khi một mục thất bại, hãy kiểm tra từ application ra ngoài: lifecycle và active context → inject/extract → SDK processor/exporter → Collector → backend. Cách này nhanh hơn việc chỉ sửa dashboard khi dữ liệu gốc đã sai.

Nguồn tham khảo chính thức

Bài liên quan

On this page