"Thêm log đi" - câu nói phổ biến khi debug. Nhưng log GÌ, ở ĐÂU, chi tiết bao nhiêu? Log quá nhiều = noise. Log quá ít = mù. Session này giúp bạn log ĐÚNG.
Mục tiêu
- Structured Logging vs String Logging - tại sao khác biệt
- Log Levels - khi nào dùng level nào
- Correlation - trace request qua nhiều services
- Khi nào log, khi nào KHÔNG log
1) Structured Logging - Tại Sao Quan Trọng
csharp// ❌ String logging: con người đọc OK, máy KHÔNG search được
logger.LogInformation($"Order {orderId} created by {userId} with total {total}");
// Output: "Order abc-123 created by user-456 with total 99.99"
// → Muốn tìm tất cả orders của user-456? GREP bằng string → chậm, sai sót
// ✅ Structured logging: con người VÀ máy đều đọc/search được
logger.LogInformation("Order {OrderId} created by {UserId} with total {Total}",
orderId, userId, total);
// Output JSON:
// {
// "Message": "Order abc-123 created by user-456 with total 99.99",
// "Properties": {
// "OrderId": "abc-123",
// "UserId": "user-456",
// "Total": 99.99
// }
// }
// → Query: WHERE UserId = "user-456" → INSTANT!
// → Group by: Total > 1000 → trực tiếp!
Quy tắc structured logging
csharp// ✅ Message template + separate parameters
logger.LogInformation("Processing order {OrderId} with {ItemCount} items", orderId, items.Count);
// ❌ String interpolation (mất structured properties!)
logger.LogInformation($"Processing order {orderId} with {items.Count} items");
// → Trông giống nhau, nhưng structured version tạo SEARCHABLE properties
// ❌ ToString() trong parameter
logger.LogInformation("User {User}", user.ToString());
// → Mất structured data, chỉ còn string
// ✅ Object as parameter (Serilog destructure)
logger.LogInformation("Order created {@Order}", new { order.Id, order.Total, order.Status });
// @ = destructure object → tất cả properties searchable
2) Log Levels - Khi Nào Dùng Level Nào
Trace: Development ONLY. Từng bước chi tiết.
→ Tracing method calls, variable values
→ TUYỆT ĐỐI tắt ở Production
Debug: Development + staging. Diagnostic info.
→ "Entering method X with params Y"
→ Tắt ở Production (quá nhiều data)
Information: PRODUCTION. Sự kiện business quan trọng.
→ "Order {OrderId} created"
→ "User {UserId} logged in"
→ "Payment {PaymentId} processed"
Warning: Đáng chú ý nhưng KHÔNG phải lỗi.
→ "Cache miss for key {Key}"
→ "Retry attempt {Attempt} for {Operation}"
→ "Configuration {Key} not set, using default"
Error: Lỗi CÓ thể recover. Request này fail, nhưng app VẪN CHẠY.
→ "Failed to process payment {PaymentId}: {Error}"
→ "Database timeout for query {Query}"
Critical: Lỗi KHÔNG recover. App có thể crash.
→ "Database connection pool exhausted"
→ "Certificate expired"
→ "Out of memory"
Production configuration
json// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Information", // App code → Info trở lên
"Microsoft.AspNetCore": "Warning", // Framework → Warning trở lên (giảm noise)
"Microsoft.EntityFrameworkCore": "Warning",
"System": "Warning"
}
}
}
3) Log GÌ - Log KHÔNG Gì
SHOULD Log (có giá trị khi debug production)
csharp// ✅ Business events
logger.LogInformation("Order {OrderId} submitted by {UserId}", order.Id, userId);
logger.LogInformation("Payment {PaymentId} processed, amount {Amount}", payment.Id, amount);
// ✅ State transitions
logger.LogInformation("Order {OrderId} status changed from {OldStatus} to {NewStatus}",
order.Id, oldStatus, newStatus);
// ✅ External call results
logger.LogInformation("Called {ServiceName} in {Duration}ms, status {StatusCode}",
"PaymentGateway", sw.ElapsedMilliseconds, response.StatusCode);
// ✅ Errors with context
logger.LogError(exception, "Failed to process order {OrderId} for user {UserId}",
orderId, userId);
SHOULD NOT Log
csharp// ❌ Sensitive data
logger.LogInformation("User logged in with password {Password}", password); // 💀
logger.LogInformation("Credit card {CardNumber}", cardNumber); // 💀
logger.LogInformation("Token {JwtToken}", token); // 💀
// ❌ Quá chi tiết trong production
logger.LogInformation("Entering GetUserAsync");
logger.LogInformation("Query: SELECT * FROM Users WHERE...");
// → Framework logs đã handle, bạn không cần log lại
// ❌ Large objects
logger.LogInformation("Request body: {@Body}", largeRequestBody);
// → Log 100KB JSON mỗi request = storage explosion
4) Correlation - Trace Request Xuyên Suốt
csharp// Vấn đề: 1 user action → 5 services → 50 log entries
// Câu hỏi: Log nào thuộc request nào?
// Solution: Correlation ID - 1 ID unique cho TOÀN BỘ flow
// Middleware tạo hoặc forward Correlation ID
public class CorrelationMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Activity.Current?.Id
?? Guid.NewGuid().ToString();
// Đặt vào logging scope → TẤT CẢ log trong request có CorrelationId
using (LogContext.PushProperty("CorrelationId", correlationId))
{
context.Response.Headers["X-Correlation-Id"] = correlationId;
// Forward khi gọi service khác
await next(context);
}
}
}
// Khi gọi service khác: forward correlation ID
httpClient.DefaultRequestHeaders.Add("X-Correlation-Id", correlationId);
// Kết quả: search bằng CorrelationId → thấy TOÀN BỘ flow
// Order Service: "Order abc created" [CorrelationId: corr-789]
// Payment Service: "Payment processed" [CorrelationId: corr-789]
// Email Service: "Confirmation sent" [CorrelationId: corr-789]
5) Debugging Production Issues - Process
Step 1: User report "đặt hàng bị lỗi"
Step 2: Lấy request ID hoặc user ID → search logs
Step 3: Tìm Correlation ID → trace toàn bộ flow
Step 4: Xác định LỖI Ở SERVICE NÀO, LINE NÀO
Step 5: Xem error details + stack trace
Step 6: Reproduce in staging → fix → test → deploy
Tools:
- Seq: self-hosted, .NET friendly, realtime search
- Application Insights: Azure managed, powerful queries
- ELK (Elasticsearch + Kibana): industry standard
- Grafana Loki: lightweight, log aggregation
dotnet-counters / dotnet-trace (Live debugging)
bash# Xem realtime metrics (không cần restart app)
dotnet-counters monitor --process-id <PID>
# → GC collections, thread pool, exception count, HTTP requests/sec
# Capture trace cho performance analysis
dotnet-trace collect --process-id <PID> --duration 00:00:30
# → Mở trace file bằng PerfView hoặc Speedscope
Master Checklist ✅
□ Structured logging (template + params, KHÔNG string interpolation)?
□ Log levels đúng (Info cho business, Warning cho anomalies)?
□ KHÔNG log sensitive data (password, token, PII)?
□ Correlation ID cho cross-service tracing?
□ Production log level = Information (KHÔNG Debug/Trace)?
□ Framework logs giảm xuống Warning?
□ Error logs có exception + context (orderId, userId)?
□ Log storage có giới hạn retention?
Tư duy master: Log tốt nhất là log mà lúc 2 giờ sáng, on-call engineer nhìn vào biết NGAY chuyện gì xảy ra, CHỖ NÀO, và VỚI AI - mà không cần gọi bạn.