• 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("格式转换失败。
    日志字符串为:" + logString + "
    异常信息:" + 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”的文件,它就是官方文档。如下所示:

     

                           

  • 相关阅读:
    114自定义UITableViewCell(扩展知识:为UITableViewCell添加动画效果)
    101在检索框中添加一个书签按钮(扩展知识:在检索框中添加一个范围条)
    088实现自动倒计时功能
    086设置日期选择器框的显示样式
    Shell if else
    Shell数组
    Shell字符串
    Shell运算符
    数据挖掘标准流程规范
    Shell转义字符与变量替换
  • 原文地址:https://www.cnblogs.com/dehai/p/4889900.html
Copyright © 2020-2023  润新知