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>

四、我的设计原则总结

在这个示例中,我遵循了三点原则:

  1. 日志分级必须清晰
  2. 错误必须独立文件
  3. 致命问题必须告警

这不是“写日志”,
而是“设计日志系统”。

五、注意

  1. 群友一直弄不起。原因是:log4net.config 配置文件的属性改了一下,复制输出到目录,始终复制
    在这里插入图片描述
    在这里插入图片描述
  2. 如果实现下面功能需要以管理员运行
    在这里插入图片描述

👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货

  • 获取示例代码,轻松上手!
  • 私信输入数字: 986t18
  • 获取代码下载链接
    image
posted @ 2026-03-01 12:26  bugcome  阅读(0)  评论(0)    收藏  举报