在上一篇文章《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事件的方法

核心代码

         private void button1_Click(object sender, EventArgs e)
{
try
{ using (var session = new TraceEventSession("IIS-Logging")) // 创建一个session
{
session.EnableProvider("Microsoft-Windows-IIS-Logging"); // Microsoft-Windows-IIS-Logging 是IIS日志模块提供的provider的名称 session.Source.Registered.All += Registered_All; // 注册事件处理函数 session.Source.Process(); // Wait for incoming events (forever).
}
}
catch
{
}
} /// <summary>
/// 事件处理函数
/// </summary>
/// <param name="data"></param>
void Registered_All(TraceEvent data)
{
try
{
string logString = data.FormattedMessage; // 返回日志项的字符串形式 // 将文本转换成对象
IISLogEntry logEntry = new IISLogEntry(logString); IISLogEntry.Add(logEntry);
}
catch
{
}
}

 上述代码创建会话了,绑定了事件源(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.

外围代码(解析日志字符串,存入数据库)

 using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; [Table("IISLogEntry")]
public class IISLogEntry
{
[Key]
public long Id { get; set; } /// <summary>
/// 服务端信息
/// </summary>
public Server Server { set; get; } /// <summary>
/// 客户端信息
/// </summary>
public Client Client { get; set; } /// <summary>
/// 请求信息
/// </summary>
public Request Request { get; set; } /// <summary>
/// 响应信息
/// </summary>
public Response Response { get; set; } /// <summary>
/// 构造函数
/// </summary>
public IISLogEntry() { } /// <summary>
/// 构造函数。使用字符串来构造一个日志对象
/// </summary>
/// <param name="logString"></param>
public IISLogEntry(string logString)
{
try
{
string[] array = logString.Trim().Split(new char[] { ' ' }); Dictionary<string, string> dictionary = new Dictionary<string, string>();
//将数组中的值放入到字典中,奇数项为key,偶数项为value
for (int i = ; i < array.Length; i++)
{
if (i % == )
{
dictionary.Add(array[i], "");
}
if (i % == )
{
dictionary[array[i - ]] = array[i];
}
} this.Server = new Server()
{
IP = dictionary["s-ip"],
Port = int.Parse(dictionary["s-port"]),
Name = dictionary["s-computername"]
};
this.Client = new Client()
{
IP = dictionary["c-ip"],
UserAgent = dictionary["cs(User-Agent)"],
UserName = dictionary["cs-username"]
};
this.Request = new Request()
{
RequestDateTime = DateTime.Now,
Method = dictionary["cs-method"],
UriResource = dictionary["cs-uri-stem"],
UriQuery = dictionary["cs-uri-query"],
BytesReceived = this.ConvertToLong(dictionary["cs-bytes"])
};
this.Response = new Response()
{
TimeTaken = this.ConvertToLong(dictionary["time-taken"]),
BytesSent = this.ConvertToLong(dictionary["sc-bytes"]),
Status = int.Parse(dictionary["sc-status"]),
SubStatus = int.Parse(dictionary["sc-substatus"]),
Win32Status = int.Parse(dictionary["sc-win32-status"]),
};
}
catch (Exception exp)
{
throw new Exception("格式转换失败。\n日志字符串为:" + logString + "\n异常信息:" + exp.Message);
}
} //**************************** CRUD ************************************
public static bool Add(IISLogEntry data)
{
using (IISLogDbContext db = new IISLogDbContext())
{
db.IISLogEntries.Add(data); try
{
db.SaveChanges();
return true;
}
catch
{
return false;
}
}
} /// <summary>
/// 将带千分号的字符串转换成长整型。如:4,939
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private long ConvertToLong(string str)
{
string str2 = str.Replace(",", "");
return long.Parse(str2);
} } /// <summary>
/// 服务端信息
/// </summary>
[ComplexType]
public class Server
{
/// <summary>
/// 服务器名称。对应:s-computername
/// </summary>
[MaxLength()]
public string Name { set; get; } /// <summary>
/// 服务器IP。对应:s-ip
/// </summary>
[MaxLength()]
public string IP { set; get; } /// <summary>
/// 服务器端口。对应:s-port
/// </summary>
public int Port { set; get; } } /// <summary>
/// 客户端信息
/// </summary>
[ComplexType]
public class Client
{
/// <summary>
/// 客户端IP。对应:c-ip
/// </summary>
[MaxLength()]
public string IP { set; get; } /// <summary>
/// 客户端所使用的用户代理。对应: cs(User-Agent)
/// </summary>
[MaxLength()]
public string UserAgent { set; get; } /// <summary>
/// 登录的用户名。对应: cs-username
/// </summary>
[MaxLength()]
public string UserName { set; get; } } /// <summary>
/// 请求信息
/// </summary>
[ComplexType]
public class Request
{
/// <summary>
/// 请求时间。对应:date和time
/// </summary>
public DateTime RequestDateTime { set; get; } /// <summary>
/// 方法。对应:cs-method
/// </summary>
[MaxLength()]
public string Method { set; get; } /// <summary>
/// uri资源。对应:cs-uri-stem
/// </summary>
[MaxLength()]
public string UriResource { get; set; } /// <summary>
/// uri查询。对应:cs-uri-query
/// </summary>
[MaxLength()]
public string UriQuery { get; set; } /// <summary>
/// 接收的字节数,单位为byte。对应:cs-bytes
/// </summary>
public long BytesReceived { get; set; } } /// <summary>
/// 响应信息
/// </summary>
[ComplexType]
public class Response
{
/// <summary>
/// 状态码。对应:sc-status
/// </summary>
public int Status { get; set; } /// <summary>
/// 子状态码。 对应:sc-substatus
/// </summary>
public int SubStatus { get; set; } /// <summary>
/// win32状态码。 对应:sc-win32-status
/// </summary>
public int Win32Status { get; set; } /// <summary>
/// 发送的字节数,单位为byte。对应:sc-bytes
/// </summary>
public long BytesSent { get; set; } /// <summary>
/// 所用时间,单位为ms。对应: time-taken
/// </summary>
public long TimeTaken { get; set; } }

上述代码是日志项实体。这里我们使用了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”的文件,它就是官方文档。如下所示:

IIS日志存入数据库之二:ETW的更多相关文章

  1. IIS日志存入数据库之一:ODBC

    园内@Fish Li的文章<IIS日志-网站运维的好帮手>中介绍将IIS的文本格式的文件导入数据库的方法.在实践中,我们发现导数据的速度很慢,一个200M的日志文件居然要近100分钟.我们 ...

  2. WebAPI + log4net日志 存入数据库

    1.首先选择你的项目 打开net管理控制台 输入 install-package log4net 进行安装  也可以 在net包 搜索 log4net 2.安装完之后 在Models文件夹 创建一个L ...

  3. IIS日志-网站运维的好帮手

    对于一个需要长期维护的网站来说,如何让网站长久稳定运行是件很有意义的事情. 有些在开发阶段没有暴露的问题很有可能就在运维阶段出现了,这也是很正常的. 还有些时候,我们希望不断地优化网站,让网站更快速的 ...

  4. 网站运维工具使用iis日志分析工具分析iis日志(iis日志的配置)

    我们只能通过各种系统日志来分析网站的运行状况,对于部署在IIS上的网站来说,IIS日志提供了最有价值的信息,我们可以通过它来分析网站的响应情况,来判断网站是否有性能问题,或者存在哪些需要改进的地方 对 ...

  5. 【转】IIS日志-网站运维的好帮手

    对于一个需要长期维护的网站来说,如何让网站长久稳定运行是件很有意义的事情. 有些在开发阶段没有暴露的问题很有可能就在运维阶段出现了,这也是很正常的. 还有些时候,我们希望不断地优化网站,让网站更快速的 ...

  6. 利用LogParser将IIS日志插入到数据库

    利用LogParser将IIS日志插入到数据库 上面的博文是定制一个计划任务来将log日志定时的导入数据库      下面这篇博文是用cmd指令将日志导入到一张sql表中,是一次性操作   Log P ...

  7. 把IIS日志导入到数据库

    1.建表 CREATE TABLE [dbo].[inetlog0828]( [date] [date] NULL, ) NULL, ) NULL, ) NULL, ) NULL, ) NULL, [ ...

  8. MySQL使用二进制日志恢复数据库

    一.二进制日志简介 MySQL有不同类型的日志,其中二进制文件记录了所有对数据库的修改,如果数据库因为操作不当或其他原因丢失了数据,可以通过二进制文件恢复. 在my.ini文件中设置了log-bin, ...

  9. log4net 添加自定义日志到数据库

    添加操作日志到数据库举例: (一)建立数据库的操作日志表,如下我建立了一个简单的日志表 (二)配置文件中的配置如下 <log4net> <!--错误日志记录数据库--> < ...

随机推荐

  1. 关于C的int

    在c运行库头文件<stdint.h>中typedef各种类型的int typedef signed char int8_t; typedef unsigned char uint8_t; ...

  2. 判断某个字符串里面是否包含caoyang 这个字符串?

    $string = 'Lorem ipsum dolor sit amet'; $preg = '/caoyang/'; $status = preg_match($preg, $string,$ma ...

  3. $(QTDIR);$(QTDIR)\include\QtCore;$(QTDIR)\include;

    $(QTDIR); 在系统环境变量中定义即可  vs属性中设置头文件路径

  4. JDesktopPane JInternalFrames

    通常 JInternalFrames 需要配合 JDesktopPane 一起使用. JInternalFrames 必须在 JDesktopPane 里面

  5. Spring MVC(一)Servlet 2.x 规范在 Spring MVC 中的应用

    Spring MVC(一)Servlet 2.x 规范在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019869 ...

  6. 数据结构和Java集合

    list接口,可重复,有序的.list有arrayList,因为是数组结构,适合用在数据的查询,linkedList,因为是链表结构,适合用在增删操作.数组如果增删的话,需要后面的元素都往前或者往后移 ...

  7. 2017/2/11CSS基础

    一:html中div: 1.DIV标签应用于 Style Sheet(样式表)方面会更显威力,它最终目的是给设计者另一种组织能力,有 Class.Style.title.ID 等属性. 2.<d ...

  8. 关于Windows 8 合约

    浅谈win8合约: http://www.devdiv.com/Windows_Metro-windows_metro_app_windows_contracts_-thread-131717-1-1 ...

  9. ContextMune上下文菜单中,二级菜单获取及状态设置

    ContextMune上下文菜单中,二级菜单获取及状态设置 在使用ContextMune上下文菜单中,能够通过二级菜单来获取及状态设置 //二级菜单获取和状态设置((ToolStripDropDown ...

  10. ubuntu16下的/etc/resolv.conf重置的解决方案

    此文件存放了网络网关信息,重启后会刷新,刷新来源有两个可能 一个是根据文件中的resolvconf目录下的resolv.conf.d目录下的base文件 另一个来源是/etc/network/inte ...