大部分具有 Exception 機制的程式語言都有提供全域的 Exception 處理,如果你已經有用 log 去記錄錯誤的習慣的話,與其在程式裡佈滿了 try catch,不如在全域處裡中去記錄沒有被 catch 的 Exception,這樣程式就可以更乾淨了,而且程式如果發生異常的關閉時也會進入全域處裡,可以確保 Exception 妥善地被記錄。
Console, WinForm 程式
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
using NLog;
namespace AppThreadException
{
static class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
[STAThread]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
public static void Main(string[] args)
{
/* ThreadException 用來攔截 UI 錯誤 */
Application.ThreadException += threadExceptionHandler;
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
Application.Run(new MyForm());
}
/// <summary>攔截 UI 錯誤</summary>
private static void threadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
_log.Fatal(e.Exception, "操作錯誤");
MessageBox.Show(e.Exception.Message, "操作錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
/// <summary>攔截不可挽回的錯誤,不能阻止程式關閉</summary>
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
MessageBox.Show(ex.Message, "執行錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
}
Web 程式
在 Global.asax.cs 中可以設定 Application_Error 就可以攔截未處裡的 Exception,除了用這個方法記錄錯誤,還可以用現有的套件(elmah)幫我們完成。
using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using NLog;
namespace MvcReport
{
public class MvcApplication : System.Web.HttpApplication
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
//...
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
_log.Fatal(ex, ex.Message);
}
//...
}
}
Net core 程式
Net core 的程式都是相同的方式,Web Request 的要另外配置
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using EverTop.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using NLog;
namespace MyWebApp
{
public class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
public static void Main(string[] args)
{
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
/* 用來攔截 Task 錯誤 */
TaskScheduler.UnobservedTaskException += unobservedTaskException;
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder
.UseStartup<Startup>()
)
.Build();
host.Run(); /* 啟動網站 */
}
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
}
private static void unobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
_log.Fatal(e.Exception, "執行錯誤");
e.SetObserved();
}
}
}
Net core 3.1 Web 程式
在 Startup.cs 中配置 middleware 進行 Exception 攔截
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (_env.IsProduction())
{
app.UseExceptionHandler("/Error");
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePagesWithReExecute("/Error");
/* 利用 middleware 進行 Exception 攔截 */
/* 這裡的順序很重要,不然會被前面 ExceptionHandler 處理掉就拿不到 Exception */
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
throw; /* 把 Exception 再丟出去給別人處理 */
}
});
}
週期性 Thread 的處裡方式
將主要邏輯寫在另外 method 裡,這樣可以專注在 Exception 上。
private bool _runFlag = false;
public void Start()
{
if (_runFlag) { return; }
_runFlag = true;
var thread = new Thread(() =>
{
while (_runFlag)
{
try
{
cycleHandler();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
}
Thread.Sleep(1000);
}
});
thread.Start();
}
public void Stop()
{
_runFlag = false;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
PHP 錯誤處理
Ref: set_error_handler, set_exception_handler
<?php
function error_handler($errno, $errstr, $errfile, $errline) {
if(error_reporting() === 0){ return; }
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_handler', E_ALL^E_NOTICE);
function exception_handler($exception) {
// 在這記錄 log
throw $exception;
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
//$a = 1 / 0;