OpenTelemetry Tracing — Toàn tập: Node.js/TypeScript, Collector và Jaeger phần 7
Bốn chi tiết quyết định script này có dùng được không: baggage: debug=1 để sampler ở Phần 4 luôn giữ trace mồi (không có nó thì script fail ngẫu nhiên theo đúng tỉ lệ sampling); sleep phải dài hơn tổng ngân sách độ trễ cộng decision_wait cộng thời gian storage index xong; endpoint /api/v3/traces/<id> cần extension jaeger_query bật API v3 (cùng cổng 16686) và trả về OTLP JSON bọc trong khoá result — kiểm tra hình dạng thật bằng jq keys một lần trước khi tin script; và ngưỡng >=4 service / >=12 span phải chốt theo luồng thật của mình, không phải con số ở đây.
SLO dựa trên trace
| SLI | Định nghĩa theo span | Nguồn tính |
|---|---|---|
| Availability | 1 trừ (số span SERVER có status.code là STATUS_CODE_ERROR chia tổng span SERVER của route) |
traces.span.metrics.calls tách theo status.code |
| Latency | p99 duration của span SERVER với http.route trọng yếu |
histogram traces.span.metrics.duration |
| Dependency health | tỷ lệ lỗi theo từng cặp client/server | traces_service_graph_request_failed_total |
Hai quy tắc bắt buộc:
- Không tính SLI từ dữ liệu đã sampling. Với ratio 10%, số đếm trace không phải số đếm request, và tail-based còn làm lệch có chủ đích về phía lỗi. Tính SLI từ metric sinh trước bước sampling (
spanmetricsđặt trướctail_sampling), hoặc từ metric ứng dụng độc lập. - Dùng exemplar để nhảy từ điểm bất thường trên biểu đồ latency sang trace cụ thể:
spanmetricscóexemplars.enabled(mặc địnhfalse,max_per_data_pointmặc định 5); bật lên thì data point mang theo trace-id mẫu và dashboard click thẳng sang Jaeger. Exemplar chỉ giữ trong đúng một flush interval, nên chu kỳ scrape phải ngắn hơnmetrics_flush_interval.
Versioning và quản trị
OTel JS phát hành hai dòng version song song, phải nâng theo cụm chứ không nâng lẻ:
| Nhóm | Package | Version đã xác minh (2026-09-08) |
|---|---|---|
| API | @opentelemetry/api |
1.9.1 (peer range của SDK: >=1.3.0 <1.10.0) |
| Stable 2.x | core, resources, sdk-trace-base, sdk-trace-node, sdk-metrics, context-async-hooks, propagator-b3 |
2.11.0 |
| Experimental 0.x | sdk-node, instrumentation, exporter-trace-otlp-http/-proto/-grpc |
0.222.0 |
| Semconv | @opentelemetry/semantic-conventions |
1.43.0 (bám theo version spec semconv) |
| Contrib | @opentelemetry/auto-instrumentations-node |
0.80.0 (đánh số độc lập với core) |
- Pin version chính xác trong
package.json— không dùng^cho nhóm experimental 0.x vì minor của dòng 0.x được phép breaking — và nâng theo lô, một PR cho cả cụm. - Đọc CHANGELOG trước khi nâng. Tên attribute đổi qua các phiên bản (
http.methodsanghttp.request.method,db.statementsangdb.query.text; Phần 9), dashboard và alert dùng tên cũ sẽ im lặng về 0. Subpath@opentelemetry/semantic-conventions/incubatingđược ghi rõ là không tuân semver và có thể breaking ở minor release. - Ép một version
@opentelemetry/apiduy nhất bằngoverrides(npm),resolutions(yarn) hoặcpnpm.overrides; kiểm tra bằngnpm ls @opentelemetry/api. Nhiều bản trongnode_modulesgâyError: @opentelemetry/api: Attempted duplicate registration of API: trace, hoặc tệ hơn là span mất im lặng. - Test span trong CI cho span nghiệp vụ quan trọng, để đổi tên attribute làm CI đỏ chứ không làm dashboard đỏ (Phần 5B).
- Tài liệu hoá attribute nghiệp vụ: một file trong repo liệt kê mọi attribute tiền tố
app.*, kiểu dữ liệu, ai dùng, cardinality dự kiến. Thiếu file này thì sau sáu tháng không ai dám xoá attribute nào. - Đặt owner cho pipeline observability: một team chịu trách nhiệm Collector, backend, retention và ngân sách. Alert của chính pipeline phải on-call như service production, dựng trên telemetry nội bộ của Collector (Prometheus, cổng 8888). Bộ rule tối thiểu ở bảng dưới. Cách đọc và xử lý từng triệu chứng ở Phần 11.
| Alert | Biểu thức PromQL | for |
Severity | Ý nghĩa |
|---|---|---|---|---|
CollectorReceiverRefusing |
sum by (receiver) (rate(otelcol_receiver_refused_spans_total[5m])) > 0 |
5m | warning | memory_limiter đang đẩy ngược, hoặc receiver quá tải — dữ liệu đang mất ngay cửa vào |
CollectorExportFailureRatio |
sum(rate(otelcol_exporter_send_failed_spans_total[5m])) / clamp_min(sum(rate(otelcol_exporter_sent_spans_total[5m])) + sum(rate(otelcol_exporter_send_failed_spans_total[5m])), 1) > 0.01 |
10m | critical | trên 1% span gửi hỏng. Mẫu số là tổng đã cố gửi, không phải chỉ số gửi thành công |
CollectorQueueNearFull |
max by (exporter) (otelcol_exporter_queue_size) / clamp_min(max by (exporter) (otelcol_exporter_queue_capacity), 1) > 0.8 |
5m | warning | queue sắp đầy; đầy là bắt đầu drop |
CollectorPipelineSilent |
sum(rate(otelcol_exporter_sent_spans_total[10m])) == 0 |
10m | critical | pipeline chết im lặng — ca nguy hiểm nhất, vì mọi dashboard khác vẫn xanh |
CollectorProcessorDropping |
sum by (processor) (rate(otelcol_processor_incoming_items_total{otel_signal="traces"}[5m])) - sum by (processor) (rate(otelcol_processor_outgoing_items_total{otel_signal="traces"}[5m])) > 0 |
15m | info | processor nào đang loại dữ liệu. Với filter và tail_sampling thì hiệu số dương là đúng thiết kế — loại trừ chúng, dùng rule này để bắt processor loại nhầm |
Hai cảnh báo về chính bảng này. Thứ nhất, tên metric ở đây là tên sau khi qua exporter Prometheus (hậu tố _total cho counter) và có thể đổi giữa các version Collector — cặp otelcol_processor_incoming_items_total/outgoing chẳng hạn đã thay cho otelcol_processor_accepted_*/refused_*/dropped_* cũ. Xác nhận tên thật trên đúng version đang chạy trước khi dán rule vào:
curl -s localhost:8888/metrics | grep -E 'otelcol_(exporter|receiver|processor)' | cut -d'{' -f1 | sort -u
Thứ hai, bộ rule này phải chạy trên một Prometheus tách khỏi đường trace. Nếu Prometheus chấm điểm sức khoẻ pipeline lại phụ thuộc vào chính pipeline đó, thì pipeline chết là alert chết cùng và không ai biết gì. Đây cũng chính là bộ rule mà §11.5 nói tới bằng lời — dùng bảng này làm nguồn duy nhất.
Migration sang OpenTelemetry
| Xuất phát | Cách chuyển | Rủi ro trace vỡ | Cách chạy song song |
|---|---|---|---|
| OpenTracing | @opentelemetry/shim-opentracing bọc TracerProvider của OTel cho code gọi Tracer cũ; code mới viết thẳng bằng API OTel. Lưu ý yêu cầu tương thích OpenTracing đã deprecated trong spec, và README của package ghi rõ nó sẽ bị gỡ ở mốc SDK 3.0 — đây là đường tạm, không phải đích |
tag OpenTracing không map 1-1 sang semantic conventions; span name cũ không theo quy ước Phần 9 | shim và API OTel dùng chung TracerProvider nên trace liền mạch suốt giai đoạn chuyển |
| Zipkin / B3 | composite propagator gồm W3CTraceContextPropagator và B3Propagator (@opentelemetry/propagator-b3) để nhận cả hai, rồi chuyển dần sang phát traceparent |
service chưa nâng chỉ đọc B3 nên trace đứt tại biên; đổi propagator không đồng bộ là nguyên nhân đứt phổ biến nhất | giữ composite suốt giai đoạn chuyển, chỉ bỏ B3 khi service cuối cùng đã nâng (Phần 3) |
| Agent vendor (Datadog, New Relic) | chạy đồng thời agent cũ và OTel SDK; hoặc gửi OTLP vào Collector rồi fan-out sang cả hai backend bằng hai exporter trong cùng pipeline | hai agent cùng patch một module có thể xung đột hoặc nhân đôi span; trace-id hai hệ khác nhau nên không đối chiếu trực tiếp | fan-out ở Collector an toàn hơn hẳn chạy hai agent trong cùng process |
Thứ tự chuyển an toàn: cho mọi service đọc được cả hai định dạng trước, rồi mới đổi định dạng phát ra, và đổi ở biên vào (API gateway, BFF) sau cùng — biên là nơi trace-id sinh ra, đổi sớm ở đó khi phía sau chưa đọc được traceparent sẽ làm cả chuỗi mất parent.
Checklist rollout theo bốn giai đoạn
Giai đoạn 1 — Một service không trọng yếu cộng Jaeger local
Việc cần làm: chọn một service ít rủi ro; khởi tạo SDK (Phần 5A); chạy Jaeger bằng image cr.jaegertracing.io/jaegertracing/jaeger:2.20.0 với -p 16686:16686 -p 4317:4317 -p 4318:4318; export OTLP thẳng vào Jaeger; sampling 100%.
Xong khi: Jaeger UI hiện trace đủ span HTTP và DB, span name dạng {method} {http.route}, service.name đúng quy ước.
Bẫy: đặt OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4318 mà quên /v1/traces (biến có hậu tố _TRACES_ dùng nguyên văn, không tự nối path); khởi tạo SDK sau khi đã import framework nên monkey-patch không ăn; tìm image all-in-one cho v2 (chỉ có ở Jaeger v1, đã end-of-life 31/12/2025).
Giai đoạn 2 — Một luồng nghiệp vụ xuyên nhiều service cộng Collector ở staging
Việc cần làm: chọn một luồng thật (đặt hàng: web đến order đến payment đến inventory); bật propagation đồng bộ ở mọi service trong luồng; dựng Collector ở staging làm điểm nhận duy nhất; thêm k8sattributes và resourcedetection; dựng bốn chỉ số ở mục "Giám sát chất lượng trace" cho đúng bốn service này.
Xong khi (đo được, không phải nhìn bằng mắt): tỉ lệ root span của order, payment, inventory dưới 1% trong 24 giờ liên tục; unpaired_spans dưới 5% tổng request của service graph; service graph hiện đủ số cạnh của sơ đồ kiến trúc luồng này; synthetic check xanh 20 lần liên tiếp với ngưỡng bốn service; qua message queue vẫn nối được bằng span link (Phần 9).
Bẫy: đứt trace tại hàng đợi và tại Promise thoát khỏi context; mỗi service đặt service.name một kiểu; propagator không đồng nhất giữa các service.
Giai đoạn 3 — Production với sampling, PII redaction và alert cho pipeline
Việc cần làm: head-based sampling ở SDK và tail-based ở gateway (Phần 4); redaction/transform cho PII kèm test canary trong CI; memory_limiter đầu pipeline kèm GOMEMLIMIT bằng 80% hard limit; spanmetrics đặt trước tail_sampling; nạp bộ alert rule ở bảng trên vào Prometheus tách khỏi đường trace.
Xong khi: tỉ lệ otelcol_exporter_send_failed_spans_total trên tổng đã cố gửi dưới 1% suốt 7 ngày liên tục và không alert nào trong bảng bị firing; dung lượng thực tế lệch dưới 30% so với công thức ở trên; test canary PII chạy xanh trong CI ở mỗi lần đổi config Collector; quét regex email và số thẻ trên mẫu ngẫu nhiên 1000 span mỗi tuần không khớp kết quả nào.
Bẫy: nhiều replica gateway có tail_sampling mà thiếu loadbalancing exporter với routing_key: traceID, khiến span của một trace rơi vào nhiều instance và quyết định sampling sai; chưa đặt retention nên đĩa đầy sau vài tuần; alert pipeline không ai nhận.
Giai đoạn 4 — Mở rộng toàn hệ, SLO và quản trị
Việc cần làm: phủ hết service (Operator cho diện rộng, SDK trong image cho service lõi); định nghĩa SLI/SLO theo bảng ở trên và bật exemplar; mở rộng servicegraph và bốn chỉ số chất lượng trace ra toàn hệ; chuẩn hoá attribute app.* và đưa test span vào CI; lập lịch nâng version theo lô.
Xong khi: mọi service production đều có trace; SLO của ít nhất ba route trọng yếu được theo dõi; có tài liệu attribute và owner pipeline; quy trình nâng version đã chạy trọn vẹn một lần.
Bẫy: cardinality nổ do đưa id vào dimensions của spanmetrics; tính SLI từ số đếm trace đã sampling; dashboard vỡ im lặng sau khi nâng semconv vì tên attribute đổi.
Tra cứu sâu hơn: opentelemetry.io/docs/collector, opentelemetry.io/docs/platforms/kubernetes/operator, github.com/open-telemetry/opentelemetry-collector-contrib, jaegertracing.io/docs.
Phần 11 — Troubleshooting
Mọi mục dưới đây theo cùng một khuôn: triệu chứng → nguyên nhân có thể → cách kiểm tra → cách sửa. Khi đang có sự cố, chạy đúng thứ tự các bước và chỉ đổi một thứ mỗi lần.
11.1 Không thấy trace nào
Trước khi chẩn đoán: ngân sách độ trễ end-to-end
Câu hỏi đầu tiên sau khi dựng xong stack luôn là "vừa gọi API xong, chờ bao lâu mà Jaeger vẫn trắng thì mới là bất thường?". Mỗi chặng trên đường đi đều đệm dữ liệu một nhịp, và các nhịp đó cộng dồn. Cộng lại trước, chẩn đoán sau — chạy cây bên dưới khi hệ thống mới chỉ đang chờ là cách nhanh nhất để đi sai hướng.
| Chặng | Độ trễ cộng thêm | Biến điều khiển |
|---|---|---|
| SDK gom span trong app | 5s | BatchSpanProcessor.scheduledDelayMillis / OTEL_BSP_SCHEDULE_DELAY (5000 ms — Phần 2, Phần 6); lô đầy OTEL_BSP_MAX_EXPORT_BATCH_SIZE trước hạn thì đi sớm hơn |
| Export app → Collector | vài chục ms trong cùng cluster | OTEL_EXPORTER_OTLP_TIMEOUT (10000 ms) chỉ là trần, không phải độ trễ thường gặp (Phần 6) |
batch ở Collector agent |
200ms mặc định, 5s trong config mẫu Phần 7 | batch.timeout, send_batch_size (8192) — lô đầy trước timeout thì đi ngay |
batch ở Collector gateway |
cộng thêm đúng một lần như trên | như trên; kiến trúc hai tầng trả giá hai lần (Phần 10) |
tail_sampling ở gateway |
10-30s | decision_wait (mặc định 30s, ví dụ ở Phần 4 và Phần 7 đặt 10s); đồng hồ chạy từ span đầu tiên của trace, không phải từ span cuối |
sending_queue / retry_on_failure |
0 khi backend khoẻ; tới max_elapsed_time (300s trong mẫu Phần 7) khi backend chậm |
retry_on_failure.initial_interval, max_interval, max_elapsed_time (Phần 7) |
| Ghi và index ở storage | Cassandra gần như tức thì; Elasticsearch ~1s | index.refresh_interval của ES (mặc định 1s) — span đã ghi nhưng chưa searchable |
| Riêng tab Monitor (SPM) | +60s rồi cộng tiếp chu kỳ scrape | spanmetrics.metrics_flush_interval (60s — Phần 7, Phần 10) và scrape_interval của Prometheus |
Cộng lại thành ba con số cần thuộc:
- Dev tối giản (app → một Collector → Jaeger, không tail sampling): ~5-8s.
- Production hai tầng có tail sampling: ~20-45s.
- Tab Monitor: tới ~2 phút sau request đầu tiên, kể cả khi trace đã hiện trong tab Search từ lâu.
Rút ngắn khi đang debug (nhớ trả lại sau — đây là cấu hình tốn I/O, không dùng lâu dài):
OTEL_BSP_SCHEDULE_DELAY=1000ở app.batch.timeout: 1sở mọi tầng Collector.- Tạm gỡ
tail_samplingkhỏiservice.pipelines.traces.processors— component khai báo mà không nối vào pipeline thì không chạy (Phần 7).
Đo con số thật của hệ thống mình thay vì ước lượng: lấy endTimeUnixNano của span gốc làm mốc, rồi lặp mỗi giây đến khi Jaeger trả về trace.
# Bao lâu sau khi span kết thúc thì trace mở được trong Jaeger?
TRACE_ID=4bf92f3577b34da6a3ce929d0e0e4736
START=$(date +%s)
until curl -sf "http://localhost:16686/api/v3/traces/${TRACE_ID}" | grep -q resourceSpans; do
sleep 1
done
echo "Trace hiện sau $(( $(date +%s) - START )) giây kể từ lúc bắt đầu chờ"
Chỉ khi đã vượt ngân sách trên mà vẫn trắng thì mới bắt đầu chạy cây chẩn đoán dưới đây.
Cây chẩn đoán
Ca phổ biến nhất. Đi từ app ra backend, không nhảy cóc.
flowchart TD
S["Không thấy trace nào"] --> A{"a. SDK có start"}
A -->|"Không log"| A1["File telemetry chưa chạy trước app — Phần 5A"]
A -->|"Có log"| B{"b. ConsoleSpanExporter in ra gì"}
B -->|"Trống"| D{"d. Sampler có drop hết"}
B -->|"Chỉ span thủ công"| C1["c. Instrumentation không patch được module — Phần 5A"]
B -->|"Đủ span"| E{"e, f. Endpoint và protocol"}
D -->|"Có"| D1["Tạm đặt parentbased_always_on để kiểm chứng — Phần 4"]
D -->|"Không"| C1
E -->|"404, UNIMPLEMENTED, ECONNREFUSED"| E1["g. Sai path, sai cổng, hoặc mạng chặn — Phần 6"]
E -->|"Gửi thành công"| H{"h, i, j. Collector nhận và gửi được"}
H -->|"Không"| H1["Đọc metric nội bộ và service.pipelines — Phần 7"]
H -->|"Có"| K["k. Sai khoảng thời gian hoặc sai Service trên UI; l. Chết trước khi flush"]
| # | Bước | Cách kiểm tra | Kết luận nếu fail |
|---|---|---|---|
| a | SDK có start | OTEL_LOG_LEVEL=debug, xem log khởi tạo và log exporter |
Không có dòng nào của OTel → file telemetry chưa chạy trước app (Phần 5A) |
| b | Có span được tạo | Thêm ConsoleSpanExporter |
Console trống → sang bước c/d |
| c | Chỉ có span thủ công | Gọi thử một route HTTP và một query DB | Instrumentation không hook được module: sai thứ tự khởi tạo hoặc ESM (Phần 5A) |
| d | Sampler | OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG |
always_off, hoặc traceidratio với arg rất nhỏ, drop gần hết (Phần 4) |
| e | Endpoint | Rà mọi biến OTEL_EXPORTER_OTLP_* |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT dùng nguyên văn, phải tự ghi /v1/traces; chỉ OTEL_EXPORTER_OTLP_ENDPOINT mới tự nối path (Phần 6) |
| f | Protocol | Đối chiếu package exporter và OTEL_EXPORTER_OTLP_PROTOCOL với cổng đích |
@opentelemetry/exporter-trace-otlp-grpc → 4317; -otlp-http (http/json) và -otlp-proto (http/protobuf) → 4318. Giá trị hợp lệ của protocol: grpc, http/protobuf (mặc định theo spec), http/json |
| g | Mạng | curl -v, getent hosts, openssl s_client |
Sai DNS/namespace, firewall chặn, TLS không tin cậy |
| h | Collector nhận | debug exporter + otelcol_receiver_accepted_spans |
Không tăng → gói tin chưa tới receiver |
| i | Collector gửi được | otelcol_exporter_send_failed_spans, otelcol_exporter_queue_size |
Tăng đều → hỏng ở chặng Collector → backend |
| j | Pipeline | zpages tại /debug/pipelinez, hoặc đọc lại service.pipelines |
Component khai báo mà không nối vào pipeline thì không chạy (Phần 7) |
| k | UI | Nới khoảng thời gian, chọn đúng Service |
service.name khác suy đoán, hoặc lệch đồng hồ (mục 11.4) |
| l | Flush lúc thoát | Gọi sdk.shutdown() trong handler tín hiệu |
Job/CLI/serverless chết trước khi BatchSpanProcessor kịp export |
Bật hai công cụ chẩn đoán ngay trong file khởi tạo:
// instrumentation.ts — phải chạy trước app
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
// Tương đương OTEL_LOG_LEVEL=debug nhưng bật được có điều kiện
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
const sdk = new NodeSDK({
// In thẳng span ra stdout để xác nhận span có được tạo hay không
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
});
sdk.start();
// (l) Không có đoạn này thì span cuối cùng mất khi container nhận SIGTERM
for (const sig of ['SIGTERM', 'SIGINT'] as const) {
process.once(sig, () => {
sdk.shutdown().finally(() => process.exit(0));
});
}
NodeSDK đọc OTEL_LOG_LEVEL ngay trong constructor, nên thứ gì log trước thời điểm đó sẽ không hiện; gọi diag.setLogger(...) ở dòng đầu file khởi tạo là cách chắc chắn hơn. Xong việc thì gỡ ConsoleSpanExporter: nó ghi đồng bộ ra stdout, rất tốn I/O ở production.
11.2 Trace bị vỡ, orphan span, nhiều trace rời rạc
| Nguyên nhân | Cách kiểm tra | Cách sửa |
|---|---|---|
| Không propagate qua queue (Kafka, SQS, BullMQ) | So traceparent producer đính vào message với giá trị consumer đọc ra |
Tự inject/extract vào message header, nối bằng Link (Phần 3, Phần 9) |
| Proxy, API gateway hoặc CDN strip header | Ở service nhận, log nguyên vẹn header của request | Whitelist traceparent, tracestate, baggage trên proxy |
| Service dùng propagator khác (B3, Jaeger) | So sánh OTEL_PROPAGATORS ở hai đầu |
Đặt cùng danh sách; giai đoạn chuyển đổi thì khai báo cả hai (Phần 3) |
| Sampler khác nhau giữa các service | Xem mục 11.7 | Dùng parentbased_* ở downstream |
Mất context trong async (callback, EventEmitter, worker) |
Log trace.getActiveSpan()?.spanContext().traceId ngay chỗ nghi ngờ |
Bọc trong context.with(...), không cắt chuỗi async (Phần 3) |
| Tự tạo root span thay vì extract | Đọc middleware: có gọi propagation.extract không |
Extract context từ header rồi mới startSpan dưới context đó |
Cách nhanh nhất là so trace-id ở hai đầu. Gửi một traceparent tự đặt: nếu backend không có trace-id này thì lỗi ở phía nhận, không phải phía gửi.
curl -i http://localhost:3000/api/orders \
-H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
Nhớ luật hợp lệ: trace-id hoặc parent-id toàn số 0 là không hợp lệ và cả header bị bỏ qua (https://www.w3.org/TR/trace-context/).
11.3 Span không bao giờ kết thúc, memory tăng dần
Span chỉ được đẩy sang span processor khi end() được gọi. Span chưa end() giữ nguyên attribute, event và mọi reference trong closure.
Nguyên nhân: thiếu span.end() ở nhánh lỗi hoặc nhánh return sớm; end() nằm trong callback không bao giờ được gọi (request treo, promise không settle); giữ reference tới span trong map/cache mà không xóa.
Cách phát hiện: request đã trả về nhưng span không xuất hiện trong Jaeger; RSS tăng theo tổng số request chứ không theo concurrency; node --heapsnapshot-signal=SIGUSR2 rồi so sánh hai snapshot; đếm số span đang mở bằng một span processor phụ.
import type { Context } from '@opentelemetry/api';
import type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
// Chỉ dùng để chẩn đoán: chênh lệch onStart - onEnd chính là số span đang mở
export class ActiveSpanCounter implements SpanProcessor {
private open = 0;
onStart(_span: Span, _ctx: Context): void { this.open++; }
onEnd(_span: ReadableSpan): void { this.open--; }
get value(): number { return this.open; }
async forceFlush(): Promise<void> {}
async shutdown(): Promise<void> {}
}
Cắm processor phụ này vào SDK bằng option spanProcessors: [...] của constructor — BasicTracerProvider#addSpanProcessor(...) đã bị xóa ở dòng SDK 2.x.
Cách sửa: luôn end() trong finally. Lưu ý startActiveSpan cũng không tự end span.
import { SpanStatusCode, trace } from '@opentelemetry/api';
const tracer = trace.getTracer('checkout');
declare function callPaymentGateway(orderId: string): Promise<void>;
export async function charge(orderId: string): Promise<void> {
const span = tracer.startSpan('charge order');
try {
await callPaymentGateway(orderId);
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });
throw err;
} finally {
span.end(); // thiếu finally là rò span
}
}
11.4 Duration sai, span con bắt đầu trước span cha
Timestamp do từng process ghi bằng đồng hồ của host mình. Hai host lệch vài ms là đủ để span con của service B nằm trước span cha của service A.
Nhận biết trong Jaeger: span con bắt đầu sớm hơn cha, span con dài hơn cha, hoặc khoảng cách âm giữa client span và server span. Jaeger hiệu chỉnh clock skew ở tầng query và đánh dấu span đã bị chỉnh trên UI — biên độ hiệu chỉnh đặt bằng jaeger_query.max_clock_skew_adjust (mặc định 30s; 0s để tắt và xem số liệu thô); chi tiết ở Phần 8, mục "Bẫy khi đọc trace".
Cách giảm: chạy NTP/chrony trên mọi node, giám sát offset và cảnh báo khi vượt vài ms. Container dùng đồng hồ của node nên phải sửa ở node.
Không suy diễn latency mạng từ startTime(server span) - startTime(client span): đó là latency thật cộng skew, mà skew thường cùng bậc độ lớn với latency trong cùng datacenter, thậm chí ra số âm. Muốn ước lượng overhead mạng thì lấy duration(client span) - duration(server span), vì hai đại lượng này đo trên cùng một đồng hồ.
11.5 Mất dữ liệu khi tải cao
Triệu chứng: trace thiếu span rải rác, tỉ lệ hụt tăng theo traffic, app không báo lỗi gì rõ ràng.
| Chặng | Nguyên nhân | Kiểm tra | Cách sửa |
|---|---|---|---|
| App | BatchSpanProcessor drop khi queue đầy |
Log mức warn của SDK; so số span Collector nhận với số request thực tế | Tăng OTEL_BSP_MAX_QUEUE_SIZE (mặc định 2048) và OTEL_BSP_MAX_EXPORT_BATCH_SIZE (512, phải ≤ queue size); giảm OTEL_BSP_SCHEDULE_DELAY (5000 ms) để đẩy dày hơn; hoặc giảm số span sinh ra bằng sampling (Phần 4) |
| App → Collector | Export timeout | OTEL_BSP_EXPORT_TIMEOUT (30000 ms) |
Thêm replica Collector, đặt Collector gần app (sidecar/DaemonSet — Phần 10) |
| Collector, chặng nhận | memory_limiter (beta) từ chối để tạo backpressure, lỗi dội ngược lên receiver |
otelcol_receiver_refused_spans, otelcol_processor_refused_spans |
Tăng limit_mib/RAM, đặt GOMEMLIMIT khoảng 80% hard limit, giữ memory_limiter ở vị trí đầu tiên của pipeline, tăng replica |
| Collector exporter | sending_queue đầy |
otelcol_exporter_queue_size so với otelcol_exporter_queue_capacity |
Tăng queue_size và num_consumers (số worker); bật persistent queue bằng extension file_storage (beta; contrib và k8s — Phần 7) |
| Backend | Throttle | otelcol_exporter_send_failed_spans tăng; HTTP 429/503 trong log |
Giảm lưu lượng bằng sampling, mở rộng storage backend (Phần 8) |
Ngưỡng cảnh báo khuyến nghị (internal telemetry của Collector mặc định expose Prometheus ở cổng 8888 — https://opentelemetry.io/docs/collector/internal-telemetry/):
| Metric | Ngưỡng báo động |
|---|---|
otelcol_receiver_refused_spans |
> 0 kéo dài 5 phút |
otelcol_exporter_send_failed_spans |
> 1% so với otelcol_exporter_sent_spans |
otelcol_exporter_queue_size / otelcol_exporter_queue_capacity |
> 0.8 |
otelcol_processor_refused_spans |
> 0 (bản Collector mới còn expose otelcol_processor_incoming_items và otelcol_processor_outgoing_items để so lượng vào — ra của từng processor) |
Theo spec OTLP chỉ 429, 502, 503, 504 được retry (500 không retry), và client nên tôn trọng header Retry-After nếu có (Phần 6).
11.6 Attribute bị mất hoặc bị cắt
Span có limit; khi vượt, SDK bỏ bớt và ghi số lượng đã bỏ vào các trường dropped*Count của span trong OTLP.
| Env var | Mặc định theo spec |
|---|---|
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT |
128 |
OTEL_SPAN_EVENT_COUNT_LIMIT |
128 |
OTEL_SPAN_LINK_COUNT_LIMIT |
128 |
OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT |
128 |
OTEL_LINK_ATTRIBUTE_COUNT_LIMIT |
128 |
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT |
không giới hạn |
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT |
không giới hạn |
Danh sách đầy đủ: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/.
db.query.text(Stable; tên cũdb.statementđã deprecated — Phần 9) bị cắt cụt: hầu như luôn do ai đó đặtOTEL_ATTRIBUTE_VALUE_LENGTH_LIMIThoặcOTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMITtrong Helm chart hay Dockerfile để giảm dung lượng. Bỏ hoặc nâng giá trị, đồng thời kiểm tra option cắt/ẩn query riêng của instrumentation DB đang dùng.- Attribute mất mà limit vẫn dư: khả năng bị processor
attributes,redactionhoặctransformxóa ở Collector (Phần 7), hoặc code đặt attribute sau khi span đãend()— thao tác đó bị bỏ qua im lặng. - Muốn đặt bằng code thay vì env:
NodeSDKcó optionspanLimits. - Số bị bỏ nằm ở
droppedAttributesCount,droppedEventsCount,droppedLinksCounttrong payload OTLP; cách hiển thị tùy backend, dễ thấy nhất là bắt payload bằngdebugexporter của Collector.
11.7 Trace có nhưng thiếu một service
| Nguyên nhân | Cách kiểm tra | Cách sửa |
|---|---|---|
| Service đó chưa instrument | Tìm service.name của nó trong danh sách Service của Jaeger |
Cài SDK (Phần 5A) |
| Sampler không tôn trọng parent | OTEL_TRACES_SAMPLER ở service đó là traceidratio hoặc always_off thay vì parentbased_* |
Đổi sang parentbased_traceidratio hoặc parentbased_always_on (Phần 4) |
| Span bị filter hoặc drop ở Collector | Processor filter, policy của tail_sampling, hoặc service đó gửi vào Collector khác |
Đọc lại pipeline; với tail sampling phải đảm bảo mọi span cùng trace về cùng một instance (Phần 4, Phần 7) |
Trùng service.name |
Hai deployment cùng tên bị gộp làm một | Tách bằng bộ ba service.namespace + service.name + service.instance.id (Phần 9) |
Nếu service đó có nhận request nhưng không extract context thì span của nó nằm ở trace khác chứ không mất — tìm theo thời gian và operation name (mục 11.2).
11.8 N+1 query và các pattern hiệu năng đọc được từ trace
| Pattern | Dấu hiệu trên timeline | Cách xác nhận | Hướng sửa |
|---|---|---|---|
| N+1 query | Hàng chục đến hàng trăm span DB anh em, cùng db.query.summary, mỗi span rất ngắn, xếp nối tiếp dưới một span cha |
Đếm span theo operation name, nhân với duration trung bình để ra phần trăm thời gian | JOIN, IN (...), batch loader, eager load |
| Fan-out tuần tự | Nhiều span client cùng loại nối đuôi nhau, không chồng lấn | Thời gian bắt đầu tăng đều, span sau bắt đầu ngay khi span trước kết thúc | Promise.all cho các lời gọi độc lập |
| Retry storm | Nhiều span client trùng tên, chỉ span cuối thành công; http.request.resend_count > 0 |
Xem error.type và http.response.status_code của các span hỏng |
Sửa timeout, thêm circuit breaker |
| Chờ connection pool | Khoảng trống giữa lúc span cha bắt đầu và span DB đầu tiên | Tương quan với số connection tối đa của pool và concurrency | Tăng pool, giảm thời gian giữ connection |
| Thời gian nằm ngoài span con | Tổng duration span con nhỏ hơn nhiều so với span cha | Thêm span thủ công quanh đoạn xử lý CPU nghi ngờ | Tối ưu code đồng bộ, xem event loop lag và GC |
11.9 Bộ công cụ debug
| Công cụ | Trả lời được câu hỏi gì |
|---|---|
OTEL_LOG_LEVEL=debug, diag.setLogger(...) |
SDK có start không, exporter gửi tới đâu, lỗi gì |
ConsoleSpanExporter |
Có span nào được tạo không; tên, attribute, parent, sampled flag ra sao |
npm ls @opentelemetry/api (pnpm why, yarn why) |
Có nhiều bản @opentelemetry/api trong node_modules không |
curl -v vào endpoint OTLP |
Cổng, path, DNS, TLS có thông không; backend trả mã gì |
telemetrygen (contrib) |
Chặng Collector → backend có chạy không, khi bỏ app ra khỏi phương trình |
debug exporter của Collector (thay cho logging exporter đã bị gỡ) |
Collector có thực sự nhận span không và nội dung ra sao (đặt verbosity: detailed khi cần chi tiết) |
Internal metrics cổng 8888 (mức mặc định normal, chỉnh ở service::telemetry::metrics::level) |
Nhận bao nhiêu, từ chối bao nhiêu, gửi hỏng bao nhiêu, queue đầy chưa |
zpages (beta) cổng 55679: /debug/pipelinez, /debug/servicez, /debug/extensionz |
Pipeline nào thực sự được dựng, component nào đang chạy |
pprof (beta) cổng 1777 |
Collector tốn CPU hay heap ở đâu |
health_check (alpha) cổng 13133 |
Collector còn sống không; tùy chọn check_collector_pipeline được chính docs cảnh báo là không hoạt động đúng, đã có extension healthcheckv2 thay thế |
tcpdump, ss -ltnp |
Gói tin có rời máy không, có process nào nghe cổng đó không |
# Endpoint OTLP/HTTP còn sống? Payload rỗng hợp lệ phải trả 200 kèm partialSuccess rỗng
curl -i -X POST http://collector:4318/v1/traces \
-H 'Content-Type: application/json' \
-d '{"resourceSpans":[]}'
# Sinh trace giả để tách bạch: lỗi ở app hay ở hạ tầng
go install github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@latest
telemetrygen traces --otlp-endpoint collector:4317 --otlp-insecure --traces 1
11.10 Bảng thông báo lỗi thường gặp
| Thông báo / triệu chứng | Nơi xuất hiện | Ý nghĩa | Xử lý |
|---|---|---|---|
connect ECONNREFUSED 127.0.0.1:4318 |
log exporter Node | Không có ai nghe ở cổng đó; trong container, localhost chính là container app |
Trỏ tới tên service Docker/K8s; kiểm tra Collector đã lên chưa |
getaddrinfo ENOTFOUND otel-collector |
log exporter Node | DNS không phân giải được (sai namespace, sai network) | Dùng FQDN kiểu otel-collector.observability.svc.cluster.local |
HTTP 404 từ endpoint OTLP |
log exporter Node | Đang POST vào root path vì OTEL_EXPORTER_OTLP_TRACES_ENDPOINT được dùng nguyên văn |
Ghi đủ http://collector:4318/v1/traces (Phần 6) |
gRPC UNIMPLEMENTED |
log exporter gRPC | Gửi OTLP/gRPC vào cổng HTTP 4318, hoặc phía nhận không có service OTLP gRPC | Đổi sang 4317, hoặc dùng exporter HTTP cho đúng cổng |
gRPC UNAVAILABLE |
log exporter gRPC | Không kết nối được: Collector chưa lên, sai địa chỉ, TLS không khớp | Kiểm tra như hàng ECONNREFUSED; đối chiếu cấu hình TLS/insecure hai bên |
HTTP 401 / 403 |
log exporter | Backend yêu cầu xác thực, thiếu OTEL_EXPORTER_OTLP_HEADERS |
Bổ sung header xác thực (Phần 6, Phần 10) |
HTTP 429 / 503 |
log exporter hoặc Collector | Backend đang throttle | Giảm lưu lượng bằng sampling; tôn trọng Retry-After |
HTTP 200 kèm partial_success và rejected_spans > 0 |
phản hồi OTLP/HTTP | Backend nhận một phần, từ chối phần còn lại; client MUST NOT retry request này | Đọc error_message trong phản hồi: thường do span sai định dạng hoặc vượt hạn mức phía backend |
Error: @opentelemetry/api: Attempted duplicate registration of API: trace |
lúc khởi động app | Nhiều bản @opentelemetry/api trong node_modules, hoặc SDK khởi tạo hai lần |
npm ls @opentelemetry/api, ép về một version bằng overrides (npm) / resolutions (yarn) / pnpm.overrides; biến thể nhẹ hơn của cùng lỗi này là span biến mất im lặng vì instrumentation và SDK dùng hai bản api khác nhau |
Cảnh báo Accessing resource attributes before async attributes settled |
log SDK | Resource detector bất đồng bộ chưa xong tại thời điểm resource được đọc | Thường vô hại với span sinh ra sau đó, và các bản SDK gần đây đã bớt phát cảnh báo này khi chỉ đọc attribute đồng bộ; nếu cần chắc chắn thì await resource.waitForAsyncAttributes?.() trước khi đọc, hoặc thu hẹp detector bằng OTEL_NODE_RESOURCE_DETECTORS |
Log send_failed của Collector, metric queue chạm capacity |
log + metrics Collector | Chặng Collector → backend đang hỏng hoặc quá tải | Xem mục 11.5 |
11.11 Runbook: tìm trace cho một đơn hàng cụ thể
Kịch bản vận hành thường gặp nhất: CSKH báo đơn ORD-2026-887431 lỗi lúc 14:32, cần đúng trace của đơn đó. Làm theo thứ tự, dừng ở bước đầu tiên cho kết quả.
-
Bản ghi đơn hàng có cột
trace_idkhông? Có → mở thẳnghttp://jaeger:16686/trace/<trace_id>. Đây là đường chắc chắn nhất: tra theo trace ID không cần index tag nên vẫn ra kết quả cả khi Search trắng, và là cách duy nhất mở trace đã nằm trong archive storage (Phần 8). -
Không có → log có mang
trace_idkèm khoá nghiệp vụ không? (mẫu pino/winston ở Phần 5B). Grep log theo order id để lấytrace_id, rồi quay lại bước 1.# Loki logcli query '{app="checkout"} |= "ORD-2026-887431"' --limit 5 --since 24h # Elasticsearch curl -s 'http://es:9200/logs-*/_search?q=%22ORD-2026-887431%22&size=5&_source=trace_id,@timestamp'Trên Grafana, derived field của datasource Loki biến
trace_idtrong dòng log thành nút mở thẳng trace (Phần 5B) — bước 2 khi đó chỉ còn một cú click. -
Không có → Jaeger Search theo tag
hasaki.order.id=ORD-2026-887431, đặtLookbackbao trùm 14:32. Cảnh báo ngay tại đây: kết quả rỗng ở bước này không chứng minh trace không tồn tại. Lọc theo tag chỉ mạnh với Elasticsearch/OpenSearch, còn yếu với Cassandra; vàLimit Resultscắt theo trace mới nhất chứ không phải khớp nhất, nên trace cần tìm có thể bị cắt mà UI không báo gì (Phần 8). -
Vẫn không thấy → phân biệt ba nguyên nhân bằng cờ đã lưu, đừng debug nhầm hướng:
- Bản ghi đơn có cờ sampled (bit 0 của
spanContext().traceFlags— Phần 4) và cờ bằng0→ trace bị head-based sampling drop ngay từ app. Hệ thống không hỏng, không có gì để tìm. - Cờ bằng
1→ đối chiếu thời điểm đơn hàng với retention của storage (Cassandraschema.trace_ttlmặc định 48h; Elasticsearch theo rollover/ILM — Phần 8). Quá hạn thì trace đã bị xoá. - Cờ bằng
1và vẫn trong hạn → lúc này mới thực sự là mất span: sang mục 11.5.
- Bản ghi đơn có cờ sampled (bit 0 của
-
Chuẩn bị trước để lần sau dừng ở bước 1: cột
trace_idcùng cờ sampled trên bảng đơn hàng;trace_idtrong mọi dòng log; và một đường force-trace bật sẵn từ trước khi sự cố xảy ra (Phần 4).
Phần 12 — Anti-patterns và best practices
Bảng anti-pattern → hậu quả → cách làm đúng
| Anti-pattern | Hậu quả cụ thể | Cách làm đúng |
|---|---|---|
Chèn id vào tên span (GET /orders/8f3a-…) |
Cardinality vô hạn, Jaeger không gom được operation, spanmetrics nổ series | Tên span là template {method} {http.route}, id vào app.order.id — Phần 9 |
| Span cho mỗi vòng lặp hoặc mỗi hàm thuần tính toán | Hàng nghìn span một request, waterfall không đọc nổi | Chỉ tạo span ở ranh giới I/O và đơn vị nghiệp vụ; vòng lặp dùng attribute đếm app.items.count |
| Dùng Span Event thay log có cấu trúc, và ngược lại | Event chặn ở OTEL_SPAN_EVENT_COUNT_LIMIT (mặc định 128) và mất theo span khi sampling loại bỏ |
Event cho mốc vòng đời span (retry, cache miss), còn lại là log kèm trace_id/span_id; Logs SDK của JS còn ở mức Development |
| Ghi PII vào attribute (email, số điện thoại, địa chỉ) | Trace lưu nhiều ngày, cả tổ chức đọc được, không xóa chọn lọc được | Hash/tokenize tại nguồn, chặn lần hai bằng processor redaction (beta cho traces) hoặc transform — Phần 10 |
| Ghi cả body request/response vào span | OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT mặc định không giới hạn: payload phình, queue exporter đầy, span bị drop |
Chỉ ghi kích thước, số lượng, mã lỗi; cần trích đoạn thì siết spanLimits |
| Không end span trong nhánh lỗi | Span không bao giờ tới onEnd nên không được export, trace thiếu hẳn nhánh lỗi |
span.end() đặt trong finally |
recordException mà không set status ERROR |
Span vẫn UNSET: policy status_code của tail_sampling và dimension status.code của spanmetrics không bắt được |
Kèm setStatus({ code: SpanStatusCode.ERROR }); exception.escaped đã deprecated, exception.type/message/stacktrace vẫn Stable |
| Set status ERROR cho mọi HTTP 4xx | Error rate giả vì client gửi sai, alert kêu liên tục, SLO mất nghĩa | Semconv HTTP: 4xx MUST để UNSET ở SpanKind SERVER, SHOULD ERROR ở CLIENT; 5xx SHOULD ERROR cả hai. Ghi kèm error.type |
Bật toàn bộ instrumentation, kể cả fs |
instrumentation-fs sinh span cho từng thao tác file, khối lượng span gấp hàng chục lần |
Trong auto-instrumentations-node, instrumentation-fs và instrumentation-host-metrics mặc định đã tắt — đừng bật lại; tắt thêm qua OTEL_NODE_DISABLED_INSTRUMENTATIONS |
| Instrument health check và endpoint metrics | Probe Kubernetes chạy mỗi vài giây, chiếm phần lớn span và ăn hết ngân sách sampling | ignoreIncomingRequestHook của instrumentation-http, hoặc processor filter (alpha, cú pháp trace_conditions) |
| Export thẳng từ app lên SaaS, không qua Collector | Ở production: không có buffer/retry tập trung, credential nằm trong mọi pod, đổi backend phải build lại | App gửi OTLP tới Collector (4317/4318); Collector lo queue, retry, redaction, routing. Dev local/POC, một monolith một backend, hay job serverless lưu lượng nhỏ thì gửi thẳng vẫn hợp lệ — Phần 7 §7.1 |
SimpleSpanProcessor ở production |
Mỗi span export ngay trên đường đi của request, latency và số kết nối tăng | BatchSpanProcessor; Simple chỉ để debug local — ngoại lệ production duy nhất là serverless/AWS Lambda, nơi container bị đóng băng ngay khi handler return nên chu kỳ nền của Batch không bao giờ chạy (Phần 5A) |
Không dùng sampler ParentBased |
Mỗi service quyết định độc lập, trace vỡ mảnh còn vài span rời rạc | new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(r) }); TraceIdRatioBased vẫn Stable nhưng spec đã đánh dấu deprecated, SDK không đổi hành vi trước 01/01/2027 — Phần 4 |
| Ratio sampling khác nhau giữa các service | Suy rộng số liệu sai; lỡ bỏ ParentBased thì trace đứt tại biên giữa hai ratio | Một ratio thống nhất toàn hệ ở head-based, lọc chọn lọc đẩy về tail-based |
Tính RED metrics từ span sau tail_sampling |
Mẫu lệch hẳn về lỗi và request chậm, error rate và p99 sai có hệ thống | Đặt connector spanmetrics (alpha) trước tail_sampling, hoặc dùng SPM query-time của Jaeger — Phần 8 |
Không đặt service.name |
Resource rơi về unknown_service:node, mọi service dồn vào một mục trên Jaeger |
OTEL_SERVICE_NAME, hoặc ATTR_SERVICE_NAME trong resourceFromAttributes() truyền vào option resource của NodeSDK — Phần 5A |
service.name theo tên container ngẫu nhiên |
Mỗi pod thành một "service", danh sách phình vô hạn, dependency graph vô nghĩa | Tên cố định theo vai trò, instance để ở service.instance.id; bộ ba service.namespace + service.name + service.instance.id phải duy nhất toàn cục |
| Nhét thông tin cấp process (hostname, pod, version) vào span attribute | Lặp lại trên từng span, payload phình, không lọc được ở mức resource | resourceFromAttributes(...) cộng resourcedetection (beta) và k8sattributes (đã Stable) |
| Khai báo component trong Collector nhưng không đưa vào pipeline | Collector khởi động sạch, không báo lỗi, nhưng component không chạy | Mọi component phải có tên trong service::pipelines; đối chiếu bằng zpages /debug/pipelinez (cổng 55679) |
Không đặt memory_limiter đầu pipeline |
Burst traffic làm Collector OOMKill, mất toàn bộ dữ liệu đang trong queue | memory_limiter là processor đầu tiên, đặt GOMEMLIMIT khoảng 80% hard limit |
| Không xử lý shutdown/flush | SIGTERM lúc rolling update làm mất batch cuối, thường đúng là span của sự cố | await sdk.shutdown() trên SIGTERM/SIGINT rồi mới thoát — Phần 5A |
import './tracing' trong index.ts |
express/pg đã load xong trước khi instrumentation kịp vá module, không sinh span nào | CJS node --require ./instrumentation.js app.js; ESM node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import ./instrumentation.mjs app.js (Node ≥ 18.19.0), không lặp cờ trong NODE_OPTIONS — Phần 5A |
Nhiều bản @opentelemetry/api trong node_modules |
Error: @opentelemetry/api: Attempted duplicate registration of API: trace, hoặc span mất im lặng |
npm ls @opentelemetry/api, ép về một version bằng overrides/resolutions; package nội bộ khai báo api là peerDependency |
| Dùng baggage cho dữ liệu lớn hoặc bí mật | Baggage đi kèm mọi request ra ngoài, kể cả sang bên thứ ba; W3C chỉ bảo đảm propagate khi ≤ 64 list-member và ≤ 8192 byte | Chỉ mang khóa định tuyến ngắn (tenant, experiment), không bao giờ token hay PII — Phần 3 |
| Coi trace là thay thế cho log và metrics | Không có metrics thì không alert theo SLO; trace đã sampling nên không dùng để đếm | Metrics để alert, trace để định vị, log để đọc chi tiết, nối bằng trace_id |
| Bật tracing 100% rồi bất ngờ về hóa đơn | Chi phí ingest và lưu trữ tăng tuyến tính theo số span và số byte attribute | Ước lượng span/s × byte/span trước khi bật; ratio head-based thấp cộng tail-based giữ lỗi — Phần 4, Phần 10 |
| Không có alert cho chính pipeline observability | Collector drop span âm thầm, đến lúc sự cố mới biết không có dữ liệu | Scrape internal telemetry cổng 8888; alert otelcol_exporter_send_failed_spans, otelcol_processor_refused_spans, otelcol_exporter_queue_size so với otelcol_exporter_queue_capacity |
| Dùng tên attribute cũ của semconv trong dashboard mới | http.method, http.status_code, db.statement đã deprecated, panel trắng khi nâng instrumentation |
Dùng http.request.method, http.response.status_code, db.query.text; roll out bằng OTEL_SEMCONV_STABILITY_OPT_IN=http/dup rồi cắt sang http — Phần 9 |
Cấu hình exporter jaeger trong Collector |
Exporter này đã bị gỡ khỏi opentelemetry-collector-contrib, Collector không khởi động được | Exporter otlp trỏ tới Jaeger cổng 4317, vì Jaeger nhận OTLP native |
Chạy image jaegertracing/all-in-one:1.x cho hệ thống thật |
Jaeger v1 end-of-life 31/12/2025, v2 không còn image all-in-one; memory mất sạch khi restart, badger chỉ chạy một node |
Image cr.jaegertracing.io/jaegertracing/jaeger, chọn role bằng file config, storage Elasticsearch/OpenSearch hoặc Cassandra — Phần 8 |
| Attribute cardinality cao làm dimension của spanmetrics | user_id/order_id thành label Prometheus, nổ series và Prometheus OOM |
Giữ dimension mặc định (service.name, span.name, span.kind, status.code, collector.instance.id), chỉ thêm dimension có tập giá trị hữu hạn |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://collector:4318 |
Biến này dùng nguyên văn, SDK POST vào / và nhận 404, không span nào tới nơi |
Ghi đủ http://collector:4318/v1/traces, hoặc dùng OTEL_EXPORTER_OTLP_ENDPOINT để SDK tự nối path — Phần 6 |
10 quy tắc vàng
- Tên span là template ổn định, không bao giờ chứa id — id thuộc về attribute.
- Mỗi span
end()trongfinally, và status chỉ ERROR khi thực sự hỏng. - Cái gì đúng cho cả process thì đặt ở Resource, không lặp trên từng span.
service.namecố định theo vai trò, danh tính instance để ởservice.instance.id.- Production thì không export thẳng từ app lên backend, đi qua Collector; dev local/POC và job serverless lưu lượng nhỏ là ngoại lệ hợp lệ (Phần 7 §7.1).
- Production mặc định là
BatchSpanProcessorcộng shutdown/flush có kiểm soát. ParentBasedvà một ratio thống nhất toàn hệ; lọc thông minh làm ở tail-based.- Tính RED metrics trước sampling, không bao giờ sau.
memory_limiterđứng đầu mọi pipeline, và pipeline observability phải có alert riêng.- Bám semantic conventions Stable; attribute tự đặt dùng namespace
app.*và cardinality hữu hạn.
Review checklist khi merge code có instrumentation
- [ ] Không tên span nào chứa id, UUID, email hay query string.
- [ ] Mọi
startSpan/startActiveSpanđều cóend()trongfinally. - [ ] Nhánh
catchcó cảrecordExceptionvàsetStatusERROR; 4xx trên SERVER span không bị đánh ERROR. - [ ] Attribute mới đã có trong semconv Stable chưa; nếu tự đặt thì có tiền tố
app.và tập giá trị hữu hạn. - [ ] Không có PII, token, body request/response trong attribute lẫn baggage.
- [ ] Instrumentation vừa bật đã được ước lượng khối lượng span (fs, health check, vòng lặp retry).
- [ ] Thay đổi sampler/ratio đã đồng bộ với các service khác và vẫn giữ
ParentBased. - [ ] Config Collector: component có mặt trong
service::pipelines,memory_limitervẫn ở đầu. - [ ] Thay đổi dimension của spanmetrics đã cân nhắc về số series Prometheus.
- [ ] Lệnh chạy app vẫn dùng
--require/--import, vànpm ls @opentelemetry/apichỉ ra đúng một version.
Phần 13 — Cheatsheet, glossary và tài liệu tham khảo
Bảng biến môi trường OTEL_*
Chỉ ghi default khi spec chốt. Cơ chế OTLP xem Phần 6, sampling xem Phần 4.
Chung (SDK)
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_SERVICE_NAME |
Đặt service.name; thắng service.name khai trong OTEL_RESOURCE_ATTRIBUTES. |
checkout-api |
(trống) |
OTEL_RESOURCE_ATTRIBUTES |
Attribute resource bổ sung, dạng k=v,k=v. |
deployment.environment.name=prod,service.version=1.4.2 |
(trống) |
OTEL_SDK_DISABLED |
Tắt SDK cho mọi signal. Không tác động tới propagator khai ở OTEL_PROPAGATORS. |
true |
false |
OTEL_LOG_LEVEL |
Mức log nội bộ của SDK. | debug |
info |
OTEL_TRACES_EXPORTER |
otlp, zipkin, console, none; logging đã deprecated, jaeger không còn hợp lệ. |
otlp |
otlp |
OTEL_CONFIG_FILE |
File declarative config (Development); khi đặt thì thắng các env var khác. Tên cũ OTEL_EXPERIMENTAL_CONFIG_FILE đã deprecated. |
./otel.yaml |
(trống) |
Exporter OTLP
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_EXPORTER_OTLP_PROTOCOL (và ..._TRACES_PROTOCOL) |
Encoding: grpc, http/protobuf, http/json. |
grpc |
http/protobuf |
OTEL_EXPORTER_OTLP_ENDPOINT |
Base URL; SDK tự nối /v1/traces. |
http://collector:4318 |
http://localhost:4318 (HTTP), http://localhost:4317 (gRPC) |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Dùng nguyên văn, không nối path — phải ghi đủ /v1/traces. |
http://collector:4318/v1/traces |
http://localhost:4318/v1/traces (HTTP), http://localhost:4317 (gRPC) |
OTEL_EXPORTER_OTLP_HEADERS (và ..._TRACES_HEADERS) |
Header auth, định dạng W3C Baggage k=v,k=v. |
api-key=xxxx,tenant=web |
(không có) |
OTEL_EXPORTER_OTLP_COMPRESSION |
gzip hoặc none. |
gzip |
(không nén) |
OTEL_EXPORTER_OTLP_TIMEOUT |
Timeout mỗi lần export, ms. | 5000 |
10000 |
OTEL_EXPORTER_OTLP_INSECURE |
Chỉ áp dụng cho OTLP/gRPC khi endpoint không có scheme http/https. |
true |
false |
OTEL_EXPORTER_OTLP_CERTIFICATE, _CLIENT_KEY, _CLIENT_CERTIFICATE |
mTLS tới Collector. | /etc/certs/ca.pem |
(không có) |
Sampler
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_TRACES_SAMPLER |
always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio, jaeger_remote, parentbased_jaeger_remote, xray. |
parentbased_traceidratio |
parentbased_always_on |
OTEL_TRACES_SAMPLER_ARG |
Tham số sampler; với traceidratio là số trong [0..1]. |
0.05 |
(trống); traceidratio coi như 1.0 |
Giá trị sai hoặc lạ: spec yêu cầu SDK ghi log rồi bỏ qua, coi như không đặt.
Propagator
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_PROPAGATORS |
Danh sách propagator, phân cách dấu phẩy; giá trị trùng bị khử. | tracecontext,baggage,b3multi |
tracecontext,baggage |
Giá trị hợp lệ: tracecontext, baggage, b3, b3multi, xray, none; jaeger và ottrace đã deprecated; riêng xray/ottrace cần package @opentelemetry/auto-configuration-propagators mới chạy qua env (Phần 3).
Span limits
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT |
Attribute tối đa mỗi span. | 64 |
128 |
OTEL_SPAN_EVENT_COUNT_LIMIT |
Event tối đa mỗi span. | 32 |
128 |
OTEL_SPAN_LINK_COUNT_LIMIT |
Link tối đa mỗi span. | 32 |
128 |
OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT |
Attribute tối đa mỗi event. | 32 |
128 |
OTEL_LINK_ATTRIBUTE_COUNT_LIMIT |
Attribute tối đa mỗi link. | 32 |
128 |
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT |
Cắt độ dài giá trị attribute của span. | 2048 |
no limit |
OTEL_ATTRIBUTE_COUNT_LIMIT / OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT |
Giới hạn chung, bị bản SPAN_ ghi đè khi cả hai cùng đặt. |
128 / 4096 |
128 / no limit |
Batch span processor
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_BSP_SCHEDULE_DELAY |
Chu kỳ flush, ms. | 2000 |
5000 |
OTEL_BSP_EXPORT_TIMEOUT |
Timeout một lần export, ms. | 10000 |
30000 |
OTEL_BSP_MAX_QUEUE_SIZE |
Hàng đợi span chờ export; đầy thì span bị drop im lặng. | 4096 |
2048 |
OTEL_BSP_MAX_EXPORT_BATCH_SIZE |
Số span mỗi batch, phải <= max queue size. | 512 |
512 |
Node distro (@opentelemetry/auto-instrumentations-node)
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_NODE_RESOURCE_DETECTORS |
env, host, os, process, serviceinstance, container, alibaba, aws, azure, gcp, all, none; thứ tự được tôn trọng. |
env,host,os,container |
bật tất cả |
OTEL_NODE_ENABLED_INSTRUMENTATIONS |
Tên không có tiền tố @opentelemetry/instrumentation-, phân cách dấu phẩy. |
http,express,pg,ioredis |
(không đặt) |
OTEL_NODE_DISABLED_INSTRUMENTATIONS |
Áp dụng sau ENABLED; tên có ở cả hai biến sẽ bị TẮT. | net,dns |
(không đặt) |
Trong gói meta chỉ hai instrumentation bị tắt sẵn: @opentelemetry/instrumentation-fs (quá ồn) và @opentelemetry/instrumentation-host-metrics.
Semconv stability
| Biến | Ý nghĩa | Ví dụ | Default |
|---|---|---|---|
OTEL_SEMCONV_STABILITY_OPT_IN |
Danh sách phân cách dấu phẩy: http, http/dup, database, database/dup, messaging, messaging/dup, rpc, rpc/dup. Giá trị trần = chỉ phát bộ stable; hậu tố /dup = phát cả hai bộ. |
http/dup,database/dup |
không đặt = tiếp tục phát bộ cũ (experimental) |
Cheatsheet API OpenTelemetry JS
Tất cả import từ @opentelemetry/api. Cách dùng thực chiến xem Phần 5B.
| Ký hiệu | Dùng để làm gì | Lưu ý |
|---|---|---|
trace.getTracer(name, version?) |
Lấy tracer, đồng thời khai instrumentation scope. | name là tên module, không phải tên service. |
tracer.startActiveSpan(name, [opts], fn) |
Tạo span và set làm active context trong callback. | Mặc định dùng cái này; vẫn phải tự gọi span.end(). |
tracer.startActiveSpan(name, opts, ctx, fn) |
Overload 4 tham số: chỉ định parent context tường minh thay vì lấy context đang active. | Dùng khi parent đến từ propagation.extract(...) hoặc từ một context đã cất sẵn. |
tracer.startSpan(name, opts?, ctx?) |
Tạo span nhưng không set active. | Dùng cho span chạy song song hoặc bắc cầu qua nhiều callback. |
{ root: true } trong SpanOptions |
Luôn mở trace mới, bỏ qua mọi parent đang active. | Cho cron job và webhook/callback từ bên ngoài — traceparent của đối tác là input không tin cậy (Phần 3). |
trace.getActiveSpan() |
Lấy span đang active để gắn thêm attribute. | Trả undefined khi ngoài context — luôn kiểm tra trước. |
trace.getSpan(ctx) / trace.getSpanContext(ctx) |
Lấy span, hoặc chỉ SpanContext, ra khỏi một context bất kỳ. |
getSpanContext là thứ dùng để dựng Link sau khi extract từ header message (Phần 5B). |
trace.setSpan(ctx, span) |
Trả context mới có span đó làm active. | Ghép với context.with khi chọn parent thủ công. |
trace.deleteSpan(ctx) |
Trả context mới đã gỡ span active ra. | Dùng khi cố ý cắt quan hệ cha-con cho một nhánh chạy nền. |
trace.wrapSpanContext(sc) |
Bọc một SpanContext thành span non-recording. |
Khi chỉ còn dữ liệu định danh (đọc từ DB, từ header) mà không còn span thật để làm parent hoặc link. |
context.active() |
Lấy context hiện tại. | Trên Node dựa vào AsyncLocalStorage. |
context.with(ctx, fn) |
Chạy fn bên trong context chỉ định. |
Cách chuẩn để nối lại context bị đứt (Phần 11). |
context.bind(ctx, target) |
Đóng băng context vào một hàm hoặc EventEmitter trước khi đưa nó ra ngoài phạm vi hiện tại. |
Cách vá chuẩn cho listener đăng ký sớm và callback do thư viện ngoài gọi lại (Phần 2, Phần 3). |
ROOT_CONTEXT |
Context rỗng. | Dùng khi extract mà cố ý bỏ qua mọi context đang active — điển hình là consumer message (Phần 5B). |
createContextKey(name) |
Tạo key riêng để cất giá trị của bạn vào context. | Giá trị đi theo đúng luồng thực thi như span active, nhưng không qua được ranh giới process — muốn thế thì dùng baggage. |
span.isRecording() |
Kiểm tra span có đang ghi hay không. | Bọc quanh phần tính attribute tốn kém: sampler trả NOT_RECORD thì span là non-recording, mọi setAttribute đều bị bỏ đi. |
span.setAttribute(k, v) / setAttributes(obj) |
Gắn attribute vào span. | Chỉ nhận string/number/boolean và mảng đồng kiểu; tránh cardinality cao. |
span.addEvent(name, attrs?, time?) |
Đánh dấu một mốc thời điểm trong span. | Rẻ hơn span con nhưng không có duration. |
span.addLink(link) / span.addLinks(links) |
Thêm link sau khi span đã bắt đầu. | Có từ @opentelemetry/api 1.9.0; link thêm sau không đổi được sampling, nên ưu tiên truyền links lúc startSpan. |
span.recordException(err) |
Ghi exception thành event exception.type / exception.message / exception.stacktrace. |
Không tự đổi status; phải gọi thêm setStatus. |
span.setStatus({ code, message }) |
Đặt trạng thái span. | Chỉ set ERROR cho lỗi thật, không set cho 4xx do client sai (Phần 12). |
span.updateName(name) |
Đổi tên span sau khi biết route thật. | Sampler head-based đã chạy trước nên không đổi được quyết định sampling. |
span.end(endTime?) |
Kết thúc span, đẩy sang SpanProcessor. | Quên end() = span không bao giờ xuất hiện, kèm rò bộ nhớ. |
propagation.inject(ctx, carrier, setter?) |
Ghi traceparent / tracestate / baggage vào carrier. |
Cần khi tự viết HTTP client hoặc producer message (Phần 3). |
propagation.extract(ctx, carrier, getter?) |
Đọc header vào một context mới. | Phải bọc tiếp bằng context.with(...), nếu không vẫn mất parent. |
propagation.createBaggage(entries) / setBaggage(ctx, bag) / getBaggage(ctx) |
Tạo, gắn vào context và đọc baggage. | Baggage không tự thành span attribute; muốn có thì tự copy trong một SpanProcessor (Phần 2, Phần 3). |
SpanStatusCode |
Enum UNSET, OK, ERROR. |
UNSET là mặc định và hoàn toàn hợp lệ. |
SpanKind |
Enum INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER. |
Đặt sai kind làm hỏng service graph (Phần 1). |
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG) |
Bật log nội bộ SDK khi debug. | Rất ồn — chỉ bật tạm lúc troubleshoot (Phần 11). |
Package @opentelemetry/* hay dùng
| Package | Công dụng |
|---|---|
@opentelemetry/api |
API tracing gọi trong code nghiệp vụ; đánh version riêng, chỉ được có một bản trong node_modules. |
@opentelemetry/sdk-node |
Bootstrap gọn: NodeSDK với start() / shutdown(). Dòng experimental 0.x. |
@opentelemetry/auto-instrumentations-node |
Gói meta bật hàng loạt instrumentation, kèm entry /register cho zero-code. |
@opentelemetry/sdk-trace-base, @opentelemetry/sdk-trace-node |
BasicTracerProvider / NodeTracerProvider, BatchSpanProcessor, các sampler. Dòng stable 2.x. |
@opentelemetry/resources |
resourceFromAttributes(), defaultResource(), emptyResource() — class Resource không còn được export ở 2.x. |
@opentelemetry/semantic-conventions |
Hằng ATTR_* cho convention stable; convention chưa ổn định nằm ở subpath /incubating, không theo semver, có thể breaking ở bản minor. |
@opentelemetry/exporter-trace-otlp-http / -proto / -grpc |
Ba exporter OTLP tương ứng http/json, http/protobuf, grpc. Đều thuộc dòng experimental. |
@opentelemetry/instrumentation |
Base class để tự viết instrumentation, kèm loader hook ESM hook.mjs. |
@opentelemetry/context-async-hooks |
Context manager dựa trên AsyncLocalStorage. |
@opentelemetry/propagator-b3, @opentelemetry/propagator-jaeger |
Propagator cho hệ thống cũ dùng header B3 hoặc uber-trace-id. |
Lệnh hay dùng
| Việc cần làm | Lệnh |
|---|---|
| Chạy app CJS với file telemetry riêng | node --require ./dist/tracing.js dist/index.js |
| Zero-code, CJS | node --require @opentelemetry/auto-instrumentations-node/register app.js |
| Zero-code, ESM (Node >= 18.19.0) | node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import @opentelemetry/auto-instrumentations-node/register app.js |
| TypeScript chưa build, ESM (Node >= 20) | npx tsx --import ./src/tracing.ts src/index.ts |
Soát trùng bản @opentelemetry/api |
npm ls @opentelemetry/api (hoặc pnpm why, yarn why) |
| Dựng stack Jaeger + Collector | docker compose up -d rồi docker compose logs -f otel-collector |
| Jaeger v2 một container | docker run --rm -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 5778:5778 -p 9411:9411 cr.jaegertracing.io/jaegertracing/jaeger:2.20.0 |
| Sinh trace thử | telemetrygen traces --otlp-endpoint localhost:4317 --otlp-insecure --traces 10 (cài: go install github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@latest) |
| Health check Collector | curl -s http://localhost:13133/ — extension health_check; khỏe thì HTTP 200 với body kiểu {"status":"Server available","upSince":...,"uptime":...}, hỏng thì 503. Jaeger v2: curl -s http://localhost:13133/status. |
| Metric nội bộ Collector | curl -s http://localhost:8888/metrics \| grep otelcol_exporter_sent_spans (đổi tên để xem otelcol_receiver_accepted_spans, otelcol_exporter_send_failed_spans, otelcol_exporter_queue_size) |
| Xem pipeline đang chạy | curl -s http://localhost:55679/debug/pipelinez (extension zpages) |
Gửi một span thử bằng OTLP http/json — traceId là 32 ký tự hex, spanId 16 hex (không phải base64), kind: 2 là SERVER. Mốc thời gian phải lấy theo giờ hiện tại: hard-code một giá trị cũ thì backend vẫn trả 200 nhưng span rơi ngoài khung Lookback mặc định của UI, tìm mãi không ra (Phần 6):
NOW=$(date +%s)
curl -i http://localhost:4318/v1/traces \
-H 'Content-Type: application/json' \
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"curl-test"}}]},"scopeSpans":[{"scope":{"name":"manual"},"spans":[{"traceId":"4bf92f3577b34da6a3ce929d0e0e4736","spanId":"00f067aa0ba902b7","name":"GET /health","kind":2,"startTimeUnixNano":"'"${NOW}"'000000000","endTimeUnixNano":"'"${NOW}"'500000000"}]}]}]}'
HTTP 200 kèm partial_success có rejectedSpans > 0 nghĩa là backend đã nhận nhưng bỏ bớt — client không được retry request đó. Chỉ 429, 502, 503, 504 mới được retry; 500 thì không.
Đối chiếu nhanh port
| Port | Thành phần | Giao thức / đường dẫn |
|---|---|---|
| 4317 | Collector; Jaeger v2 (Stable) | OTLP/gRPC |
| 4318 | Collector; Jaeger v2 (Stable) | OTLP/HTTP, path /v1/traces |
| 16686 | Jaeger v2 | UI + HTTP query API |
| 16685 | Jaeger v2 query API (Stable) | gRPC jaeger.api_v3.QueryService |
| 5778 | Jaeger v2 remote sampling (Stable) | HTTP /sampling?service=<name> |
| 5779 | Jaeger v2 remote sampling (Stable) | gRPC jaeger.api_v2.SamplingManager |
| 13133 | Collector extension health_check (alpha, đã có bản kế nhiệm healthcheckv2); Jaeger v2 dùng healthcheckv2 |
HTTP / (Jaeger v2: /status) |
| 1777 | Collector extension pprof (beta); Jaeger v2 |
HTTP /debug/pprof/* |
| 55679 | Collector extension zpages (beta) |
HTTP /debug/servicez, /debug/pipelinez, /debug/tracez |
| 8888 | Internal telemetry của Collector và của Jaeger v2 | HTTP Prometheus /metrics |
| 8889 | Jaeger v2: metrics của role ingester. Ở Collector đây thường là port người dùng tự đặt cho exporter prometheus, không phải default. |
HTTP Prometheus |
| 9411 | Zipkin receiver của Collector; Zipkin endpoint của Jaeger v2 | HTTP /api/v2/spans |
| 14268 | Jaeger /api/traces — Deprecated; vẫn là default của thrift_http trong receiver jaeger (core/contrib/k8s, beta) |
HTTP Thrift |
| 14250 | Jaeger jaeger.api_v2.CollectorService — Deprecated; vẫn là default của grpc trong receiver jaeger |
gRPC |
| 6831 / 6832 | Jaeger Thrift compact / binary — Deprecated; default của thrift_compact / thrift_binary trong receiver jaeger |
UDP |
Cổng 14269 là admin port của Jaeger v1 (EOL 31/12/2025), không có ở v2.
Glossary Việt - Anh
| Thuật ngữ | Ý nghĩa ngắn | Nói kỹ ở |
|---|---|---|
| trace | Hành trình một request qua các service, gom bằng chung trace_id. |
Phần 1 |
| span | Một đơn vị công việc có tên, mốc bắt đầu/kết thúc, attribute, status. | Phần 1 |
| root span | Span không có parent, mở đầu một trace. | Phần 1 |
| child span | Span có parent nằm trong cùng trace. | Phần 1 |
| parent | Span cha, xác định qua parent_span_id; mỗi span chỉ có đúng một. |
Phần 1 |
| SpanContext | Bộ định danh truyền đi được: trace_id, span_id, trace_flags, trace_state. |
Phần 1, 3 |
| SpanKind | Vai trò span: INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER. | Phần 1, 9 |
| attribute | Cặp key-value mô tả span hoặc resource, dùng để lọc và nhóm. | Phần 1, 9 |
| event | Mốc thời điểm có tên bên trong span, không có duration. | Phần 1 |
| link | Tham chiếu sang span khác ngoài quan hệ cha-con (batch, fan-out). | Phần 1, 9 |
| status | Trạng thái span: UNSET, OK, ERROR. | Phần 1, 12 |
| resource | Tập attribute mô tả thực thể phát telemetry (service.name, service.version). |
Phần 1, 9 |
| instrumentation scope | Tên và version của thư viện tạo span, khai lúc getTracer. |
Phần 1, 5B |
| tracer | Đối tượng tạo span. | Phần 2 |
| TracerProvider | Nhà máy tạo tracer; giữ resource, sampler, span processor. | Phần 2, 5A |
| SpanProcessor | Hook nhận span lúc bắt đầu và kết thúc; BatchSpanProcessor gom rồi đẩy sang exporter. |
Phần 2, 5A |
| exporter | Thành phần gửi span ra ngoài (OTLP, console). | Phần 2, 6 |
| sampler | Bộ quyết định span nào được ghi và gửi đi. | Phần 4 |
| head-based sampling | Quyết định ngay tại root span, trước khi biết kết quả request. | Phần 4 |
| tail-based sampling | Quyết định sau khi trace gần đủ, chạy ở Collector; giữ được trace lỗi và trace chậm. | Phần 4, 7 |
| adjusted count | Hệ số nhân để suy ra số lượng thật từ phần đã sampling. | Phần 4 |
| context | Kho key-value theo luồng thực thi, chứa span active và baggage. | Phần 2, 3 |
| context propagation | Truyền context qua ranh giới process bằng header. | Phần 3 |
| propagator | Bộ đọc/ghi context vào carrier (tracecontext, baggage, b3). |
Phần 3 |
| baggage | Cặp key-value nghiệp vụ đi kèm request; không tự thành attribute. | Phần 3 |
| OTLP | Giao thức chuẩn của OpenTelemetry: grpc, http/protobuf, http/json. |
Phần 6 |
| Collector | Tiến trình trung gian nhận, xử lý, chuyển tiếp telemetry. | Phần 7 |
| receiver | Đầu vào của Collector (otlp, jaeger, zipkin). |
Phần 7 |
| processor | Khâu biến đổi hoặc lọc dữ liệu trong pipeline Collector. | Phần 7 |
| connector | Thành phần nối hai pipeline, vừa là exporter vừa là receiver (spanmetrics, servicegraph, routing). |
Phần 7 |
| extension | Thành phần phụ trợ nằm ngoài pipeline dữ liệu (health_check, pprof, zpages). |
Phần 7 |
| pipeline | Chuỗi receivers → processors → exporters cho một signal. | Phần 7 |
| semantic conventions | Bộ tên attribute và quy tắc đặt tên chuẩn hóa. | Phần 9 |
| cardinality | Số giá trị phân biệt của một attribute; cao thì tốn lưu trữ và nổ metric. | Phần 9, 10 |
| backpressure | Đầu ra chậm hơn đầu vào, làm đầy queue rồi drop dữ liệu. | Phần 7, 11 |
| flush | Đẩy ngay toàn bộ span đang nằm trong buffer đi. | Phần 5A, 11 |
| shutdown | Dừng SDK có trật tự: flush nốt rồi đóng exporter. | Phần 5A, 11 |
| self time | Thời gian span tự tiêu tốn, sau khi trừ phần các span con chiếm. | Phần 8 |
| service graph | Đồ thị phụ thuộc giữa các service, dựng từ cặp span CLIENT/SERVER. | Phần 7, 8 |
| exemplar | Con trỏ từ một điểm metric sang trace_id cụ thể. |
Phần 10 |
| N+1 query | Anti-pattern lộ ra khi thấy hàng loạt span DB gần giống nhau trong một request. | Phần 8, 11 |
Tài liệu tham khảo chính thức
Spec
- https://opentelemetry.io/docs/specs/otel/ — ngữ nghĩa chuẩn của trace, span, SDK, sampler; mở khi hai ngôn ngữ hành xử khác nhau.
- https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/ — env var chuẩn kèm default; mở khi cần chốt một giá trị mặc định.
- https://opentelemetry.io/docs/specs/otlp/ — luật OTLP: encoding, retry, partial success.
- https://github.com/open-telemetry/opentelemetry-proto — định nghĩa protobuf; mở khi tự viết client hoặc soi payload.
- https://opentelemetry.io/docs/specs/semconv/ — spec semconv; mở khi cần biết một nhóm attribute đang Stable hay Development.
Hướng dẫn JavaScript
- https://opentelemetry.io/docs/languages/js/ — trang gốc cho SDK JS.
- https://opentelemetry.io/docs/languages/js/instrumentation/ — tạo span thủ công; mở khi viết instrumentation trong code nghiệp vụ.
- https://github.com/open-telemetry/opentelemetry-js — source và CHANGELOG; mở khi cần biết một API xuất hiện từ bản nào.
- https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/esm-support.md — cờ chạy ESM; mở khi app dưới ESM không sinh span nào.
- https://github.com/open-telemetry/opentelemetry-js-contrib — instrumentation theo thư viện (express, pg, ioredis, kafkajs).
Collector
- https://opentelemetry.io/docs/collector/ — kiến trúc, cài đặt, các distribution chính thức.
- https://opentelemetry.io/docs/collector/configuration/ — cú pháp
receivers/processors/exporters/service. - https://opentelemetry.io/docs/collector/extend/ocb/ — build distro riêng bằng OpenTelemetry Collector Builder (URL cũ
/docs/collector/custom-collector/đã 404). - https://github.com/open-telemetry/opentelemetry-collector-contrib — README từng component: tham số, stability, distro nào có sẵn. Mở trước khi copy bất kỳ config nào.
Jaeger
- https://www.jaegertracing.io/docs/latest/ — tài liệu v2; mở khi cấu hình storage hoặc chọn role.
- https://www.jaegertracing.io/docs/latest/apis/ — bảng cổng và trạng thái API, cái nào đã Deprecated.
- https://github.com/jaegertracing/jaeger — issue và release notes; mở khi nghi ngờ một hành vi là bug.
W3C
- https://www.w3.org/TR/trace-context/ — định dạng
traceparentvàtracestate; mở khi debug header giữa các service. - https://www.w3.org/TR/baggage/ — cú pháp và giới hạn của header
baggage.
Registry semconv
- https://opentelemetry.io/docs/specs/semconv/registry/attributes/ — tra tên attribute chuẩn trước khi tự đặt tên mới.
- https://github.com/open-telemetry/semantic-conventions — CHANGELOG các bản semconv và mapping attribute cũ → mới.
CNCF và cộng đồng
- https://slack.cncf.io/ — các kênh
#otel-js,#otel-collector,#jaegerđể hỏi trực tiếp maintainer. - https://github.com/open-telemetry/community — lịch họp SIG và cách tham gia nhóm theo ngôn ngữ.
Đường học tiếp
- Metrics và Logs của OpenTelemetry: cùng resource, cùng Collector, ghép ba signal để đi từ biểu đồ sang đúng trace. Trong JS, Metrics đã Stable còn Logs vẫn ở mức Development.
- Exemplar: nối một điểm metric với
trace_id, biến dashboard RED thành điểm khởi đầu để mở trace chậm. - Grafana Tempo và TraceQL: lưu trace giá rẻ trên object storage, truy vấn bằng ngôn ngữ giàu hơn UI Jaeger.
- Continuous profiling và eBPF profiling: bù phần trace không thấy được — CPU và memory bên trong một span dài.
- OpenTelemetry Operator nâng cao: auto-instrumentation injection,
InstrumentationCRD, target allocator trên Kubernetes. - OpenTelemetry Profiles (Alpha) và distro
otelcol-ebpf-profiler: theo dõi tiến độ trước khi cân nhắc đưa vào production. - Declarative configuration qua
OTEL_CONFIG_FILE(Development): gom cấu hình SDK vào một file YAML thay cho hàng chục env var.
All rights reserved