You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

100 lines
6.9 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

事件日志
======
EventSource 最初是微软为Windows自身的日志框架 ETWEvent Tracing for Windows)设计的,目前已经没有平台的限制。
这是一种非常高效地记录日志的方式,它提供的强类型的编程方式可以使记录日志变得很“优雅”。
EventSource所谓的强类型编程模式主要体现在两个万面
其一可以继承抽象类EventSource定义一个具体的派生米型并将发送日志事件的操作实现在它的某个方法中
其二,日志消息的内容可以通过一个自定义的数据类型来承载。
我们可以将下面演示程序中的 DatabaseSource 视为某个数据库访问组件拥有的 EventSource。
将其定义成一个封闭Sealed的类型并利用静态只读字段Instance以单例的形式来使用这个对象。
“SQL命令执行”这一事件定义了对应的OnCommandExecute方法该方法的两个参数分别表示 DbCommand的类型CommandType和文本存储过程名称或者SOL语句
OnCommandExecute 方法最终调用继承的 WriteEvent 方法来发送日志事件。该方法的第一个参数1表示日志事件的ID。
```csharp
pubiic sealed class DatabaseSource :EventSource
public static readonly DatabaseSource Instance = new();
private DatabaseSource() ()
public void OnCommandExecute(CommandType commandType, string commandText)=> WriteEvent(1, commandType, commandText);
```
在如下所示的演示程序中利用Instance 字段得到了对应的DatabaseSource对象并应的形式调用了它的OnCommandExecute方法。
```csharp
using App;
using System.Data;
DatabaseSource,Instance,OnCommandExecute(CommandType.Text, "SELECT*FROM T_USER");
```
一个EventSource同样具有一个确定的名称。从ETW层面来讲EventSource 的名称实际就是 ETW Provider 的名称。
自定义的 EventSource 类型默认会以类型名称来命名所以上面演示程序采用的EventSource名称为“DatabaseSource”。
日志事件需要有一个具有唯一性的整作为ID如果没有显式设置则系统会采用从1开始自增的方式为每个日志方法分配一个ID.
由于DatabaseSource中只定义了一个唯一的日志方法OnCommandExecute所以它被赋予的ID自然是1。
当事件方法在调用 WriteEvent 方法发送日志事件时需要指定与当前方法匹配的事件ID这就是该方法在调用WriteEvent方法时将第一个参数设置为1的原因。
由于EventSource具有向ETW日志系统发送日志事件的功能所以可以利用一些工具来收集这些事件。
作者习惯使用的是一款叫作PerfView的GUI工具这是一款可以在网上直接下载的性能分析工具解压缩后就是一个可执行文件。
作者倾向于将该工具所在的目录添加到环境变量PATH中这样就可以采用命令行的形式进行启动。
我们可以采用Run和Collect这两种模式来启动PerfView前者利用 PerfView启动和检测某个指定的应用后者则独立启动PerfView并检测当前运行的所有应用进程。
我们可以将应用所在根目录作为工作目录并执行“PerfView/onlyproviders=*DatabaseSource run dotnet run"命令来启动PerfView。
为了将自定义的 Trace Provider 纳入 PerfView的检测范围我们将命令行开关onlyproviders设置为“*DatabaseSource”。执行“dotnet run”命令来启动应用程序PerfView Run这就意味着演示程序将作为监测程序被启动。
PerfView 会将捕获到的日志打包到当前目录下一个名为 PerfViewData.etl.zip的压缩文件中它左侧的目录结构会以图7-5所示的形式列出该文件。
双击该文件展开其子节点后会看到一个Events节点PerfView捕捉到的日志就可以通过它来查看。
双击Events节点后图7-5所示的事件视图将会列出捕获到的所有日志事件。我们可以输入“DatabaseSource”筛选由DatabaseSource发送的事件。
可以看到DatabaseSource 共发送了两个事件其中一个就是onCommandExecute。
双击事件视图左侧的“OnCommandExecute”可以查看该事件的详细信息当调用对应日志方法时提供的数据会包含在Rest列中.
ThreadID="17,608commandType="Text"commandText="SELECT *FROM T USER"
虽然系统会根据默认的规则来命名自定义 EventSource的名称和日志输出方法的事件ID但是对它们进行显式设置是更好的选择。
如下面的代码片段所示,我们在 DatabaseSource 类型上通过标注的 EventSourceAttribute 特性将名称设置为“Artech-Data-SqlClient”。OnCommandExecute方法利用标注的EventAttribute特性将事件ID设置为1。
```csharp
gventSource(Name ="Artech-Data-SqlClient")]
oublic sealed class DatabaseSource :EventSource
[Event(1)
public void OnCommandExecute(CommandType commandType, string commandText)-> WriteEvent(1, commandType, commandText);
```
除了利用PerfView 捕捉 EventSource对象触发的事件我们还可以通过 EventListener 对象以便达到相同的目的。
定义这个与 DatabaseSource 对应的 DatabaseSourceListener 类型。该类型继承自抽象类EventListener。它的 OnEventSourceCreated 方法能够感知到当前进程中所有EventSource对象的创建。
所以我们重写了该方法对匹配 EventSource实施过滤并最终通过调用EnableEvents方法订阅由目标EventSource发出的全部或者部分等级的事件。订阅事件的处理实现在重写的OnEventWritten方法中。
```charp
public class DatabaseSourceListener:EventListener
protected override void OnEventSourceCreated(EventSource eventSource)
if (eventSource.Name =="Artech-Data-SqlClient")
EnableEvents (eventSource, EventLevel.LogAlways);
protected override void OnEventWritten(EventWrittenEventArgs eventData)
Console.WriteLine($"EventId:(eventData.EventId)");
Console.WriteLine($"EventName:(eventData.EventName)");
Console.WriteLine($"Payload");
var index = 0;
if (eventData.PayloadNames != null)
foreach (var payloadName in eventData.PayloadNames)
Console.WriteLine($"\t(payloadName):(eventData.Payload?[index++])");
```
在 OnEventSourceCreated方法中调用 EnableEvents方法对由DatabaseSource发出的所有事件EventLevel.LogAlways)进行了订阅所以只有DatabaseSource对象发出的日志事件能够被捕捉。在重写的 OnEventWritten方法中作为唯一参数的 EventWrittenEventArgs对象承载了日志事件的所有信息并将事件的ID、名称和载荷数据Payload输出到控制台上。
```csharp
using App;
using System.Data;
new DatabaseSourceListener();
DatabaseSource.Instance.OnCommandExecute(CommandType.Text,"SELECT ★ FROM T_USER");
```
EventListener并不需要显式注册所以只需要按照如上所示的方式在程序运行时创建DatabaseSourceListener对象。
程序运行之后由DatabaseSourceListener对象捕获的日志事件信息会输出到控制台上。