using System;
using System.IO;
using System.Text;
public static class LogHelper
{
///
/// 寫入文字日誌檔
///
/// 日誌內容
/// 檔案名稱(不含副檔名與日期)
/// 輸出錯誤訊息
/// 是否成功
public static bool WriteLog(string content, string fileName, out string errorMsg)
{
errorMsg = string.Empty;
StreamWriter writer = null;
try
{
// 檔案命名:yyyy-MM-dd_FileName.txt
var dateStr = DateTime.Now.ToString("yyyy-MM-dd");
var fullPath = Path.Combine(@"D:\Log", $"{dateStr}_{fileName}.txt");
// 確保目錄存在
var dir = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
// 判斷檔案是否存在決定是否附加
var append = File.Exists(fullPath);
writer = new StreamWriter(fullPath, append, Encoding.UTF8);
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
writer.WriteLine($"<{timestamp}> {content}");
return true;
}
catch (IOException ex)
{
errorMsg = ex.Message;
return false;
}
finally
{
writer?.Close();
}
}
}
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
public sealed class AsyncLogWriter : IDisposable
{
private readonly string _logDir;
private readonly string _baseName;
private StreamWriter _writer;
private string _currentDate;
private readonly object _lock = new object();
public AsyncLogWriter(string logDir, string baseName)
{
_logDir = logDir;
_baseName = baseName;
Directory.CreateDirectory(_logDir);
}
public async Task WriteAsync(string content)
{
var now = DateTime.Now;
var dateStr = now.ToString("yyyy-MM-dd");
var timestamp = now.ToString("HH:mm:ss.fff");
var line = $"[{dateStr} {timestamp}] {content}";
lock (_lock)
{
// 日期變更時切換檔案
if (_currentDate != dateStr)
{
_writer?.Dispose();
var path = Path.Combine(_logDir, $"{dateStr}_{_baseName}.log");
_writer = new StreamWriter(path, true, Encoding.UTF8) { AutoFlush = true };
_currentDate = dateStr;
}
_writer.WriteLine(line);
}
await Task.CompletedTask; // 保持非同步簽名,實際寫入已由 AutoFlush 處理
}
public void Dispose() => _writer?.Dispose();
}
// 簡單同步版
LogHelper.WriteLog("使用者登入成功", "Auth", out var err);
// 非同步高效能版(建議單例或 DI 注入)
var logger = new AsyncLogWriter(@"D:\Logs", "App");
await logger.WriteAsync("訂單建立: OrderId=12345");
await logger.WriteAsync("付款完成: Amount=999");
logger.Dispose(); // 程式結束時釋放
| 項目 | 建議 |
|---|---|
| 編碼 | 統一 UTF-8(Encoding.UTF8),避免中文亂碼 |
| 時間格式 | 含毫秒 yyyy-MM-dd HH:mm:ss.fff,利於追蹤順序 |
| 檔案輪替 | 按日期/大小分割,避免單一檔案過大 |
| 目錄權限 | 確保執行身分對日誌目錄有寫入權限 |
| 例外處理 | 日誌寫入失敗不應拋出影響主流程,記錄到 Console/EventLog 為備援 |
| 結構化日誌 | 生產環境建議 Serilog/NLog + JSON 格式,支援 ELK/Seq 分析 |
| 非同步 | 高頻寫入使用非同步或批次寫入,避免阻塞主執行緒 |
// Serilog 設定(Program.cs)
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.File(@"D:\Logs\App-.log",
rollingInterval: RollingInterval.Day,
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
// 使用
Log.Information("訂單 {OrderId} 建立,金額 {Amount}", 12345, 999);
Log.Error(ex, "付款失敗 OrderId={OrderId}", 12345);
從簡單的 StreamWriter 到企業級結構化日誌,根據專案規模與需求選擇適合的方案,核心原則:**可靠、可查、不影響主流程效能**。