C# Log Helper

admin | 2026-03-23 17:05 | 阅读 102 次

没问题。这是一个零依赖(仅使用 .NET Framework 4.7.2 原生 SystemSystem.IO 命名空间)的 LogHelper 类。

它直接使用 System.IO.FileSystem.IO.StreamWriter 将日志写入文本文件,包含线程锁以确保多线程安全,并支持按天自动分割日志文件

1. 核心代码 (LogHelper.cs)

using System;
using System.IO;
using System.Text;
using System.Threading;

namespace YourNamespace.Utilities
{
    /// <summary>
    /// 零依赖日志帮助类 (.NET Framework 4.7.2 原生实现)
    /// 功能:
    /// 1. 直接写入本地文件
    /// 2. 按天自动分割文件 (格式:logs_2023-10-27.log)
    /// 3. 线程安全 (使用 lock)
    /// 4. 自动创建日志目录
    /// </summary>
    public class LogHelper
    {
        private static readonly object _lockObj = new object();
        
        // 配置项
        private readonly string _logFolder;
        private readonly string _appName;
        private readonly LogLevel _minLevel;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logFolder">日志文件夹路径 (例如: "C:\\Logs" 或 ".\\Logs")</param>
        /// <param name="appName">应用名称 (作为日志文件名前缀)</param>
        /// <param name="minLevel">最低记录级别 (默认 Info)</param>
        public LogHelper(string logFolder, string appName = "App", LogLevel minLevel = LogLevel.Info)
        {
            _logFolder = logFolder;
            _appName = appName;
            _minLevel = minLevel;

            // 确保目录存在
            if (!Directory.Exists(_logFolder))
            {
                Directory.CreateDirectory(_logFolder);
            }
        }

        #region Public Methods

        public void Debug(string message) => WriteLog(LogLevel.Debug, message, null);
        public void Debug(string message, params object[] args) => WriteLog(LogLevel.Debug, string.Format(message, args), null);

        public void Info(string message) => WriteLog(LogLevel.Info, message, null);
        public void Info(string message, params object[] args) => WriteLog(LogLevel.Info, string.Format(message, args), null);

        public void Warn(string message) => WriteLog(LogLevel.Warn, message, null);
        public void Warn(string message, params object[] args) => WriteLog(LogLevel.Warn, string.Format(message, args), null);
        public void Warn(Exception ex, string message) => WriteLog(LogLevel.Warn, message, ex);
        public void Warn(Exception ex, string message, params object[] args) => WriteLog(LogLevel.Warn, string.Format(message, args), ex);

        public void Error(string message) => WriteLog(LogLevel.Error, message, null);
        public void Error(string message, params object[] args) => WriteLog(LogLevel.Error, string.Format(message, args), null);
        public void Error(Exception ex, string message) => WriteLog(LogLevel.Error, message, ex);
        public void Error(Exception ex, string message, params object[] args) => WriteLog(LogLevel.Error, string.Format(message, args), ex);

        public void Critical(string message) => WriteLog(LogLevel.Critical, message, null);
        public void Critical(Exception ex, string message) => WriteLog(LogLevel.Critical, message, ex);

        #endregion

        #region Private Implementation

        private void WriteLog(LogLevel level, string message, Exception ex)
        {
            // 1. 级别过滤
            if (level < _minLevel) return;

            // 2. 准备内容
            string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
            string dateSuffix = DateTime.Now.ToString("yyyy-MM-dd");
            string fileName = $"{_appName}_{dateSuffix}.log";
            string fullPath = Path.Combine(_logFolder, fileName);

            string exceptionDetails = "";
            if (ex != null)
            {
                exceptionDetails = $"\r\n[Exception]: {ex.GetType().Name}\r\n[Message]: {ex.Message}\r\n[StackTrace]: {ex.StackTrace}";
                if (ex.InnerException != null)
                {
                    exceptionDetails += $"\r\n[Inner]: {ex.InnerException.Message}";
                }
            }

            // 格式化日志行:[时间] [级别] [消息] [异常]
            // 例如:[2023-10-27 10:00:00.123] [INFO] 用户登录成功
            string logLine = $"[{timestamp}] [{level,-7}] {message}{exceptionDetails}";

            // 3. 写入文件 (线程锁)
            // 注意:File.AppendAllText 内部会打开/关闭文件流,高并发下可能有性能损耗,但最稳健
            lock (_lockObj)
            {
                try
                {
                    // 使用 UTF8 编码,防止中文乱码
                    File.AppendAllText(fullPath, logLine + Environment.NewLine, Encoding.UTF8);
                }
                catch (IOException ioEx)
                {
                    // 如果连日志都写不进去,通常只能忽略或写入事件查看器,防止程序崩溃
                    // 这里简单忽略,避免死循环
                    System.Diagnostics.Debug.WriteLine($"Failed to write log: {ioEx.Message}");
                }
            }
        }

        #endregion

        #region Enums

        public enum LogLevel
        {
            Debug = 0,
            Info = 1,
            Warn = 2,
            Error = 3,
            Critical = 4
        }

        #endregion
    }
}

2. 如何使用

由于没有依赖注入容器,你通常在程序的入口点(Global.asax, Program.cs, Form_Load)创建一个静态单例实例。

场景 A: ASP.NET WebApi / MVC (Global.asax.cs)

using System.Web.Http;
using YourNamespace.Utilities;

public class WebApiApplication : System.Web.Http.Application
{
    // 定义全局静态日志实例
    public static readonly LogHelper Log = new LogHelper(
        logFolder: @"C:\MyApp\Logs", // 日志存放路径
        appName: "MyWebApi",         // 文件名前缀 -> MyWebApi_2026-03-23.log
        minLevel: LogHelper.LogLevel.Info // 只记录 Info 及以上
    );

    protected void Application_Start()
    {
        Log.Info("WebApi 应用程序启动");
        
        // 捕获全局未处理异常
        AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
        {
            Log.Critical(e.ExceptionObject as Exception, "发生未处理的域级异常");
        };

        GlobalConfiguration.Configure(WebApiConfig.Register);
    }
}

在 Controller 中调用:

public class OrderController : ApiController
{
    public IHttpActionResult GetOrder(int id)
    {
        try
        {
            WebApiApplication.Log.Info("查询订单 ID: {0}", id);
            
            // 业务逻辑...
            if (id == 0) throw new Exception("ID 不能为 0");

            return Ok(id);
        }
        catch (Exception ex)
        {
            // 记录异常堆栈
            WebApiApplication.Log.Error(ex, "查询订单 {0} 失败", id);
            return InternalServerError();
        }
    }
}

场景 B: WinForm / Console (Program.cs)

using System;
using System.Windows.Forms;
using YourNamespace.Utilities;

static class Program
{
    // 全局静态实例
    public static readonly LogHelper Log = new LogHelper(
        logFolder: ".\\Logs",      // 当前目录下的 Logs 文件夹
        appName: "MyWinFormApp", 
        minLevel: LogHelper.LogLevel.Debug
    );

    [STAThread]
    static void Main()
    {
        Log.Info("程序开始运行");

        try
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
        catch (Exception ex)
        {
            Log.Critical(ex, "程序发生致命崩溃");
            MessageBox.Show("程序出错,请联系管理员。\n错误已记录到日志文件。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

在 Form 中调用:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        Program.Log.Info("主窗口加载完成");
    }

    private void btnSave_Click(object sender, EventArgs e)
    {
        try
        {
            Program.Log.Info("用户点击保存,内容长度:{0}", txtContent.Text.Length);
            // 保存逻辑...
        }
        catch (Exception ex)
        {
            Program.Log.Error(ex, "保存数据时发生错误");
            MessageBox.Show("保存失败");
        }
    }
}

3. 生成的日志文件示例

文件路径:C:\MyApp\Logs\MyWebApi_2026-03-23.log

[2026-03-23 14:44:01.123] [Info   ] WebApi 应用程序启动
[2026-03-23 14:45:10.456] [Info   ] 查询订单 ID: 1001
[2026-03-23 14:45:10.489] [Debug  ] 数据库连接字符串已加载
[2026-03-23 14:45:12.100] [Warn   ] 订单 1002 库存不足,使用备用仓库
[2026-03-23 14:46:05.332] [Error  ] 查询订单 0 失败
[Exception]: System.Exception
[Message]: ID 不能为 0
[StackTrace]:    at OrderController.GetOrder(Int32 id) in D:\Project\Controllers\OrderController.cs:line 25
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(...)

4. 特点与注意事项

  1. 绝对零依赖:不需要安装任何 NuGet 包,复制 .cs 文件即可用。
  2. 线程安全:使用了 lock (_lockObj),即使多个线程同时写日志也不会导致文件冲突或内容错乱。
  3. 按天切割:每天自动生成一个新文件(如 App_2026-03-23.log),方便管理和归档旧日志。
  4. 异常堆栈:专门处理 Exception 对象,完整记录 MessageStackTrace,便于排查问题。
  5. 性能提示
    • 由于使用 File.AppendAllText(每次写入都打开/关闭文件流),在极高并发(如每秒数千次日志)场景下可能会有轻微的性能瓶颈。
    • 对于绝大多数常规 Web 应用、WinForm 工具或后台服务,这个性能完全足够。
    • 如果需要极致性能,通常需要引入异步队列机制(但这会增加代码复杂度,违背了“简单零依赖”的初衷)。
  6. 中文编码:强制使用 Encoding.UTF8,确保中文日志不会乱码。

这个类非常适合传统 .NET Framework 项目快速集成日志功能。

评论 (0)

发表评论
暂无评论,快来发表第一条评论吧!