精品一区二区三区在线成人,欧美精产国品一二三区,Ji大巴进入女人66h,亚洲春色在线视频

WinForm中使用NLog實(shí)現(xiàn)全局異常處理:完整指南

開發(fā) 前端
實(shí)現(xiàn)全局異常處理不僅能提高應(yīng)用程序的穩(wěn)定性和可維護(hù)性,還能為用戶提供更好的使用體驗(yàn)。在實(shí)際項(xiàng)目中,可以根據(jù)具體需求對本文提供的示例進(jìn)行擴(kuò)展和定制。

為什么需要全局異常處理?

在開發(fā)WinForm桌面應(yīng)用程序時,異常處理是確保應(yīng)用穩(wěn)定性的關(guān)鍵環(huán)節(jié)。未處理的異常不僅會導(dǎo)致程序崩潰,還會造成用戶體驗(yàn)下降和數(shù)據(jù)丟失。全局異常處理機(jī)制可以:

  • 防止應(yīng)用程序意外崩潰
  • 記錄異常信息,便于問題定位和修復(fù)
  • 向用戶提供友好的錯誤提示
  • 收集軟件運(yùn)行狀態(tài)數(shù)據(jù),輔助產(chǎn)品改進(jìn)

NLog作為.NET生態(tài)中的優(yōu)秀日志框架,具有配置靈活、性能優(yōu)異、擴(kuò)展性強(qiáng)等特點(diǎn),是實(shí)現(xiàn)全局異常處理的理想工具。

環(huán)境準(zhǔn)備

創(chuàng)建WinForm項(xiàng)目

首先,創(chuàng)建一個新的WinForm應(yīng)用程序項(xiàng)目。

安裝NLog包

通過NuGet包管理器安裝NLog:

Install-Package NLog

或在Visual Studio中右鍵項(xiàng)目 -> 管理NuGet包 -> 搜索并安裝上述包。

三、配置NLog

基礎(chǔ)配置

項(xiàng)目中添加NLog.config文件。我們可以根據(jù)需求修改配置:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off">

<!-- 定義日志輸出目標(biāo) -->
<targets>
    <!-- 文件日志,按日期滾動 -->
    <target xsi:type="File" name="file" 
            fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=tostring}" 
            archiveFileName="${basedir}/logs/archives/{#}.log"
            archiveNumbering="Date"
            archiveEvery="Day"
            archiveDateFormat="yyyy-MM-dd"
            maxArchiveFiles="30" />

    <!-- 錯誤日志單獨(dú)存儲 -->
    <target xsi:type="File" name="errorfile" 
            fileName="${basedir}/logs/errors/${shortdate}.log"
            layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=tostring}" />
</targets>

<!-- 定義日志規(guī)則 -->
<rules>
    <!-- 所有日志 -->
    <logger name="*" minlevel="Info" writeTo="file" />
    <!-- 僅錯誤日志 -->
    <logger name="*" minlevel="Error" writeTo="errorfile" />
</rules>
</nlog>

圖片圖片

自定義配置(可選)

根據(jù)項(xiàng)目需求,你可以添加更多的輸出目標(biāo),如:

  • 數(shù)據(jù)庫日志
  • 郵件通知
  • Windows事件日志
  • 網(wǎng)絡(luò)日志等

實(shí)現(xiàn)全局異常處理

創(chuàng)建Logger工具類

首先,創(chuàng)建一個Logger工具類,封裝NLog的使用:

using NLog;
using System;

namespace WinFormNLogDemo
{
    publicstaticclass LogHelper
    {
        // 創(chuàng)建NLog實(shí)例
        privatestatic readonly Logger logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// 記錄信息日志
        /// </summary>
        /// <param name="message">日志消息</param>
        public static void Info(string message)
        {
            logger.Info(message);
        }

        /// <summary>
        /// 記錄警告日志
        /// </summary>
        /// <param name="message">警告消息</param>
        public static void Warn(string message)
        {
            logger.Warn(message);
        }

        /// <summary>
        /// 記錄錯誤日志
        /// </summary>
        /// <param name="ex">異常對象</param>
        /// <param name="message">附加消息</param>
        public static void Error(Exception ex, string message = "")
        {
            if (string.IsNullOrEmpty(message))
            {
                logger.Error(ex);
            }
            else
            {
                logger.Error(ex, message);
            }
        }

        /// <summary>
        /// 記錄致命錯誤日志
        /// </summary>
        /// <param name="ex">異常對象</param>
        /// <param name="message">附加消息</param>
        public static void Fatal(Exception ex, string message = "")
        {
            if (string.IsNullOrEmpty(message))
            {
                logger.Fatal(ex);
            }
            else
            {
                logger.Fatal(ex, message);
            }
        }
    }
}

全局異常處理器

接下來,在Program.cs中添加全局異常捕獲代碼:

namespace AppNLog
{
    internal staticclass Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            // 設(shè)置應(yīng)用程序異常處理
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            // 處理UI線程異常
            Application.ThreadException += Application_ThreadException;
            // 處理非UI線程異常
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            // 啟動應(yīng)用程序
            LogHelper.Info("應(yīng)用程序啟動");
            Application.Run(new Form1());
        }

        /// <summary>
        /// 處理UI線程異常
        /// </summary>
        private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            try
            {
                // 記錄異常日志
                LogHelper.Error(e.Exception, "UI線程異常");

                // 向用戶顯示友好錯誤消息
                MessageBox.Show(
                    "程序遇到了一個問題,已記錄異常信息。\n\n" +
                    "錯誤信息: " + e.Exception.Message,
                    "應(yīng)用程序錯誤",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                try
                {
                    LogHelper.Fatal(ex, "處理UI線程異常時發(fā)生錯誤");
                }
                catch
                {
                    // 如果日志記錄也失敗,使用消息框作為最后手段
                    MessageBox.Show("無法記錄異常信息: " + ex.Message, "嚴(yán)重錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// 處理非UI線程異常
        /// </summary>
        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            try
            {
                Exception ex = e.ExceptionObject as Exception;

                // 記錄異常日志
                if (ex != null)
                {
                    LogHelper.Fatal(ex, "非UI線程異常");
                }
                else
                {
                    LogHelper.Fatal(new Exception("未知異常類型"),
                        "發(fā)生未知類型的非UI線程異常: " + e.ExceptionObject.ToString());
                }

                // 如果異常導(dǎo)致應(yīng)用程序終止,記錄這一信息
                if (e.IsTerminating)
                {
                    LogHelper.Fatal(new Exception("應(yīng)用程序即將終止"), "由于未處理的異常,應(yīng)用程序即將關(guān)閉");

                    MessageBox.Show(
                        "程序遇到了一個嚴(yán)重問題,必須關(guān)閉。\n請聯(lián)系技術(shù)支持獲取幫助。",
                        "應(yīng)用程序即將關(guān)閉",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                try
                {
                    LogHelper.Fatal(ex, "處理非UI線程異常時發(fā)生錯誤");
                }
                catch
                {
                    // 如果日志記錄也失敗,使用消息框作為最后手段
                    MessageBox.Show("無法記錄異常信息: " + ex.Message, "嚴(yán)重錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
    }
}

在界面添加測試按鈕

接下來,在MainForm中添加幾個按鈕,用于測試不同類型的異常:

namespace AppNLog
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 測試UI線程異常
        /// </summary>
        private void btnTestUIException_Click(object sender, EventArgs e)
        {
            LogHelper.Info("準(zhǔn)備測試UI線程異常");

            // 故意制造一個異常
            string str = null;
            int length = str.Length; // 這里會引發(fā)NullReferenceException
        }

        /// <summary>
        /// 測試非UI線程異常
        /// </summary>
        private void btnTestNonUIException_Click(object sender, EventArgs e)
        {
            LogHelper.Info("準(zhǔn)備測試非UI線程異常");

            // 在新線程中拋出異常
            Task.Run(() =>
            {
                // 故意制造一個異常
                int[] numbers = newint[5];
                int value = numbers[10]; // 這里會引發(fā)IndexOutOfRangeException
            });
        }

        /// <summary>
        /// 測試文件操作異常
        /// </summary>
        private void btnTestFileException_Click(object sender, EventArgs e)
        {
            LogHelper.Info("準(zhǔn)備測試文件操作異常");

            try
            {
                // 嘗試讀取一個不存在的文件
                string content = File.ReadAllText("非存在文件.txt");
            }
            catch (Exception ex)
            {
                // 局部異常處理示例
                LogHelper.Error(ex, "文件操作失敗");
                MessageBox.Show("無法讀取文件,詳情請查看日志。", "文件錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// 記錄普通日志
        /// </summary>
        private void btnLogInfo_Click(object sender, EventArgs e)
        {
            LogHelper.Info("這是一條信息日志,記錄于: " + DateTime.Now.ToString());
            MessageBox.Show("日志已記錄", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}

圖片圖片

圖片圖片

高級功能實(shí)現(xiàn)

異常信息擴(kuò)展

為了更好地記錄異常發(fā)生時的上下文環(huán)境,我們可以擴(kuò)展異常信息:

using NLog;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace WinFormNLogDemo
{
    publicstaticclass ExceptionExtensions
    {
        /// <summary>
        /// 獲取詳細(xì)的異常信息,包括內(nèi)部異常、堆棧跟蹤等
        /// </summary>
        public static string GetDetailedErrorMessage(this Exception ex)
        {
            if (ex == null) returnstring.Empty;

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("========== 異常詳細(xì)信息 ==========");
            sb.AppendLine($"發(fā)生時間: {DateTime.Now}");
            sb.AppendLine($"異常類型: {ex.GetType().FullName}");
            sb.AppendLine($"異常消息: {ex.Message}");

            // 獲取應(yīng)用程序版本信息
            sb.AppendLine($"應(yīng)用版本: {Assembly.GetExecutingAssembly().GetName().Version}");

            // 記錄操作系統(tǒng)信息
            sb.AppendLine($"操作系統(tǒng): {Environment.OSVersion}");
            sb.AppendLine($".NET版本: {Environment.Version}");

            // 堆棧跟蹤
            if (!string.IsNullOrEmpty(ex.StackTrace))
            {
                sb.AppendLine("堆棧跟蹤:");
                sb.AppendLine(ex.StackTrace);
            }

            // 內(nèi)部異常
            if (ex.InnerException != null)
            {
                sb.AppendLine("內(nèi)部異常:");
                sb.AppendLine(GetInnerExceptionDetails(ex.InnerException));
            }

            sb.AppendLine("===================================");

            return sb.ToString();
        }

        /// <summary>
        /// 遞歸獲取內(nèi)部異常信息
        /// </summary>
        private static string GetInnerExceptionDetails(Exception exception, int level = 1)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine($"[內(nèi)部異常級別 {level}]");
            sb.AppendLine($"類型: {exception.GetType().FullName}");
            sb.AppendLine($"消息: {exception.Message}");

            if (!string.IsNullOrEmpty(exception.StackTrace))
            {
                sb.AppendLine("堆棧跟蹤:");
                sb.AppendLine(exception.StackTrace);
            }

            if (exception.InnerException != null)
            {
                sb.AppendLine(GetInnerExceptionDetails(exception.InnerException, level + 1));
            }

            return sb.ToString();
        }
    }
}

然后,修改LogHelper類使用這個擴(kuò)展方法:

/// <summary>
/// 記錄錯誤日志(增強(qiáng)版)
/// </summary>
public static void ErrorDetailed(Exception ex, string message = "")
{
    string detailedMessage = ex.GetDetailedErrorMessage();

    if (string.IsNullOrEmpty(message))
    {
        logger.Error(detailedMessage);
    }
    else
    {
        logger.Error($"{message}\n{detailedMessage}");
    }
}

圖片圖片

日志查看器集成

為了方便在應(yīng)用程序內(nèi)部查看日志,可以添加一個簡單的日志查看器:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AppNLog
{
    public partial class FrmLogViewer : Form
    {
        privatestring logDirectory;
        public FrmLogViewer()
        {
            InitializeComponent();
            logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");

        }

        /// <summary>
        /// 加載日志文件列表
        /// </summary>
        private void LoadLogFiles()
        {
            try
            {
                listBoxLogFiles.Items.Clear();

                if (!Directory.Exists(logDirectory))
                {
                    MessageBox.Show("日志目錄不存在", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                string[] logFiles = Directory.GetFiles(logDirectory, "*.log");
                foreach (string file in logFiles)
                {
                    listBoxLogFiles.Items.Add(Path.GetFileName(file));
                }

                // 加載錯誤日志
                string errorDirectory = Path.Combine(logDirectory, "errors");
                if (Directory.Exists(errorDirectory))
                {
                    string[] errorFiles = Directory.GetFiles(errorDirectory, "*.log");
                    foreach (string file in errorFiles)
                    {
                        listBoxLogFiles.Items.Add("錯誤/" + Path.GetFileName(file));
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error(ex, "加載日志文件列表時出錯");
                MessageBox.Show("無法加載日志文件列表: " + ex.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// 選擇日志文件
        /// </summary>
        private void listBoxLogFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                if (listBoxLogFiles.SelectedItem == null) return;

                string selectedFile = listBoxLogFiles.SelectedItem.ToString();
                string filePath;

                if (selectedFile.StartsWith("錯誤/"))
                {
                    filePath = Path.Combine(logDirectory, "errors", selectedFile.Substring(3));
                }
                else
                {
                    filePath = Path.Combine(logDirectory, selectedFile);
                }

                if (File.Exists(filePath))
                {
                    txtLogContent.Text = File.ReadAllText(filePath);
                }
                else
                {
                    txtLogContent.Text = "日志文件不存在或已被刪除";
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error(ex, "讀取日志文件內(nèi)容時出錯");
                txtLogContent.Text = "無法讀取日志文件: " + ex.Message;
            }
        }

        /// <summary>
        /// 刷新日志文件列表
        /// </summary>
        private void btnRefresh_Click(object sender, EventArgs e)
        {
            LoadLogFiles();
        }

        /// <summary>
        /// 清空所選日志內(nèi)容
        /// </summary>
        private void btnClear_Click(object sender, EventArgs e)
        {
            txtLogContent.Clear();
        }

        private void FrmLogViewer_Load(object sender, EventArgs e)
        {
            LoadLogFiles();
        }
    }
}

圖片圖片

應(yīng)用程序退出時記錄日志

確保在應(yīng)用程序退出時記錄相關(guān)信息:

// 在MainForm中添加
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    LogHelper.Info("應(yīng)用程序正常關(guān)閉");
}

應(yīng)用場景與最佳實(shí)踐

常見應(yīng)用場景

全局異常處理在以下場景特別有用:

  1. 企業(yè)級應(yīng)用需要高穩(wěn)定性和可維護(hù)性
  2. 分布式部署的客戶端便于收集用戶端異常信息
  3. 數(shù)據(jù)處理應(yīng)用確保數(shù)據(jù)處理過程中的異常被捕獲和記錄
  4. 長時間運(yùn)行的應(yīng)用提高應(yīng)用程序的持續(xù)可用性

最佳實(shí)踐

  1. 分層記錄按照不同級別記錄日志(Debug, Info, Warning, Error, Fatal)
  2. 結(jié)構(gòu)化日志使用結(jié)構(gòu)化格式,便于后續(xù)分析
  3. 關(guān)聯(lián)信息記錄用戶ID、操作ID等關(guān)聯(lián)信息
  4. 定期清理設(shè)置日志輪轉(zhuǎn)和清理策略,避免磁盤空間占用過大
  5. 異常分析定期分析日志,發(fā)現(xiàn)并解決常見問題
  6. 性能考慮日志記錄操作應(yīng)盡量異步化,避免影響主線程性能

常見問題與解決方案

日志文件權(quán)限問題

問題:應(yīng)用程序沒有寫入日志目錄的權(quán)限。

解決方案

  1. 確保應(yīng)用程序有寫入權(quán)限
  2. 使用User目錄下的路徑存儲日志
  3. 在安裝程序中正確設(shè)置權(quán)限
// 使用用戶目錄存儲日志
string logPath = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "YourAppName",
    "logs"
);

// 確保目錄存在
if (!Directory.Exists(logPath))
{
    Directory.CreateDirectory(logPath);
}

日志內(nèi)容過大

問題:日志文件增長過快,占用過多磁盤空間。

解決方案

  1. 使用日志分級策略,只記錄必要的信息
  2. 設(shè)置日志文件大小上限和輪轉(zhuǎn)策略
  3. 實(shí)現(xiàn)自動清理歷史日志的功能
<!-- NLog配置示例:限制文件大小和數(shù)量 -->
<target xsi:type="File" name="file" 
        fileName="${basedir}/logs/${shortdate}.log"
        archiveFileName="${basedir}/logs/archives/{#}.log"
        archiveNumbering="Date"
        archiveEvery="Day"
        archiveDateFormat="yyyy-MM-dd"
        maxArchiveFiles="30"
        archiveAboveSize="10485760" <!-- 10MB -->
        cnotallow="true"
        keepFileOpen="false" />

性能問題

問題:日志記錄影響應(yīng)用程序性能。

解決方案

  1. 使用異步日志記錄
  2. 優(yōu)化日志配置,減少I/O操作
  3. 批量寫入日志,而不是頻繁的單條寫入
<!-- NLog異步處理配置 -->
<targets async="true">
    <!-- 日志目標(biāo)配置 -->
</targets>

總結(jié)

通過本文的介紹,我們學(xué)習(xí)了如何在WinForm應(yīng)用程序中使用NLog實(shí)現(xiàn)全局異常處理,主要包括:

  1. NLog的安裝與配置
  2. 全局異常處理器的實(shí)現(xiàn)
  3. 自定義日志工具類
  4. 異常信息的擴(kuò)展與增強(qiáng)
  5. 內(nèi)置日志查看器的實(shí)現(xiàn)
  6. 應(yīng)用場景與最佳實(shí)踐

實(shí)現(xiàn)全局異常處理不僅能提高應(yīng)用程序的穩(wěn)定性和可維護(hù)性,還能為用戶提供更好的使用體驗(yàn)。在實(shí)際項(xiàng)目中,可以根據(jù)具體需求對本文提供的示例進(jìn)行擴(kuò)展和定制。

責(zé)任編輯:武曉燕 來源: 技術(shù)老小子
相關(guān)推薦

2022-03-04 08:31:07

Spring異常處理

2025-09-12 09:31:29

2025-02-17 00:25:00

Winform開發(fā)

2023-12-27 07:53:08

全局異常處理處理應(yīng)用

2009-02-06 14:11:36

ASP.NET.NET全局異常處理

2022-11-16 08:41:43

2019-01-24 16:11:19

前端全局異常數(shù)據(jù)校驗(yàn)

2021-01-04 05:44:54

框架日志

2017-08-10 10:28:43

SpringBootSpring

2019-02-22 08:25:19

數(shù)據(jù)清洗預(yù)處理機(jī)器學(xué)習(xí)

2009-12-25 10:01:23

WinForm程序

2024-11-29 07:45:38

C#離線語音文字

2021-04-20 10:50:38

Spring Boot代碼Java

2023-12-26 08:00:00

微前端React

2024-05-15 15:27:39

2024-01-03 16:01:23

2009-04-28 09:51:21

WinForm控制臺輸出

2020-03-16 17:20:02

異常處理Spring Boot

2024-03-26 09:25:35

RustSerde重命名

2020-09-22 12:22:32

Windows TerWindowsLinux
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 微山县| 陵川县| 商城县| 湖南省| 抚远县| 鄂伦春自治旗| 南岸区| 东丽区| 菏泽市| 靖边县| 盘锦市| 五大连池市| 东源县| 金秀| 时尚| 双鸭山市| 惠州市| 米易县| 三原县| 长阳| 东城区| 隆德县| 射洪县| 沂源县| 临汾市| 蓝田县| 清远市| 沂水县| 巴彦县| 荔波县| 蒲江县| 开鲁县| 渭南市| 克什克腾旗| 宝鸡市| 农安县| 即墨市| 郴州市| 驻马店市| 阿图什市| 虹口区|