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”的文件,它就是官方文档。如下所示:

 

 

 

                       

 

posted @ 2015-10-20 20:38  何德海  阅读(4436)  评论(5编辑  收藏  举报