在之前的文章中,有介紹過如何使用Elmah來記錄與收集網站的錯誤資訊,而且Elmah也提供了十分好用的錯誤瀏覽介面,可以讓我們觀察線上網站的錯誤記錄,然而在實際營運網站時比較尷尬的是,線上網站發生錯誤的頻率是相當高的,就算過濾掉404等還是相當可觀,我們不可能無時無刻都在觀察Elmah的網頁,但將錯誤通知打開通知訊息又會過多,相當令人困擾,然而我們也不可能放著這些錯誤訊息不修正,因此有一個有效的錯誤訊息管理方法也是很重要的,今天將向大家分享如何管理網站的錯誤訊息。
※建立每日錯誤統計報表
為了有效率的管理網站的錯誤訊息,我們可以每天統計各種錯誤發生的次數,再根據目前的專案進度或是工作內容,將發生頻率最高或影響最大的錯誤排入日常處理,而我們在之前將錯誤記錄存放在SQL Server中也是為了可以方便進行統計分析。
在Visual Studio的Cloud Service專案中,建立一個背景工作角色
新增一個背景工作角色
新增一個實體資料模型,用來存取Elmah的資料庫
設定連線字串
選擇所要加入的資料表
新增錯誤資料模型,用來存放及呈現錯誤資料
![](<br />
public class ErrorReportModel
{
public string ApplicationName { get; set; }
<br />
public string ExceptionType { get; set; }
<br />
public string ExceptionMessage { get; set; }
<br />
public int ExceptionCount { get; set; }
}
)
新增報表範本,並且修改屬性為內嵌資源,我們在這邊使用RazorEngine這套Library來幫助我們完成Email的內容,它可以解析Razor格式的樣板並且將資料代入執行
@foreach(var reports in Model)
{
<h2>@reports.Key</h2>
<table border="1">
<thead>
<tr>
<th>錯誤類型</th>
<th>錯誤訊息</th>
<th>錯誤次數</th>
</tr>
</thead>
<tbody>
@foreach(var report in reports.Value)
{
<tr>
<td>@report.ExceptionType</td>
<td>@report.ExceptionMessage</td>
<td>@report.ExceptionCount</td>
</tr>
}
</tbody>
</table>
}
新增一個排程工作,根據每天產生的錯誤進行分類,執行完成後送出報表 (使用Quartz.Net)
public class DailyExceptionReportJob : IJob
{
public void Execute(IJobExecutionContext context)
{
// 決定開始和結束時間
DateTime startTime = DateTime.Today.AddDays(-1).ToUniversalTime();
DateTime endTime = DateTime.Today.ToUniversalTime();
// 取得每日錯誤資料
List<ErrorReportModel> result = this.GetErrorsByDatetimeRange(startTime, endTime);
// 產生統計後資料
Dictionary<string, List<ErrorReportModel>> reports = this.GetReportModels(result);
// 產生HTML報表
string content = this.GetReportHtml(reports);
//寄送每日報表
this.MailReport(content);
}
private void MailReport(string content)
{
MailAddress from = new MailAddress("xxx@email.com", "xxxx", System.Text.Encoding.UTF8);
MailMessage mail = new MailMessage(from, new MailAddress("xxx@email.com"));
string subject = "Daily Error Report";
mail.Subject = subject;
mail.SubjectEncoding = System.Text.Encoding.UTF8;
string body = content;
mail.Body = body;
mail.BodyEncoding = System.Text.Encoding.UTF8;
mail.IsBodyHtml = true;
mail.Priority = MailPriority.High;
SmtpClient client = new SmtpClient();
client.Host = "smtp.gmail.com";
client.Port = 587;
client.Credentials = new NetworkCredential("xxx@email.com", "xxxx");
client.EnableSsl = true;
client.Send(mail);
}
private string GetReportHtml(Dictionary<string, List<ErrorReportModel>> reports)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream str = assembly.GetManifestResourceStream("Ap iSample.BatchJob.BL.DailyException.ErrorReportTemplate.cshtml");
StreamReader sr = new StreamReader(str, System.Text.Encoding.UTF8);
var template = sr.ReadToEnd();
string content = Razor.Parse(template, reports);
return content;
}
private Dictionary<string, List<ErrorReportModel>> GetReportModels(List<ErrorReportModel> result)
{
Dictionary<string, List<ErrorReportModel>> reports = new Dictionary<string, List <ErrorReportModel>>();
foreach (var applicationErrors in result.GroupBy(i => i.ApplicationName))
{
var report = applicationErrors.GroupBy(i => i.ExceptionType)
.Select(
i => new ErrorReportModel
{
ApplicationName = applicationErrors.Key,
ExceptionType = i.Key,
ExceptionMessage = i.First().ExceptionMessage,
ExceptionCount = i.Count()
})
.OrderByDescending(i => i.ExceptionCount)
.ToList();
reports.Add(applicationErrors.Key, report);
}
return reports;
}
private List<ErrorReportModel> GetErrorsByDatetimeRange(DateTime startTime, DateTime endTime)
{
List<ErrorReportModel> result;
using (ExceptionDBContext dbContext = new ExceptionDBContext())
{
result = dbContext.ELMAH_Error
.Where(
i => i.TimeUtc >= startTime &&
i.TimeUtc < endTime)
.Select(
i => new ErrorReportModel()
{
ApplicationName = i.Application,
ExceptionType = i.Type,
ExceptionMessage = i.Message
})
.ToList();
}
return result;
}
}
在WorkerRole中設定工作排程,每天執行一次
public class WorkerRole : RoleEntryPoint
{
private IScheduler scheduler;
private ManualResetEvent CompletedEvent = new ManualResetEvent(false);
public override void Run()
{
DateTimeOffset runTime = DateBuilder.EvenMinuteDate(DateTime.UtcNow);
DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 10);
var job = JobBuilder.Create<DailyExceptionReportJob>()
.WithIdentity("DailyExceptionJob", null)
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("default", null)
.StartAt(runTime)
.WithCronSchedule("* 3 * * * ?")
.Build();
scheduler.ScheduleJob(job, trigger);
this.CompletedEvent.WaitOne();
}
public override bool OnStart()
{
// construct a scheduler factory
ISchedulerFactory factory = new StdSchedulerFactory();
// get a scheduler
this.scheduler = factory.GetScheduler();
this.scheduler.Start();
return base.OnStart();
}
public override void OnStop()
{
this.scheduler.Clear();
this.CompletedEvent.Set();
base.OnStop();
}
}
發行上雲端,每日執行後可以得到報表資料如下
有了每日報表之後,就算我們沒有隨時的關切線上Elmah的錯誤清單,每天也可以收到當天網站的錯誤統計資料,這有助於我們針對網站的潛在問題及風險進行管理,也可以當作網站健康度指標的依據!
延伸閱讀:
* 用Razor語法寫範本-RazorEngine組件介紹
※定期清除Elmah錯誤記錄
隨著時間的累積,Elmah的錯誤記錄也會隨之增加,然而由於網站持續的在改善並更新程式碼,我們不可能經常的花時間清查過久以前的Log,而為了避免錯誤資料過於龐大,我們習慣會設一個排程清除Elmah的錯誤資料,只保留比較近的日期。
在ExceptionDB新增StoredProcedure,為了避免一次刪除大量資料影響資料庫效能,我們這邊判斷過期資料後,一次只刪除小量資料
USE [ExceptionDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[ELMAH_ClearErrorsLogs]
AS
BEGIN
SET NOCOUNT ON;
DECLARE @clearDate char(8) = format(dateadd(d,-30,getdate()),'yyyyMMdd');
--Remove temp table
IF (OBJECT_ID('tempdb..##tmpOverdateErrorId') IS NOT NULL)
drop table #tmpOverdateErrorId;
--Move overdate error id to temp table
SELECT [ErrorId]
into #tmpOverdateErrorId
FROM [dbo].[ELMAH_Error]
WHERE [TimeUtc] < @clearDate;
--Remove elmah errors
WHILE 1 = 1
BEGIN
WAITFOR DELAY '00:00:01';
DELETE TOP(5) [dbo].[ELMAH_Error]
FROM [dbo].[ELMAH_Error] t1
INNER JOIN #tmpOverdateErrorId t2 ON t1.[ErrorId] = t2.[ErrorId];
IF @@ROWCOUNT = 0
BREAK;
END;
END
GO
更新EntityFramework的DbContext
新增Elmah_ClearErrorsLog
新增一個清除ErrorLog的工作
public class ClearErrorLogsJob : IJob
{
public void Execute(IJobExecutionContext context)
{
using (ExceptionDBContext dbContext = new ExceptionDBContext())
{
var count = dbContext.ELMAH_ClearErrorsLogs();
Trace.WriteLine("Clear error log count: " + count);
}
}
}
在Worker Role增加一個排程
var job = JobBuilder.Create<ClearErrorLogsJob>()
.WithIdentity("ClearErrorLogsJob", null)
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("default", null)
.StartAt(runTime)
.WithCronSchedule("* 3 * * * ?")
.Build();
scheduler.ScheduleJob(job, trigger);
發行至雲端,設定好排程工作的執行,這麼一來我們就可以只保留需要範圍的錯誤記錄了!
※本日小結
藉由排程工作的幫助,我們可以將系統的數據得到實際的報表,這麼一來就可以很方便的了解網站的實際狀況,並且安排對應的工作進行處理,也可以避免線上網站有大量的錯誤卻沒有被發現,關於今天的內容,歡迎大家一起討論喔^_^