在上一篇文章《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」的文件,它就是官方文檔。以下所示: