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


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

 

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

(1)尋找有沒有現成的IIS日志模塊。

(2)重寫IIS的日志模塊。

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

 

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

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

(2)針對方法2,改寫的工作量較大,且可以會性能問題,故拋棄。

(3)針對方法3,發現iis日志的保存目標可以為ETW事件,故采用。如下圖所示:

 

下面介紹一下如何訂閱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”的文件,它就是官方文檔。如下所示:

 

 

 

                       

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM