如何检测Windows服务是否崩溃并实现日志记录与邮件告警?
解决Windows服务崩溃时的日志记录与邮件告警问题
先帮你解决之前OnShutdown未生效的小问题:大概率是因为没有设置CanShutdown = true(ServiceBase的默认值为false),系统不会给服务发送关机通知。你只需要在服务的构造函数里加上CanShutdown = true,OnShutdown方法就能正常触发了。
接下来重点说服务崩溃时的异常捕获与告警,核心思路是捕获全局未处理异常,在异常发生时写入事件日志并发送邮件,具体实现步骤如下:
1. 注册全局未捕获异常事件
在服务的OnStart方法中,给当前AppDomain注册UnhandledException事件,这样任何未被捕获的异常都会触发这个事件,我们就能在事件处理方法里做后续操作:
protected override void OnStart(string[] args) { // 注册全局未处理异常捕获 AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // 其他初始化逻辑... } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Exception ex = e.ExceptionObject as Exception; if (ex != null) { // 写入事件日志(标记为错误类型) WriteToEventLog($"服务崩溃,异常信息:{ex.Message}\n堆栈跟踪:{ex.StackTrace}", EventLogEntryType.Error); // 发送邮件告警 SendAlertEmail(ex); } }
2. 完善事件日志写入方法
你之前的WriteToEventLog可以优化一下,支持指定日志类型,同时注意权限问题(创建日志源需要管理员权限,建议在服务安装时提前创建):
private void WriteToEventLog(string logMessage, EventLogEntryType entryType = EventLogEntryType.Warning) { const string sourceName = "MyServiceCrashLog"; const string logName = "Application"; // 也可以使用自定义日志名,比如"MyServiceLogs" try { if (!EventLog.SourceExists(sourceName)) { // 注意:创建日志源需要管理员权限,建议在服务安装程序中执行此操作 EventLog.CreateEventSource(sourceName, logName); } using (var eventLog = new EventLog(logName)) { eventLog.Source = sourceName; eventLog.WriteEntry(logMessage, entryType, 1003); // 用1003作为崩溃事件的ID,和之前的关机事件区分 } } catch (Exception ex) { // 防止日志写入失败导致二次异常,可将异常信息写入临时文件(可选) File.AppendAllText(@"C:\Temp\ServiceLogError.txt", $"日志写入失败:{ex.Message}\n"); } }
3. 实现邮件告警功能
使用System.Net.Mail命名空间下的类实现邮件发送,注意配置正确的SMTP服务器信息,同时考虑服务运行账户的网络权限:
private void SendAlertEmail(Exception ex) { try { using (var smtpClient = new SmtpClient("your-smtp-server.com", 587)) // 例如smtp.office365.com、smtp.gmail.com { smtpClient.Credentials = new NetworkCredential("your-email@domain.com", "your-password-or-app-password"); smtpClient.EnableSsl = true; // 多数SMTP服务器需要SSL加密 var mailMessage = new MailMessage { From = new MailAddress("your-email@domain.com"), Subject = "紧急:MyService服务崩溃告警", Body = $"服务于{DateTime.Now:yyyy-MM-dd HH:mm:ss}崩溃,异常详情:\n\n异常消息:{ex.Message}\n\n堆栈跟踪:{ex.StackTrace}", IsBodyHtml = false }; mailMessage.To.Add("admin-alert@domain.com"); // 接收告警的目标邮箱 // 建议用异步发送避免阻塞,替换为smtpClient.SendMailAsync(mailMessage)即可 smtpClient.Send(mailMessage); } } catch (Exception emailEx) { // 邮件发送失败也要记录日志 WriteToEventLog($"邮件告警发送失败:{emailEx.Message}", EventLogEntryType.Error); } }
4. 关键注意事项
- 权限问题:
- 创建事件日志源需要管理员权限,建议在服务安装程序(比如使用
InstallUtil工具)中提前创建,避免服务运行时因权限不足创建失败。 - 服务运行的账户需要具备:写入事件日志的权限、访问SMTP服务器的网络权限。使用LocalSystem账户通常权限足够,若用自定义账户需手动配置这些权限。
- 创建事件日志源需要管理员权限,建议在服务安装程序(比如使用
- 异常处理鲁棒性:日志写入和邮件发送的代码自身要加
try-catch,避免因这些操作失败导致服务二次崩溃。 - 异步优化:邮件发送是IO操作,建议使用异步方法
smtpClient.SendMailAsync,避免阻塞服务的异常处理流程(注意在异步方法中正确捕获异常)。
内容的提问来源于stack exchange,提问作者Sharpeye500
相关产品推荐
相关产品推荐

