WinForm + log4net 企业级日志实战
群里有人问,问题。我顺便写一个模板。并写出文档。
在实际项目开发中,我越来越意识到:
日志不是“能用就行”,而是必须“可维护、可分流、可扩展”。
因此我专门写了一个 WinForm + log4net 完整示例项目,实现:
- ✅ 多级别日志分流
- ✅ 控制台输出
- ✅ 按日期滚动文件
- ✅ 错误独立文件
- ✅ Windows 事件日志写入
- ✅ Fatal 邮件告警
- ✅ 程序入口统一初始化
- ✅ 配置文件热更新
下面我把完整实现思路 + 全部核心代码详细拆解。
一、日志初始化 —— 必须在程序入口完成
很多人喜欢在窗体里初始化日志,我不这么做。
我统一在 Program.cs 里完成配置加载。
Program.cs
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Windows.Forms;
namespace WinFormLog4netDemo
{
internal static class Program
{
[STAThread]
static void Main()
{
// ✅ 程序启动第一步:初始化日志系统
ConfigureLog4net();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
/// <summary>
/// 统一配置 log4net
/// </summary>
private static void ConfigureLog4net()
{
try
{
// 1. 定位配置文件
string configPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"log4net.config");
FileInfo configFile = new FileInfo(configPath);
// 2. 检查配置文件是否存在
if (!configFile.Exists)
{
throw new FileNotFoundException("log4net 配置文件不存在", configPath);
}
// 3. 加载并监控配置文件(支持热更新)
XmlConfigurator.ConfigureAndWatch(configFile);
// 4. 写一条启动日志
ILog logger = LogManager.GetLogger(typeof(Program));
logger.Info("应用程序启动,log4net 配置加载成功");
}
catch (Exception ex)
{
MessageBox.Show(
$"log4net 配置初始化失败:{ex.Message}\n{ex.StackTrace}",
"致命错误",
MessageBoxButtons.OK,
MessageBoxIcon.Stop
);
Environment.Exit(1);
}
}
}
}
为什么使用 ConfigureAndWatch?
因为它支持:
- 修改 log4net.config 后自动生效
- 不需要重启程序
- 非常适合生产环境
二、日志调用层 —— Form1 实现
我在窗体中定义一个静态日志实例:
private static readonly ILog _logger =
LogManager.GetLogger(typeof(Form1));
使用当前类作为 logger 名称,方便定位日志来源。
Form1.cs 完整代码
using System;
using System.Windows.Forms;
using log4net;
namespace WinFormLog4netDemo
{
public partial class Form1 : Form
{
// ✅ 使用当前类作为日志名称
private static readonly ILog _logger =
LogManager.GetLogger(typeof(Form1));
public Form1()
{
InitializeComponent();
// 日志已在 Program 中完成初始化
}
/// <summary>
/// Debug 日志
/// </summary>
private void btnDebugLog_Click(object sender, EventArgs e)
{
_logger.Debug("用户点击了 Debug 按钮,调试信息:参数 x=123,参数 y=456");
ShowSuccessTip("Debug 日志已记录!");
}
/// <summary>
/// Info 日志
/// </summary>
private void btnInfoLog_Click(object sender, EventArgs e)
{
_logger.InfoFormat(
"用户[{0}]在[{1:yyyy-MM-dd HH:mm:ss}]点击了 Info 按钮",
Environment.UserName,
DateTime.Now
);
ShowSuccessTip("Info 日志已记录!");
}
/// <summary>
/// Warn 日志
/// </summary>
private void btnWarnLog_Click(object sender, EventArgs e)
{
_logger.Warn("用户输入的参数格式不规范(预期:数字,实际:字符串),已自动修正");
ShowWarningTip("Warn 日志已记录!");
}
/// <summary>
/// Error 日志(含异常堆栈)
/// </summary>
private void btnErrorLog_Click(object sender, EventArgs e)
{
try
{
int a = 10;
int b = 0;
int result = a / b;
}
catch (Exception ex)
{
_logger.Error("除法运算出现异常,已捕获并处理", ex);
ShowErrorTip("Error 日志已记录(含异常堆栈)!");
}
}
/// <summary>
/// Fatal 日志
/// </summary>
private void btnFatalLog_Click(object sender, EventArgs e)
{
_logger.Fatal("数据库连接池耗尽,无法执行任何数据库操作,程序即将终止");
ShowFatalTip("Fatal 日志已记录!");
}
#region 统一提示方法
private void ShowSuccessTip(string message)
{
MessageBox.Show(message, "操作成功",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void ShowWarningTip(string message)
{
MessageBox.Show(message, "警告",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
private void ShowErrorTip(string message)
{
MessageBox.Show(message, "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
private void ShowFatalTip(string message)
{
MessageBox.Show(message, "致命错误",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
#endregion
}
}
三、核心配置文件 log4net.config
这是整套日志系统的灵魂。
1️⃣ Debug → 控制台输出
<appender name="DebugConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%-5level] %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelMatchFilter">
<levelToMatch value="DEBUG" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
特点:
只匹配 DEBUG,不让其他级别混入。
2️⃣ Info / Warn → 按日期滚动文件
<appender name="InfoWarnFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs/InfoWarn/" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMdd'.log'" />
<staticLogFileName value="false" />
<maxSizeRollBackups value="30" />
<encoding value="utf-8" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%-5level] %logger [%thread] - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="WARN" />
</filter>
</appender>
3️⃣ Error / Fatal → 单独错误文件
<appender name="ErrorFatalFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs/ErrorFatal/" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMdd'.log'" />
<staticLogFileName value="false" />
<encoding value="utf-8" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%-5level] %logger [%thread] - %message%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
4️⃣ Windows 事件日志输出
<appender name="ErrorEventAppender" type="log4net.Appender.EventLogAppender">
<logName value="Application" />
<applicationName value="WinFormLog4netDemo" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%-5level] %logger - %message%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
5️⃣ Fatal 邮件告警
<appender name="FatalMailAppender" type="log4net.Appender.SmtpAppender">
<to value="admin@your-domain.com" />
<from value="alert@your-domain.com" />
<subject value="【WinForm 程序致命错误告警】" />
<smtpHost value="smtp.qq.com" />
<smtpPort value="587" />
<enableSsl value="true" />
<smtpUsername value="alert@your-domain.com" />
<smtpPassword value="你的SMTP授权码" />
<bufferSize value="1" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [FATAL] %logger%newline%message%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelMatchFilter">
<levelToMatch value="FATAL" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
Root 节点
<root>
<level value="ALL" />
<appender-ref ref="DebugConsoleAppender" />
<appender-ref ref="InfoWarnFileAppender" />
<appender-ref ref="ErrorFatalFileAppender" />
<appender-ref ref="ErrorEventAppender" />
<appender-ref ref="FatalMailAppender" />
</root>
四、我的设计原则总结
在这个示例中,我遵循了三点原则:
- 日志分级必须清晰
- 错误必须独立文件
- 致命问题必须告警
这不是“写日志”,
而是“设计日志系统”。
五、注意
- 群友一直弄不起。原因是:log4net.config 配置文件的属性改了一下,复制输出到目录,始终复制
![在这里插入图片描述]()
![在这里插入图片描述]()
- 如果实现下面功能需要以管理员运行
![在这里插入图片描述]()
👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货
- 获取示例代码,轻松上手!
- 私信输入数字: 986t18
- 获取代码下载链接
![image]()





浙公网安备 33010602011771号