IIS日誌存入數據庫之二:ETW

在上一篇文章《IIS日誌存入數據庫之一:ODBC》中,我提到了ODBC方式保存的缺點,即:沒法保存響應時間以及接收和響應的字節數。html

 

若是必定要獲取響應時間以及接收和響應的字節數的話,就要另想辦法了。備選的方法有:sql

(1)尋找有沒有現成的IIS日誌模塊。數據庫

(2)重寫IIS的日誌模塊。windows

(3)在現有的IIS日誌模塊的基礎上進行改造。數組

 

下面是對三種備選方法的探索:服務器

(1)針對方法1,在IIS的官網上找到了一個名爲Adanced logging的日誌模塊,,,然並卵。session

(2)針對方法2,改寫的工做量較大,且能夠會性能問題,故拋棄。框架

(3)針對方法3,發現iis日誌的保存目標能夠爲ETW事件,故採用。以下圖所示:ide

 

下面介紹一下如何訂閱IIS的ETW事件。ps:關於ETW事件的介紹,請查看個人另一篇文章:《在.net中使用ETW事件的方法函數

核心代碼

 1         private void button1_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5 
 6                 using (var session = new TraceEventSession("IIS-Logging"))         // 建立一個session
 7                 {
 8                     session.EnableProvider("Microsoft-Windows-IIS-Logging"); // Microsoft-Windows-IIS-Logging 是IIS日誌模塊提供的provider的名稱
 9 
10                     session.Source.Registered.All += Registered_All;  // 註冊事件處理函數
11 
12                     session.Source.Process();  // Wait for incoming events (forever).  
13                 }
14             }
15             catch
16             {
17             }
18         }
19 
20 
21         /// <summary>
22         /// 事件處理函數
23         /// </summary>
24         /// <param name="data"></param>
25         void Registered_All(TraceEvent data)
26         {
27             try
28             {
29                 string logString = data.FormattedMessage; // 返回日誌項的字符串形式
30                 
31                 // 將文本轉換成對象
32                 IISLogEntry logEntry = new IISLogEntry(logString);
33 
34                 IISLogEntry.Add(logEntry);
35             }
36             catch
37             {
38             }
39         }

     上述代碼建立會話了,綁定了事件源(IIS的ETW事件提供者),訂閱了事件源。這段代碼有兩個關鍵點:

  (1)怎麼知道IIS日誌模塊的事件提供程序的名稱是「Microsoft-Windows-IIS-Logging」呢?答案是經過命令行指令:logman query providers。下面是這個指令返回的結果:

 

  (2)在綁定ETW事件處理程序的時候,咱們使用了session.Source.Registered, session.Source.Registered返回了一個RegisteredTraceEventParser對象,經過這個對象咱們才能在事件處理程序中使用data.FormattedMessage來取得日誌項的內容。有關RegisteredTraceEventParser,官方文檔中有這麼一句:

  RegisteredTraceEventParser – which knows about any event provider that registers itself with the operating system (using the wevtutil command)).  

  This includes most providers that ship with the windows operating system that are NOT the kernel provider or EventSources.  You can see a list of such providers with the ‘logman query providers’ command.

 

外圍代碼(解析日誌字符串,存入數據庫)

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel.DataAnnotations;
  4 using System.ComponentModel.DataAnnotations.Schema;
  5 
  6 
  7 
  8 
  9 
 10 [Table("IISLogEntry")]
 11 public class IISLogEntry
 12 {
 13     [Key]
 14     public long Id { get; set; }
 15 
 16 
 17     /// <summary>
 18     /// 服務端信息
 19     /// </summary>
 20     public Server Server { set; get; }
 21 
 22     /// <summary>
 23     /// 客戶端信息
 24     /// </summary>
 25     public Client Client { get; set; }
 26 
 27 
 28     /// <summary>
 29     /// 請求信息
 30     /// </summary>
 31     public Request Request { get; set; }
 32 
 33 
 34     /// <summary>
 35     /// 響應信息
 36     /// </summary>
 37     public Response Response { get; set; }
 38 
 39 
 40 
 41 
 42     /// <summary>
 43     /// 構造函數
 44     /// </summary>
 45     public IISLogEntry() { }
 46 
 47 
 48 
 49     /// <summary>
 50     /// 構造函數。使用字符串來構造一個日誌對象
 51     /// </summary>
 52     /// <param name="logString"></param>
 53     public IISLogEntry(string logString)
 54     {
 55         try
 56         {
 57             string[] array = logString.Trim().Split(new char[] { ' ' });
 58 
 59             Dictionary<string, string> dictionary = new Dictionary<string, string>();
 60             //將數組中的值放入到字典中,奇數項爲key,偶數項爲value
 61             for (int i = 0; i < array.Length; i++)
 62             {
 63                 if (i % 2 == 0)
 64                 {
 65                     dictionary.Add(array[i], "");
 66                 }
 67                 if (i % 2 == 1)
 68                 {
 69                     dictionary[array[i - 1]] = array[i];
 70                 }
 71             }
 72 
 73             this.Server = new Server()
 74             {
 75                 IP = dictionary["s-ip"],
 76                 Port = int.Parse(dictionary["s-port"]),
 77                 Name = dictionary["s-computername"]
 78             };
 79             this.Client = new Client()
 80             {
 81                 IP = dictionary["c-ip"],
 82                 UserAgent = dictionary["cs(User-Agent)"],
 83                 UserName = dictionary["cs-username"]
 84             };
 85             this.Request = new Request()
 86             {
 87                 RequestDateTime = DateTime.Now,
 88                 Method = dictionary["cs-method"],
 89                 UriResource = dictionary["cs-uri-stem"],
 90                 UriQuery = dictionary["cs-uri-query"],
 91                 BytesReceived = this.ConvertToLong(dictionary["cs-bytes"])
 92             };
 93             this.Response = new Response()
 94             {
 95                 TimeTaken = this.ConvertToLong(dictionary["time-taken"]),
 96                 BytesSent = this.ConvertToLong(dictionary["sc-bytes"]),
 97                 Status = int.Parse(dictionary["sc-status"]),
 98                 SubStatus = int.Parse(dictionary["sc-substatus"]),
 99                 Win32Status = int.Parse(dictionary["sc-win32-status"]),
100             };
101         }
102         catch (Exception exp)
103         {
104             throw new Exception("格式轉換失敗。\n日誌字符串爲:" + logString + "\n異常信息:" + exp.Message);
105         }
106     }
107 
108 
109 
110 
111 
112     //**************************** CRUD ************************************
113     public static bool Add(IISLogEntry data)
114     {
115         using (IISLogDbContext db = new IISLogDbContext())
116         {
117             db.IISLogEntries.Add(data);
118 
119             try
120             {
121                 db.SaveChanges();
122                 return true;
123             }
124             catch
125             {
126                 return false;
127             }
128         }
129     }
130 
131 
132 
133 
134 
135     /// <summary>
136     /// 將帶千分號的字符串轉換成長整型。如:4,939
137     /// </summary>
138     /// <param name="str"></param>
139     /// <returns></returns>
140     private long ConvertToLong(string str)
141     {
142         string str2 = str.Replace(",", "");
143         return long.Parse(str2);
144     }
145 
146 }
147 
148 
149 
150 
151 
152 /// <summary>
153 /// 服務端信息
154 /// </summary>
155 [ComplexType]
156 public class Server
157 {
158     /// <summary>
159     /// 服務器名稱。對應:s-computername
160     /// </summary>
161     [MaxLength(50)]
162     public string Name { set; get; }
163 
164 
165     /// <summary>
166     /// 服務器IP。對應:s-ip
167     /// </summary>
168     [MaxLength(15)]
169     public string IP { set; get; }
170 
171 
172     /// <summary>
173     /// 服務器端口。對應:s-port
174     /// </summary>
175     public int Port { set; get; }
176 
177 
178 }
179 
180 
181 
182 /// <summary>
183 /// 客戶端信息
184 /// </summary>
185 [ComplexType]
186 public class Client
187 {
188     /// <summary>
189     /// 客戶端IP。對應:c-ip
190     /// </summary>
191     [MaxLength(15)]
192     public string IP { set; get; }
193 
194 
195     /// <summary>
196     /// 客戶端所使用的用戶代理。對應: cs(User-Agent)
197     /// </summary>
198     [MaxLength(200)]
199     public string UserAgent { set; get; }
200 
201 
202     /// <summary>
203     /// 登陸的用戶名。對應: cs-username
204     /// </summary>
205     [MaxLength(20)]
206     public string UserName { set; get; }
207 
208 
209 }
210 
211 
212 
213 
214 
215 
216 /// <summary>
217 /// 請求信息
218 /// </summary>
219 [ComplexType]
220 public class Request
221 {
222     /// <summary>
223     /// 請求時間。對應:date和time
224     /// </summary>
225     public DateTime RequestDateTime { set; get; }
226 
227 
228     /// <summary>
229     /// 方法。對應:cs-method
230     /// </summary>
231     [MaxLength(10)]
232     public string Method { set; get; }
233 
234 
235     /// <summary>
236     /// uri資源。對應:cs-uri-stem 
237     /// </summary>
238     [MaxLength(1000)]
239     public string UriResource { get; set; }
240 
241 
242     /// <summary>
243     /// uri查詢。對應:cs-uri-query 
244     /// </summary>
245     [MaxLength(1000)]
246     public string UriQuery { get; set; }
247 
248 
249 
250     /// <summary>
251     /// 接收的字節數,單位爲byte。對應:cs-bytes
252     /// </summary>
253     public long BytesReceived { get; set; }
254 
255 
256 
257 }
258 
259 
260 
261 /// <summary>
262 /// 響應信息
263 /// </summary>
264 [ComplexType]
265 public class Response
266 {
267     /// <summary>
268     /// 狀態碼。對應:sc-status 
269     /// </summary>
270     public int Status { get; set; }
271 
272 
273     /// <summary>
274     /// 子狀態碼。 對應:sc-substatus
275     /// </summary>
276     public int SubStatus { get; set; }
277 
278     /// <summary>
279     /// win32狀態碼。 對應:sc-win32-status
280     /// </summary>
281     public int Win32Status { get; set; }
282 
283 
284     /// <summary>
285     ///  發送的字節數,單位爲byte。對應:sc-bytes
286     /// </summary>
287     public long BytesSent { get; set; }
288 
289 
290     /// <summary>
291     ///  所用時間,單位爲ms。對應: time-taken
292     /// </summary>
293     public long TimeTaken { get; set; }
294 
295 
296 }

 上述代碼是日誌項實體。這裏咱們使用了EF做爲ORM框架,mssql做爲數據庫。

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;


public class IISLogDbContext : DbContext
{

    public IISLogDbContext()
        : base("IISLog")
    {
    }


    /// <summary>
    /// 這個類的註釋中的值,只用於在開發的時候進行選配
    /// </summary>
    static IISLogDbContext()
    {
        // Database.SetInitializer(new DropCreateDatabaseAlways<IISLogDbContext>());
        //  Database.SetInitializer(new CreateDatabaseIfNotExists<IISLogDbContext>());
        // Database.SetInitializer(new DropCreateDatabaseIfModelChanges<IISLogDbContext>());
    }


    /// <summary>
    /// 初始化,當模型建立的時候,
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); // 移除協定:表名複數化
        base.OnModelCreating(modelBuilder);
    }



    // *********************  DbSet **************************

    public DbSet<IISLogEntry> IISLogEntries { get; set; }


}

 上述代碼是數據上下文。

 

 最後提一句,怎麼找到官方文檔呢?在使用nuget獲取Microsoft TraceEvent Library以後,工程文件中就會多一個名爲「_TraceEventProgrammersGuide.docx」的文件,它就是官方文檔。以下所示:

 

相關文章
相關標籤/搜索