OpenTelemetry trong .NET: trace, metric, log
Cách cấu hình OpenTelemetry vào ASP.NET Core cho distributed trace, metric, và structured log. Pipeline instrumentation duy nhất chạy mọi alert trong service.
Mục lục
- Khi nào observability không thể thương lượng nữa?
- Ngân sách số nào cho tier observability?
- Pipeline observability tối thiểu trông thế nào?
- Cấu hình .NET 10 cho OpenTelemetry?
- Trace và metric tuỳ chỉnh nào nên thêm?
- Biến observability thành alert hữu dụng ra sao?
- Bản thân observability tạo failure mode nào?
- Khi nào đầu tư observability là sớm?
- Đi tiếp đâu từ đây?
Lần đầu phải debug sự cố production mà không có observability, bạn hiểu vì sao mọi chương trong series đều trích metric. Không trace, metric, log, bạn đoán mò. Có chúng, post-mortem tự viết. Chương này cấu hình OpenTelemetry vào ASP.NET Core theo hình đã thành chuẩn 2026.
Khi nào observability không thể thương lượng nữa?
Ba tín hiệu.
Service ở production. Dù chỉ một khách hàng, outage tốn danh tiếng. Không metric thì không alert; không trace thì không sửa; không log thì không giải thích.
Service có hơn một instance. Log file local trên mỗi máy hết chạy ngay khi request trải qua replica. Cần pipeline tập trung.
Service phụ thuộc service khác. Checkout chậm hoá ra do provider thanh toán third-party chậm mất nhiều giờ chẩn đoán nếu không có distributed trace, vài phút khi có.
Nếu code là script một lần chạy và bạn đọc console, không cần chương này. Còn lại, có.
Ngân sách số nào cho tier observability?
Tín hiệu Khối lượng/req Yếu tố chi phí Storage
Metric ~hằng số số label unique rẻ
Trace 1 trace tỉ lệ sampling đắt
Log ~5-20 dòng kích thước + số dòng vừa
Cho service 10K QPS:
- Metric: vài nghìn series x scrape 30s = không đáng kể.
- Trace 100% = 36M trace/giờ. Sampling 1% = 360K, dễ chịu hơn.
- Log 10/req = 100K log/s. Log structured 200 byte = 20 MB/s = 1.7 TB/ngày. Lọc và sample.
Đừng log mức INFO trên path nóng. WARN trở lên cho steady state; DEBUG chỉ khi điều tra.
Pipeline observability tối thiểu trông thế nào?
flowchart LR
App[ASP.NET Core] -->|OTel SDK| Collector[OTel Collector]
Collector -->|metric| Prom[(Prometheus)]
Collector -->|trace| Jaeger[(Jaeger / Tempo)]
Collector -->|log| Loki[(Loki / OpenSearch)]
Prom --> Grafana
Jaeger --> Grafana
Loki --> Grafana
Grafana --> Alert[Alert -> PagerDuty]
App emit qua OTel; Collector demultiplex theo loại tín hiệu vào backend chuyên dụng; Grafana là dashboard thống nhất. Cùng pipeline hỗ trợ Datadog, Honeycomb, Azure Monitor - đổi backend không đụng code app.
Cấu hình .NET 10 cho OpenTelemetry?
Một block trong Program.cs:
builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService(
serviceName: builder.Environment.ApplicationName,
serviceVersion: Assembly.GetExecutingAssembly().GetName().Version?.ToString()))
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("MyService.*")
.SetSampler(new TraceIdRatioBasedSampler(0.01)) // 1% head-based
.AddOtlpExporter())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation() // GC, thread pool
.AddProcessInstrumentation()
.AddMeter("MyService.*")
.AddOtlpExporter());
// Log qua Serilog với OTel sink
builder.Host.UseSerilog((ctx, lc) => lc
.ReadFrom.Configuration(ctx.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.OpenTelemetry(o => o.Endpoint = "http://collector:4317"));
Ba chi tiết. Instrumentation ASP.NET Core mặc định cho bạn cả phương pháp RED không cần code. Instrumentation EF Core gồm SQL - lọc PII trước khi export. Sampling 1% head-based là default tiết kiệm nhất; kết hợp với tail sampling trên error cho trace error giàu hơn.
Trace và metric tuỳ chỉnh nào nên thêm?
Ba loại đáng công:
// Span tuỳ chỉnh quanh thao tác nghiệp vụ
using var activity = ActivitySource.StartActivity("checkout.process");
activity?.SetTag("order.id", orderId);
activity?.SetTag("order.amount", amount);
// ... code nghiệp vụ
// Metric tuỳ chỉnh cho KPI nghiệp vụ
private static readonly Meter Meter = new("MyService.Business");
private static readonly Counter<long> OrdersPlaced =
Meter.CreateCounter<long>("orders_placed_total");
OrdersPlaced.Add(1, new KeyValuePair<string, object?>("payment_method", "stripe"));
// Log structured với correlation
log.LogInformation(
"Order {OrderId} placed by user {UserId} for {Amount:0.00}",
orderId, userId, amount);
ActivitySource gắn span tuỳ chỉnh vào cây trace tự động; Meter
lộ counter để Prometheus scrape. Log structured giữ correlation ID
ASP.NET Core inject - Grafana có thể nhảy từ trace sang log của nó.
Biến observability thành alert hữu dụng ra sao?
Ba quy tắc.
Một: alert theo triệu chứng user thấy, không phải nguyên nhân nội bộ. "5xx rate trên 0.5% trong 5 phút" tốt. "Thời gian GC trên 1%" không - có thể ổn, tuỳ workload. "Symptom-based alerting" của SRE là khung đúng. Tín hiệu từ tầng cache - hit rate, miss-per-second - là loại đúng: user thấy như latency.
Hai: alert theo burn rate SLO, không ngưỡng thô. Error rate 1% ổn nếu SLO 99% trong 30 ngày; là cháy nếu SLO 99.99%. Tính burn rate so SLO và alert khi tiêu error budget quá nhanh.
Ba: mọi alert phải có runbook. Nếu engineer on-call không trả lời được "phải làm gì?", alert là tiếng ồn. Ghép mọi alert với trang Confluence hay wiki giải thích chẩn đoán và sửa.
Bản thân observability tạo failure mode nào?
- Bùng nổ cardinality - metric label với
userIdsinh một series mỗi user, làm Prometheus chảy. Phòng: không bao giờ label với field cardinality cao; aggregate. - Sampling trace lệch - 1% sampling lỡ request chậm hiếm. Phòng: tail-based sampling giữ mọi error và mọi trace chậm.
- Rò PII trong log - log full request body đổ email, token,
data thanh toán. Phòng: log structured với field rõ; không
LogInformation("Body: {Body}", body). - Outage observability mất alert - pipeline alert fail thầm lặng và bạn không biết service down. Phòng: alert dead-man (alert nên fire mỗi ngày; nếu không, có vấn đề).
Khi nào đầu tư observability là sớm?
Khi service một instance, traffic thấp, chưa lên production. Máy dev local đọc console log là ổn. Thêm OpenTelemetry vào ngày deploy staging - không trước, không sau. Dây vào service đã phục vụ user khó hơn cài đặt từ ngày đầu. Ước lượng QPS chương 2 cho biết khi nào "production" đủ thật để cần điều này.
Đi tiếp đâu từ đây?
Chương kế tiếp: rate limit trong .NET - khối ops thứ hai. Cùng observability, rate limit là cái giữ service sống khi upstream hỏng. Sau đó, các chương case study lắp mọi block từ foundations đến ops thành thiết kế thật, đầy đủ.