在我們做項目的過程中,日志跟蹤異常是非常必要的,當程式釋出到伺服器上時,如果出現異常直接抛出給使用者這是非常不友好的。對于不懂程式的使用者來說,這回讓人感覺莫名其妙,對于那些程式高手,可能就是攻破這個網站的關鍵。
在asp.net 程式的web.config 配置檔案中有如下兩個節點作為程式異常配置:
(1)<customErrors>節點
<customErrors>節點用于定義 一些自定義錯誤資訊的資訊。此節點有Mode和defaultRedirect兩個屬性,其中 defaultRedirect屬性是一個可選屬性,表示應 用程式發生錯誤時重定向到的預設URL,如果沒有指定該屬性則顯示一般性錯誤。 Mode屬性是一個必選屬性,它有三個可能值,它們所代表的意義分别如下:
On 表示在本地和遠端使用者都會看到自定義錯誤資訊。
Off 禁用自定義錯誤資訊,本地和遠端使用者都會看到詳細的錯誤資訊。
RemoteOnly 表示本地使用者将看到詳細錯誤資訊,而遠端使用者将會看到自定義錯誤資訊。
這 裡有必要說明一下本地使用者和遠端使用者的概念。當我們通路asp.net應用程時所使用的機器和釋出asp.net應用程式所使用的機器為同一台機器時成為 本地使用者,反之則稱之為遠端使用者。在開發調試階段為了便于查找錯誤Mode屬性建議設定為Off,而在部署階段應将Mode屬性設定為On或者 RemoteOnly,以避免這些詳細的錯誤資訊暴露了程式代碼細節進而引來黑客的入侵。
(2) <error>子節點
在<customErrors>節點下還包含有< error>子節點,這個節點主要是根據伺服器的HTTP錯誤狀态代碼而重定向到我們自定義的錯誤頁面,注意要使<error>子節點 下的配置生效,必須将<customErrors>節點節點的Mode屬性設定為“On”。下面是一個例子:
<customErrors mode="On" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="403.htm" />
<error statusCode="404" redirect="404.htm" />
</customErrors>
以上配置可以讓程式方式異常的時候顯示更友好,不将異常資訊直接抛出給使用者。這個時候我們就需要達到使用者友好提示的同時還要跟蹤程式異常。下面介紹的是ORM架構中提供的一個小小的日志跟蹤程式。
1. 異常消息類型
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
代碼
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace CommonData.Log
7 {
8 public enum MessageType
9 {
10 /// <summary>
11 /// 未知資訊異常
12 /// </summary>
13 Unkown,
14
15 /// <summary>
16 ///普通資訊異常
17 /// </summary>
18 Common,
19
20 /// <summary>
21 /// 警告資訊異常
22 /// </summary>
23 Warning,
24
25 /// <summary>
26 /// 錯誤資訊異常
27 /// </summary>
28 Error,
29
30 /// <summary>
31 /// 成功資訊
32 /// </summary>
33 Success
34 }
35 }
36
以上定義的是一個枚舉類型,定義了日志的級别,分别為:未知資訊異常, 普通資訊異常 , 警告資訊異常, 錯誤資訊異常, 成功資訊.
寫程式的過程中可以根據不同的需要輸出不同級别的日志。
(2). 日志檔案建立類别
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
8 public enum LogType
11 /// 此枚舉訓示每天建立一個新的日志檔案
13 Daily,
16 /// 此枚舉訓示每周建立一個新的日志檔案
18 Weekly,
21 /// 此枚舉訓示每月建立一個新的日志檔案
23 Monthly,
26 /// 此枚舉訓示每年建立一個新的日志檔案
28 Annually
29 }
30 }
31
這也是一個枚舉類型,定義了日志檔案建立的标準。程式可以每天建立一個日志檔案,可以一周,一月,一年來建立一次。一般情況寫我是使用每天建立一個日志檔案,這樣可以每日下載下傳該日志檔案跟蹤程式異常。如果我們每周或更長時間建立一次,這樣在這個是時段内就不能即時清理改日志檔案(因為該檔案在目前線程中使用,後面介紹)。
(3).定義日志消息實體類
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
8 public class LogMessage
11 /// 日志記錄時間
13 private DateTime _time;
15 public DateTime Time
16 {
17 get
18 {
19 return _time;
20 }
21 set
22 {
23 _time = value;
24 }
25 }
26
27 /// <summary>
28 /// 日志記錄内容
29 /// </summary>
30 private string _content;
32 public string Content
33 {
34 get
35 {
36 return _content;
37 }
38 set
39 {
40 _content = value;
41 }
42 }
43
44 /// <summary>
45 /// 日志類型
46 /// </summary>
47 private MessageType _type;
48
49 public MessageType Type
50 {
51 get
52 {
53 return _type;
54 }
55 set
56 {
57 _type = value;
58 }
59 }
60
61
62 public LogMessage(): this("", MessageType.Unkown)
63 {
64 }
65
66 public LogMessage(string content, MessageType type): this(DateTime.Now, content, type)
67 {
68 }
69
70 public LogMessage(DateTime time,string content,MessageType type)
71 {
72 this._time = time;
73 this._content = content;
74 this._type = type;
75 }
76
77 public override string ToString()
78 {
79 return this._time.ToString() + "\t" + this._content + "\t";
80 }
81 }
82 }
83
這個類定義了日志消息的一些基本屬性,其中很重要的一點就是 重寫ToString 這個方法。該對象具有了新ToString方法,它傳回的是該消息的時間和類容。這樣在後面的日志記錄和輸出過程中比較友善,而不用每次都去連接配接字元串。
(4) 日志記錄
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
namespace CommonData.Log
{
public class Log:IDisposable
{
private static Queue<LogMessage> logMessages;
private static string path;
private static bool state;
private static LogType logtype;
private static DateTime time;
private static StreamWriter writer;
/// <summary>
/// 無參構造方法,指定日志檔案路徑為目前目錄,預設日志類型為每日日志類型
/// </summary>
public Log():this(".\\",LogType.Daily)
{
}
/// 構造方法,指定日志檔案路徑為目前目錄
/// <param name="t">日志檔案類型</param>
public Log(LogType t):this(".\\",t)
/// 構造方法
/// <param name="path">指定日志檔案路徑</param>
public Log(string filepath,LogType t)
if(logMessages==null)
{
state = true;
path= filepath;
logtype = t;
FileOpen();
logMessages = new Queue<LogMessage>();
Thread thread = new Thread(Work);
thread.Start();
}
/// 利用線程來寫入日志内容
private void Work()
while(true)
if(logMessages.Count>0)
{
LogMessage message = null;
lock (logMessages)
{
message = logMessages.Dequeue();
}
if(message!=null)
WriteLogMessage(message);
}
else
if(state)
Thread.Sleep(1);
else
FileClose();
/// 将日志内容寫入到文本
private void WriteLogMessage(LogMessage message)
try
if (writer == null)
FileOpen();
if (DateTime.Now >= time)
FileOpen();
writer.Write(message.Time);
writer.Write("\t");
writer.Write(message.Type);
writer.Write("\t\r\n");
writer.Write(message.Content);
writer.Write("\r\n\r\n");
writer.Flush();
catch (Exception e)
Console.WriteLine(e.Message);
/// 根據日志檔案類型判斷日志檔案名稱
/// 通過判斷檔案的到期時間标記将決定是否建立新檔案。
/// <returns></returns>
private string GetFileName()
DateTime now = DateTime.Now;
string format = "";
switch (logtype)
{
case LogType.Daily:
time = new DateTime(now.Year,now.Month,now.Day);
time = time.AddDays(1);
//time = time.AddMinutes(1);
format = "yyyyMMdd'.log'";
break;
case LogType.Weekly:
time = time.AddDays(7);
case LogType.Monthly:
time = new DateTime(now.Year,now.Month,1);
time = time.AddMonths(1);
format = "yyyyMM'.log'";
case LogType.Annually:
time = new DateTime(now.Year,1,1);
time = time.AddYears(1);
format = "yyyy'.log'";
return now.ToString(format);
/// 寫入日志檔案
public void Write(LogMessage message)
if (logMessages != null)
lock (logMessages)
logMessages.Enqueue(message);
/// <param name="text">日志内容</param>
/// <param name="type">消息類型</param>
public void Write(string text,MessageType type)
Write(new LogMessage(text,type));
/// <param name="now">目前時間</param>
public void Write(DateTime now, string text, MessageType type)
Write(new LogMessage(now,text,type));
/// <param name="e">異常對象</param>
public void Write(Exception e,MessageType type)
Write(new LogMessage(e.Message,type));
/// 打開檔案準備寫入内容
private void FileOpen()
writer = new StreamWriter(path+GetFileName(),true,Encoding.Default);
/// 關閉檔案流
private void FileClose()
if(writer!=null)
writer.Flush();
writer.Close();
writer.Dispose();
writer = null;
/// 釋放記憶體空間
public void Dispose()
state = false;
GC.SuppressFinalize(this);
}
}
這個才是這個日志系統的關鍵部分,程式設計的思路是程式啟動之後,程式開辟了另外一條線程,該線程專用于寫日志資訊。程式中使用了一個隊列消息,當程式發生異常或者使用者記錄日志,會将這個日志消息放入這個隊列中去,至于寫日志主程式就不用再去處理,隻要捕獲到異常主程式隻是負責将消息放入隊列就可以了。寫日志的工作是有開辟的線程完成的。該線程會根據定義在多長時間内建立新的日志檔案,還會不斷的去隊列中檢測是否有新的日志内容需要記錄,并且完成日志的記錄。正是因為這個線程,是以在日志記錄的過程中不能去清理這個日志檔案。是以要特别注意。