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

日誌系統 Logging

為什麼需要日誌(Logging)?

日誌就是你的程式在執行過程中留下的紀錄,讓你知道發生了什麼事。

💡 比喻:飛機的黑盒子

  • 飛機正常飛行時,黑盒子持續記錄各種數據
  • 一旦發生事故,調查員靠黑盒子還原當時的狀況
  • 日誌就是你程式的「黑盒子」
  • 系統上線後出了問題,你沒辦法用 breakpoint 除錯
  • 你只能靠日誌來還原問題發生的經過
沒有日誌時:
使用者:「你的系統剛剛壞了!」
工程師:「什麼時候?發生了什麼?」
使用者:「不知道,反正就是壞了。」
工程師:「……」(無從查起)

有日誌時:
使用者:「你的系統剛剛壞了!」
工程師:(打開日誌)
[2024-01-15 14:30:22] ERROR: 資料庫連線逾時,連線字串:Server=db01
[2024-01-15 14:30:22] ERROR: 重試 3 次後仍然失敗
[2024-01-15 14:30:23] CRITICAL: 訂單服務無法處理請求
工程師:「找到了!是資料庫 db01 掛了。」

ILogger:ASP.NET Core 內建日誌

// ASP.NET Core 已經內建日誌功能,不需要額外安裝套件
// 透過 DI 注入 ILogger<T> 就能使用

using Microsoft.Extensions.Logging;

public class OrderController : ControllerBase
{
    // 注入 ILogger,泛型參數用目前的類別名稱
    // 這樣日誌會自動標記是哪個類別產生的
    private readonly ILogger<OrderController> _logger;

    // 建構式注入
    public OrderController(ILogger<OrderController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> CreateOrder(OrderRequest request)
    {
        // 記錄一般資訊
        _logger.LogInformation("開始建立訂單,使用者:{UserId}", request.UserId);

        try
        {
            // 處理訂單邏輯...
            var order = await _orderService.CreateAsync(request);

            // 記錄成功訊息
            _logger.LogInformation("訂單建立成功,訂單編號:{OrderId}", order.Id);

            return Ok(order);
        }
        catch (Exception ex)
        {
            // 記錄錯誤訊息(包含例外物件)
            _logger.LogError(ex, "建立訂單失敗,使用者:{UserId}", request.UserId);

            return StatusCode(500, "系統錯誤,請稍後再試");
        }
    }
}

Log Levels:日誌等級

日誌等級由低到高(越高越嚴重):

等級          數值   用途                      比喻
──────────────────────────────────────────────────────
Trace          0    最詳細的追蹤資訊            偵探的隨身筆記(每個細節)
Debug          1    開發除錯用的資訊            工程師的草稿紙
Information    2    一般流程記錄                航海日誌(正常航行紀錄)
Warning        3    不正常但還能運作            黃燈警告(注意但不停車)
Error          4    發生錯誤,某個操作失敗       紅燈(出事了!)
Critical       5    系統即將崩潰的嚴重錯誤       火災警報(快逃!)

設定某個等級後,只有「等於或高於」該等級的日誌才會被記錄。
例如設定 Warning,則 Warning、Error、Critical 會被記錄,
Trace、Debug、Information 會被忽略。
// 各種日誌等級的使用範例

public class PaymentService
{
    // 注入日誌服務
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(ILogger<PaymentService> logger)
    {
        _logger = logger;
    }

    public async Task ProcessPaymentAsync(PaymentRequest request)
    {
        // Trace:非常詳細的追蹤資訊(通常只在本機開發時開啟)
        _logger.LogTrace("進入 ProcessPaymentAsync,參數:{@Request}", request);

        // Debug:開發除錯用
        _logger.LogDebug("開始驗證支付金額:{Amount}", request.Amount);

        // Information:一般業務流程紀錄
        _logger.LogInformation("處理付款,訂單:{OrderId},金額:{Amount}",
            request.OrderId, request.Amount);

        // Warning:不正常但系統還能運作
        if (request.Amount > 100000)
        {
            _logger.LogWarning("大額交易警告!訂單:{OrderId},金額:{Amount}",
                request.OrderId, request.Amount);
        }

        try
        {
            // 呼叫金流 API...
            await CallPaymentGateway(request);
        }
        catch (TimeoutException ex)
        {
            // Error:操作失敗
            _logger.LogError(ex, "付款閘道逾時,訂單:{OrderId}", request.OrderId);
            throw;
        }
        catch (Exception ex)
        {
            // Critical:系統層級的嚴重錯誤
            _logger.LogCritical(ex, "付款系統完全無法使用!");
            throw;
        }
    }
}

結構化日誌(Structured Logging)

傳統日誌 vs 結構化日誌:

傳統日誌(純文字):
"2024-01-15 使用者 123 購買了產品 456,金額 999 元"
→ 要搜尋「使用者 123 的所有訂單」很困難(只能用字串搜尋)

結構化日誌(有欄位):
{
    "Timestamp": "2024-01-15",
    "Message": "使用者購買產品",
    "UserId": 123,
    "ProductId": 456,
    "Amount": 999
}
→ 可以直接查詢 WHERE UserId = 123(像查資料庫一樣!)
// ASP.NET Core 的 ILogger 天生支援結構化日誌

// ❌ 錯誤:用字串拼接(無法被結構化解析)
_logger.LogInformation("使用者 " + userId + " 購買了產品 " + productId);
// 也不要用 $"..." 字串插值
_logger.LogInformation($"使用者 {userId} 購買了產品 {productId}");

// ✅ 正確:用佔位符(Placeholder),讓日誌框架結構化處理
// {UserId} 和 {ProductId} 會被當作獨立的欄位存儲
_logger.LogInformation("使用者 {UserId} 購買了產品 {ProductId}",
    userId, productId);

// 這樣在日誌查詢平台(如 Seq、Kibana)就能:
// - WHERE UserId = 123
// - GROUP BY ProductId
// - 對 Amount 做統計分析

// 記錄完整物件:用 @ 前綴做解構
var order = new { Id = 1, Total = 999, Items = 3 };
// @ 前綴會把物件序列化成 JSON 記錄
_logger.LogInformation("訂單資訊:{@Order}", order);
// 輸出:訂單資訊:{ Id: 1, Total: 999, Items: 3 }

Serilog:更強大的日誌套件

// 1. 安裝 Serilog 套件
// dotnet add package Serilog.AspNetCore
// dotnet add package Serilog.Sinks.Console
// dotnet add package Serilog.Sinks.File

// 2. 在 Program.cs 設定 Serilog
using Serilog;

// 設定 Serilog 日誌管線
Log.Logger = new LoggerConfiguration()
    // 最低記錄等級
    .MinimumLevel.Information()
    // 覆寫特定命名空間的等級(減少微軟框架的雜訊)
    .MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning)
    // 輸出到 Console(有顏色標示)
    .WriteTo.Console()
    // 輸出到檔案(每天一個檔案,保留 30 天)
    .WriteTo.File("logs/app-.log",
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: 30)
    // 建立 Logger
    .CreateLogger();

// 把 Serilog 設定為 ASP.NET Core 的日誌提供者
builder.Host.UseSerilog();

// 3. 使用方式跟 ILogger 完全一樣!
// 因為 Serilog 實作了 ILogger 介面
// 你的 Controller 和 Service 不用改任何程式碼

日誌輸出目標(Sinks)

// Serilog 可以同時輸出到多個目標

Log.Logger = new LoggerConfiguration()
    // 輸出到 Console(開發時方便看)
    .WriteTo.Console(
        // 自訂輸出格式
        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
    )
    // 輸出到檔案(正式環境基本需求)
    .WriteTo.File("logs/app-.log",
        // 每天換一個新檔案
        rollingInterval: RollingInterval.Day,
        // 單一檔案最大 10MB
        fileSizeLimitBytes: 10_000_000,
        // 超過大小就建新檔
        rollOnFileSizeLimit: true,
        // 保留最近 30 個檔案
        retainedFileCountLimit: 30
    )
    // 輸出到 Seq(結構化日誌查詢平台)
    // .WriteTo.Seq("http://localhost:5341")
    .CreateLogger();

appsettings.json 設定日誌等級

// appsettings.json - 透過設定檔控制日誌等級
{
  // 日誌設定區塊
  "Logging": {
    "LogLevel": {
      // 預設等級:Information(記錄一般資訊以上)
      "Default": "Information",
      // 微軟框架的日誌等級設高一點(減少雜訊)
      "Microsoft.AspNetCore": "Warning",
      // EF Core 的 SQL 查詢日誌(開發時可以打開看 SQL)
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  // Serilog 專用設定
  "Serilog": {
    "MinimumLevel": {
      // 預設最低等級
      "Default": "Information",
      // 覆寫特定命名空間
      "Override": {
        // 微軟框架只記錄 Warning 以上
        "Microsoft": "Warning",
        // EF Core 只記錄 Warning 以上
        "Microsoft.EntityFrameworkCore": "Warning"
      }
    }
  }
}
// appsettings.Development.json - 開發環境可以開更多日誌
{
  // 開發環境的日誌設定
  "Logging": {
    "LogLevel": {
      // 開發時記錄更詳細的資訊
      "Default": "Debug",
      // 可以看到 EF Core 產生的 SQL
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  }
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:在日誌中記錄敏感資料

// ❌ 錯誤:把密碼、信用卡號寫進日誌
_logger.LogInformation("使用者登入,帳號:{Email},密碼:{Password}",
    email, password);
// 日誌可能存在檔案、傳到遠端伺服器,任何看到日誌的人都能看到密碼!

// ❌ 錯誤:記錄完整的信用卡號
_logger.LogInformation("付款成功,卡號:{CardNumber}", cardNumber);

// ✅ 正確:永遠不要記錄敏感資料
_logger.LogInformation("使用者登入,帳號:{Email}", email);
// 信用卡只記錄後四碼
_logger.LogInformation("付款成功,卡號尾碼:{CardLast4}",
    cardNumber[^4..]);

❌ 錯誤 2:使用錯誤的日誌等級

// ❌ 錯誤:所有東西都用 Information
_logger.LogInformation("系統即將崩潰!記憶體不足!");  // 這應該是 Critical!
_logger.LogInformation("找不到使用者 123");             // 這可能是 Warning
_logger.LogInformation("變數 x 的值是 42");             // 這應該是 Debug

// ✅ 正確:根據嚴重程度選擇適當的等級
_logger.LogCritical("系統即將崩潰!記憶體不足!");    // 最嚴重的錯誤
_logger.LogWarning("找不到使用者 {UserId}", 123);     // 不正常但不致命
_logger.LogDebug("變數 x 的值是 {Value}", 42);        // 除錯用的資訊

❌ 錯誤 3:不用結構化日誌

// ❌ 錯誤:用字串拼接或插值
_logger.LogInformation($"使用者 {userId} 在 {DateTime.Now} 購買了 {productName}");
// 問題 1:無法在日誌平台做結構化查詢
// 問題 2:效能差(即使日誌等級設定會跳過這條,字串還是會被拼接)

// ✅ 正確:用訊息模板(Message Template)
_logger.LogInformation(
    "使用者 {UserId} 在 {PurchaseTime} 購買了 {ProductName}",
    userId, DateTime.Now, productName);
// 每個佔位符都是可查詢的結構化欄位
// 如果日誌等級設定會跳過,佔位符不會被處理(效能更好)

💡 重點整理

概念 說明
ILogger ASP.NET Core 內建的日誌介面
Log Levels Trace < Debug < Information < Warning < Error < Critical
結構化日誌 用佔位符而非字串拼接,讓日誌可以被查詢分析
Serilog 強大的第三方日誌套件,支援多種輸出目標
Sinks 日誌的輸出目標:Console、File、Seq、Elasticsearch 等
appsettings.json 透過設定檔控制不同命名空間的日誌等級

💡 大家的想法 · 0

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