全局异常捕获需用AppDomain.UnhandledException(.NET Framework)或HostBuilder.UseExceptionHandler(.NET Core+),但仅限轻量操作;崩溃堆栈应通过Exception.ToString()完整记录并落盘,再由独立进程发邮件。

AppDomain.CurrentDomain.UnhandledException 怎么捕获全局异常
程序崩溃时没机会走正常逻辑,必须靠 .NET 的全局异常钩子。关键不是“怎么发邮件”,而是“怎么确保崩溃那一刻的堆栈不丢”。
这个事件只在非托管线程或未被 try/catch 拦截的异常下触发,但 UnhandledExceptionEventArgs.IsTerminating 为 true 时,进程即将退出,不能再做耗时操作(比如发网络请求)——这是最常踩的坑。
- 务必在事件处理器里只做轻量动作:记录日志文件、写入本地临时文件、或启动一个独立进程去发邮件
- 不要在该事件里直接调用
SmtpClient.Send或 await 任何异步方法 - 如果用了 .NET Core/.NET 5+,
AppDomain已废弃,得改用AppContext.SetSwitch("Microsoft.Extensions.Logging.Console.DisableColors", true)配合HostBuilder的UseExceptionHandler,但注意:它只捕获 HTTP 请求层异常,对主线程崩溃无效
SmtpClient 发邮件失败的典型原因和绕过方式
本地调试时能发,部署到服务器就静默失败?大概率是 SMTP 认证或端口被拦截。别信“配置没错”,先验证基础链路。
SmtpClient 在 .NET 6+ 已标记为 obsolete,但它仍是目前最直接可用的同步发送方式;替代方案如 MailKit 更可靠,但引入新依赖。
- 检查
SmtpClient.EnableSsl:Gmail 要设为true,但端口得配587(不是 465);某些企业邮箱反而禁用 SSL,得关掉并用25 - 密码不能是账号登录密码,得用应用专用密码(Gmail)或 SMTP 授权码(QQ 邮箱)
- 防火墙/杀毒软件可能拦截
SmtpClient的 outbound 连接,临时关闭测试一次就能确认 - 发件人地址必须和认证账号一致,否则多数 SMTP 服务会 530 错误拒绝
怎么把崩溃堆栈完整塞进邮件正文
直接 e.ExceptionObject.ToString() 看似方便,但会丢掉 InnerException 和 Source 信息,线上定位时根本没法还原现场。
堆栈不是越长越好,而是要带上下文:时间、进程 ID、机器名、.NET 版本、以及最关键的 Exception.TargetSite —— 它能告诉你到底哪一行代码崩了(哪怕 PDB 没部署)。
- 用
Exception.ToString()是底线,但推荐手动拼接:$"{ex.GetType().Name}: {ex.Message}\r\n{ex.StackTrace}\r\nInner: {ex.InnerException?.ToString()}" - 避免直接
ex.StackTrace,它不含文件名和行号(除非有 PDB 且路径正确),优先用ex.ToString(),它自动包含所有嵌套层级 - 加个时间戳和
Environment.MachineName,多个实例部署时能快速区分是哪台机器出的问题
Windows 服务或后台进程崩溃后发不了邮件怎么办
服务账户默认没交互式桌面权限,也常被限制网络访问。这时候发邮件不是“代码写得不对”,而是运行环境根本不让干。
最稳的解法不是硬刚 SMTP,而是把错误落地,再由另一个有权限的进程来处理——比如 Windows 计划任务每分钟扫一次错误日志文件。
- 崩溃时写入 JSON 格式文件到
C:\ProgramData\MyApp\CrashLogs\,含完整Exception.ToString()和时间戳 - 另起一个最小权限的控制台程序,用
ScheduledTask或System.Timers.Timer定期读取并发送,它可配置为 Administrator 用户运行 - 别用
File.WriteAllText直接覆盖,用File.AppendAllText+ 时间戳命名,防止并发写入丢失
真正难的从来不是“怎么发邮件”,而是怎么让崩溃信息跨过权限隔离、网络限制、进程生命周期这些现实约束,稳稳落到你邮箱里。细节都在配置和部署方式里,不在那几行 SmtpClient 代码中。


















