C# Log Helper
admin |
2026-03-23 17:05 |
阅读 102 次
没问题。这是一个零依赖(仅使用 .NET Framework 4.7.2 原生 System 和 System.IO 命名空间)的 LogHelper 类。
它直接使用 System.IO.File 和 System.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. 特点与注意事项
- 绝对零依赖:不需要安装任何 NuGet 包,复制
.cs文件即可用。 - 线程安全:使用了
lock (_lockObj),即使多个线程同时写日志也不会导致文件冲突或内容错乱。 - 按天切割:每天自动生成一个新文件(如
App_2026-03-23.log),方便管理和归档旧日志。 - 异常堆栈:专门处理
Exception对象,完整记录Message和StackTrace,便于排查问题。 - 性能提示:
- 由于使用
File.AppendAllText(每次写入都打开/关闭文件流),在极高并发(如每秒数千次日志)场景下可能会有轻微的性能瓶颈。 - 对于绝大多数常规 Web 应用、WinForm 工具或后台服务,这个性能完全足够。
- 如果需要极致性能,通常需要引入异步队列机制(但这会增加代码复杂度,违背了“简单零依赖”的初衷)。
- 由于使用
- 中文编码:强制使用
Encoding.UTF8,确保中文日志不会乱码。
这个类非常适合传统 .NET Framework 项目快速集成日志功能。