EngineeringSenior DeveloperTraining Sessions

Session 14 - Logging, Tracing & Debugging Like a Pro

"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.