☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
microservices 進階

🔍 微服務可觀測性:日誌、追蹤與指標

📌 可觀測性三大支柱

可觀測性 (Observability)
├── 📋 Logs(日誌):記錄發生了什麼事
│   └── "訂單 #123 建立成功"
├── 🔗 Traces(追蹤):追蹤請求在服務間的流動
│   └── Gateway → OrderService → InventoryService → PaymentService
└── 📊 Metrics(指標):量化系統的狀態
    └── 請求數/秒、延遲 P99、錯誤率
支柱 回答的問題 工具
Logs 發生了什麼?為什麼出錯? Serilog + Seq / ELK
Traces 請求經過了哪些服務?哪裡慢? OpenTelemetry + Jaeger
Metrics 系統整體表現如何?需要擴展嗎? Prometheus + Grafana

📌 集中式日誌:Serilog

為什麼需要集中式日誌?

微服務的日誌分散在多個容器中,需要集中收集才能有效除錯。

// ── 安裝 Serilog ──
// dotnet add package Serilog.AspNetCore
// dotnet add package Serilog.Sinks.Seq
// dotnet add package Serilog.Enrichers.Environment
// dotnet add package Serilog.Enrichers.Thread

// Program.cs
builder.Host.UseSerilog((context, config) =>
{
    config
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .Enrich.WithMachineName()
        .Enrich.WithThreadId()
        .Enrich.WithProperty("ServiceName", "OrderService")
        .WriteTo.Console(outputTemplate:
            "[{Timestamp:HH:mm:ss} {Level:u3}] {ServiceName} | {Message:lj}{NewLine}{Exception}")
        .WriteTo.Seq("http://seq:5341"); // 集中式日誌伺服器
});

// 使用結構化日誌
public class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public async Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
    {
        _logger.LogInformation("開始建立訂單 CustomerId={CustomerId}, Items={ItemCount}",
            cmd.CustomerId, cmd.Items.Count);

        try
        {
            var order = new Order { /* ... */ };
            await _repo.AddAsync(order);

            _logger.LogInformation("訂單建立成功 OrderId={OrderId}, Total={Total}",
                order.Id, order.TotalAmount);

            return order;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "訂單建立失敗 CustomerId={CustomerId}",
                cmd.CustomerId);
            throw;
        }
    }
}

📌 關聯 ID (Correlation ID) 追蹤請求鏈

// ── 中介軟體:為每個請求加上 Correlation ID ──
public class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;
    private const string CorrelationHeader = "X-Correlation-ID";

    public CorrelationIdMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        // 從上游取得 Correlation ID,沒有就建立新的
        if (!context.Request.Headers.TryGetValue(
            CorrelationHeader, out var correlationId))
        {
            correlationId = Guid.NewGuid().ToString();
        }

        context.Items[CorrelationHeader] = correlationId.ToString();

        // 加入回應標頭
        context.Response.OnStarting(() =>
        {
            context.Response.Headers[CorrelationHeader] = correlationId;
            return Task.CompletedTask;
        });

        // 加入日誌上下文
        using (LogContext.PushProperty("CorrelationId", correlationId.ToString()))
        {
            await _next(context);
        }
    }
}

// 呼叫下游服務時傳遞 Correlation ID
public class CorrelationIdDelegatingHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CorrelationIdDelegatingHandler(
        IHttpContextAccessor httpContextAccessor)
        => _httpContextAccessor = httpContextAccessor;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        var correlationId = _httpContextAccessor.HttpContext?
            .Items["X-Correlation-ID"]?.ToString();

        if (!string.IsNullOrEmpty(correlationId))
            request.Headers.Add("X-Correlation-ID", correlationId);

        return await base.SendAsync(request, ct);
    }
}

📌 分散式追蹤:OpenTelemetry

// ── 安裝 ──
// dotnet add package OpenTelemetry.Extensions.Hosting
// dotnet add package OpenTelemetry.Instrumentation.AspNetCore
// dotnet add package OpenTelemetry.Instrumentation.Http
// dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore
// dotnet add package OpenTelemetry.Exporter.OtlpProtobuf

// Program.cs
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource =>
        resource.AddService("OrderService"))
    .WithTracing(tracing =>
    {
        tracing
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddEntityFrameworkCoreInstrumentation()
            .AddSource("OrderService.Activities")
            .AddOtlpExporter(opt =>
            {
                opt.Endpoint = new Uri("http://jaeger:4317");
            });
    })
    .WithMetrics(metrics =>
    {
        metrics
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation()
            .AddPrometheusExporter();
    });

// 使用 Prometheus 端點
app.MapPrometheusScrapingEndpoint();

自訂追蹤 Span

public class OrderService
{
    private static readonly ActivitySource _activitySource =
        new("OrderService.Activities");

    public async Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
    {
        using var activity = _activitySource.StartActivity("CreateOrder");
        activity?.SetTag("order.customer_id", cmd.CustomerId.ToString());
        activity?.SetTag("order.item_count", cmd.Items.Count);

        // 驗證步驟
        using (var validateActivity = _activitySource.StartActivity("ValidateOrder"))
        {
            await ValidateAsync(cmd);
            validateActivity?.SetTag("validation.result", "success");
        }

        // 儲存步驟
        using (var saveActivity = _activitySource.StartActivity("SaveOrder"))
        {
            var order = new Order { /* ... */ };
            await _repo.AddAsync(order);
            activity?.SetTag("order.id", order.Id.ToString());
            return order;
        }
    }
}

📌 健康指標:Prometheus + Grafana

// 自訂業務指標
public class OrderMetrics
{
    private readonly Counter<long> _ordersCreated;
    private readonly Histogram<double> _orderProcessingDuration;
    private readonly UpDownCounter<long> _activeOrders;

    public OrderMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("OrderService");

        _ordersCreated = meter.CreateCounter<long>(
            "orders.created",
            description: "建立的訂單數量");

        _orderProcessingDuration = meter.CreateHistogram<double>(
            "orders.processing.duration",
            unit: "ms",
            description: "訂單處理耗時");

        _activeOrders = meter.CreateUpDownCounter<long>(
            "orders.active",
            description: "進行中的訂單數量");
    }

    public void OrderCreated(string category)
    {
        _ordersCreated.Add(1, new KeyValuePair<string, object?>("category", category));
        _activeOrders.Add(1);
    }

    public void RecordDuration(double milliseconds)
        => _orderProcessingDuration.Record(milliseconds);

    public void OrderCompleted()
        => _activeOrders.Add(-1);
}

📌 Docker Compose:完整可觀測性堆疊

# 加入可觀測性服務
services:
  # 集中日誌
  seq:
    image: datalust/seq:latest
    environment:
      - ACCEPT_EULA=Y
    ports:
      - "5341:5341"  # 接收日誌
      - "8081:80"    # Web UI

  # 分散式追蹤
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # Web UI
      - "4317:4317"    # OTLP gRPC

  # 指標收集
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  # 指標儀表板
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

下一章: 我們將學習如何將微服務部署到 Kubernetes,建立完整的 CI/CD Pipeline。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊