前言

    在 Net Core 2.2 中,官方文档表示,对 EventListener 这个日志监视类的内容进行了扩充,同时赋予了跟踪 CoreCLR 事件的权限;通过跟踪 CoreCLR 事件,比如通过跟踪 CoreCLR 事件,可以了解和收集到比如 GC,JIT,ThreadPool,intreop 这些运行时服务的行为;通过使用配置注入,我们将获得一种动态跟踪事件的能力。

1. EventListener 介绍

1.1 EventListener 中文直译为:事件侦听器

EventListener 位于程序集 System.Diagnostics.Tracing 中,该类提供了一组启用/禁用的方法,按照惯例,先来看一下源代码,了解一下其结构

    public abstract class EventListener : IDisposable
{
protected EventListener(); public event EventHandler<EventSourceCreatedEventArgs> EventSourceCreated; public event EventHandler<EventWrittenEventArgs> EventWritten; protected static int EventSourceIndex(EventSource eventSource); public void DisableEvents(EventSource eventSource); public virtual void Dispose(); public void EnableEvents(EventSource eventSource, EventLevel level); public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword); protected internal virtual void OnEventWritten(EventWrittenEventArgs eventData);
}

从类结构中可以看出,EventListener 中的方法并不多,而且从名字都可以推断出其行为,

因为该类是一个抽象类,并不能直接使用,接下来我们创建一个 ReportListener 类继承它

2. 创建自定义事件侦听器
 public class ReportListener : EventListener
{
public ReportListener() { } public Dictionary<string, ListenerItem> Items { get; set; } = new Dictionary<string, ListenerItem>();
public ReportListener(Dictionary<string, ListenerItem> items)
{
this.Items = items;
} protected override void OnEventSourceCreated(EventSource eventSource)
{
if (Items.ContainsKey(eventSource.Name))
{
var item = Items[eventSource.Name];
EnableEvents(eventSource, item.Level, item.Keywords);
}
} protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (Items.ContainsKey(eventData.EventSource.Name))
{
Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventSource.Name}.{eventData.EventName}");
for (int i = 0; i < eventData.Payload.Count; i++)
{
string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
}
Console.WriteLine("\n");
}
}
}

ReportListener 自定义事件侦听器的代码非常简单,只是简单的继承了 EventListener 后,重写了父类的两个方法:创建事件和写入事件

同时,还定义了一个公共属性 Dictionary<string, ListenerItem> Items ,该属性接受一个 ListenerItem 的跟踪配置集,通过配置文件注入,动态觉得哪些事件可以被写入到侦听器中

3. 配置跟踪项目

在配置文件 appsettings.json 中增加以下内容

{
"listener": [
{
"name": "HomeEventSource",
"level": 5,
"keywords": -1
}
]
}

配置说明

上面的配置文件表示,定义一个事件源对象(EventSource),名称为 HomeEventSource,事件级别(EventLevel)为 5,关键字(EventKeywords)为 -1

关于事件级别和事件关键字的值,和系统定义的一致

3.1 事件级别定义

namespace System.Diagnostics.Tracing
{
public enum EventLevel
{
LogAlways = 0,
Critical = 1,
Error = 2,
Warning = 3,
Informational = 4,
Verbose = 5
}
}

3.2 事件关键字定义

namespace System.Diagnostics.Tracing
{
[Flags]
public enum EventKeywords : long
{
All = -1,
None = 0,
WdiContext = 562949953421312,
MicrosoftTelemetry = 562949953421312,
WdiDiagnostic = 1125899906842624,
Sqm = 2251799813685248,
AuditFailure = 4503599627370496,
CorrelationHint = 4503599627370496,
AuditSuccess = 9007199254740992,
EventLogClassic = 36028797018963968
}
}

3.3 配置文件完全按照系统值定义,为了更好的使用配置文件,我们定义了下面的实体类

    public class ListenerItem
{
public string Name { get; set; }
public EventLevel Level { get; set; } = EventLevel.Verbose;
public EventKeywords Keywords { get; set; } = EventKeywords.All;
}
4. 开始使用事件侦听器

为了在应用程序中使用事件侦听器,我们需要初始化事件侦听器,你可以初始化多个事件侦听器;但是,每个事件侦听器仅需要初始化一次即可

4.1 初始化自定义事件侦听器,在 Startup.cs 文件中加入以下代码

		public void AddEventListener(IServiceCollection services)
{
var listeners = this.Configuration.GetSection("listener").Get<List<ListenerItem>>();
Dictionary<string, ListenerItem> dict = new Dictionary<string, ListenerItem>();
if (listeners != null)
{
foreach (var item in listeners)
{
dict.Add(item.Name, item);
}
}
var report = new ReportListener(dict);
services.AddSingleton<ReportListener>(report);
} public void ConfigureServices(IServiceCollection services)
{
AddEventListener(services);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

初始化动作非常简单,仅是从配置文件中读取需要跟踪的项,然后注册到 ReportListener 内部即可,为了演示事件的注册,我们需要创建一个事件源,就像配置文件中的名称 HomeEventSource

4.2 创建自定义的事件源对象

    public class HomeEventSource : EventSource
{
public static HomeEventSource Instance = new HomeEventSource(); [Event(1001)]
public void RequestStart(string message) => WriteEvent(1001, message); [Event(1002)]
public void RequestStop(string message) => WriteEvent(1002, message);
}

自定义事件源 HomeEventSource 继承自 EventSource,我们可无需为该自定义事件源进行显式命名,因为默认将会使用 HomeEventSource 类名进行注册事件

现在,我们尝试着 HomeController 去生产一个事件,看看效果

5. 生产事件

5.1 转到 HomeController,在 HomeController 的 Get 方法中使用 HomeEventSource 生产两个事件

    [Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
HomeEventSource.Instance.RequestStart("处理业务开始");
var arra = new string[] { "value1", "value2" };
HomeEventSource.Instance.RequestStop("处理业务结束");
return arra;
}
}

5.2 回顾一下自定义事件侦听器 ReportListener 的重写方法

        protected override void OnEventSourceCreated(EventSource eventSource)
{
if (Items.ContainsKey(eventSource.Name))
{
var item = Items[eventSource.Name];
EnableEvents(eventSource, item.Level, item.Keywords);
}
} protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (Items.ContainsKey(eventData.EventSource.Name))
{
Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventSource.Name}.{eventData.EventName}");
for (int i = 0; i < eventData.Payload.Count; i++)
{
string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
}
Console.WriteLine("\n");
}
}

由于我们做配置文件中指定了必须是 HomeEventSource 事件源才启用事件,所以上面的代码表示,当一个 HomeEventSource 事件进入的时候,将事件的内容打印到控制台,实际应用中,你可以将这些信息推送到日志订阅服务器,以方便跟踪和汇总

5.3 运行程序,看看输出结果如何

可以看到,事件生产成功,实际上,CoreCLR 内部生产了非常多的事件,下面我们尝试启用以下 3 个事件源,预期将会收到大量的事件信息

5.4 尝试更多事件源

       protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
} else if (eventSource.Name.Equals("System.Data.DataCommonEventSource"))
{
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
} else if (eventSource.Name.Equals("Microsoft-AspNetCore-Server-Kestrel"))
{
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
}
}

5.5 再次运行程序,看下图输出结果

从图中可以看出,这次我们跟踪到了 Microsoft-AspNetCore-Server-Kestrel 事件源生产的开始和结束连接事件

结束语

  • 在 CoreCLR 的事件总线中,包含了千千万万的事件源生产的事件,以上的实验只是冰山一角,如果你把创建事件源的 EventKeywords 指定为 All,你将会看到天量的日志信息,但是,在这里,友情提示大家,千万不要这样做,这种做法会对服务性能带来极大损害
  • 在业务代码中,写入大量的调试日志是不可取的,但是使用事件侦听器,可以控制事件的创建和写入,当需要对某个接口进行监控的时候,通过将需要调试的事件源加入配置文件中进行监控,这将非常有用

示例代码下载

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.ListenerDemo

Asp.Net Core 轻松学-利用日志监视进行服务遥测的更多相关文章

  1. Asp.Net Core 轻松学-利用文件监视进行快速测试开发

    前言     在进行 Asp.Net Core 应用程序开发过程中,通常的做法是先把业务代码开发完成,然后建立单元测试,最后进入本地系统集成测试:在这个过程中,程序员的大部分时间几乎都花费在开发.运行 ...

  2. Asp.Net Core 轻松学-利用 Swagger 自动生成接口文档

    前言     目前市场上主流的开发模式,几乎清一色的前后端分离方式,作为服务端开发人员,我们有义务提供给各个客户端良好的开发文档,以方便对接,减少沟通时间,提高开发效率:对于开发人员来说,编写接口文档 ...

  3. Asp.Net Core 轻松学-利用xUnit进行主机级别的网络集成测试

    前言     在开发 Asp.Net Core 应用程序的过程中,我们常常需要对业务代码编写单元测试,这种方法既快速又有效,利用单元测试做代码覆盖测试,也是非常必要的事情:但是,但我们需要对系统进行集 ...

  4. Asp.Net Core 轻松学系列-1阅读指引目录

    https://www.cnblogs.com/viter/p/10474091.html 目录 前言 1. 从安装到配置 2. 业务实现 3. 日志 4. 测试 5. 缓存使用 6.网络和通讯 7. ...

  5. Asp.Net Core 轻松学-使用MariaDB/MySql/PostgreSQL和支持多个上下文对象

    前言 在上一篇文章中(Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库)[https://www.cnblogs.com/viter/p/10243577.html],介 ...

  6. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  7. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  8. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  9. Asp.Net Core 轻松学-多线程之Task(补充)

    前言     在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下. 1. 任务的等待 在使用 ...

随机推荐

  1. bzoj3534 [Sdoi2014]重建

    变形的$Martix-Tree$定理 发现我们要求的是$\prod_{i \in E}{p_{i}} * \prod_{i \notin E}{(1-p_{i})}$ 然后呢? 矩阵树对重边也有效对吧 ...

  2. React从入门到放弃之前奏(5):ReactRouter4

    概念 安装:npm i -S react-router react-router-dom GitHub:ReactTraining/react-router React Router中有三种类型的组件 ...

  3. mysql中利用group by过滤删除重复行

    DELETEFROM peopleWHERE peopleId IN (SELECT peopleId FROM people GROUP BY peopleId HAVING count(peopl ...

  4. appium--xpath定位元素用法

    一.xpath的使用场景: 自动化测试中经常对元素进行操作时,如果存在id.name.content_desc时,可通过appium框架提供的方法find_element_by_id/name/tag ...

  5. OsharpNS轻量级.net core快速开发框架简明入门教程-基于Osharp实现自己的业务功能

    OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...

  6. 《前端之路》之 Javascript 模块化管理的来世今生

    目录 第二章 - 04: Javascript 模块化管理的来世今生 一.什么是模块化开发 1-1.模块化第一阶段 1-2.封装到对象 1-3. 对象的优化 二.模块化管理的发展历程 2-1.Comm ...

  7. 学习 JavaScript (四)核心概念:操作符

    JavaScript 的核心概念主要由语法.变量.数据类型.操作符.语句.函数组成,前面三个上一篇文章已经讲解完了.后面三个内容超级多,这篇文章主要讲解的是操作符. 操作符 什么叫做操作符? 这是一种 ...

  8. Main(string[] args)之args传递的几种方式

    1.通过配置project属性传递.如图: 2.通过代码传递 3.通过命令行传递

  9. asp.net Core 中AuthorizationHandler 实现自定义授权

    前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...

  10. python3 Flask -day1

    window 10  python 3 安装flask 首先打开cmd命令执行窗口切换到Python安装目录的Script,输入pip出现以下页面 这里我们使用virtualenv虚拟开发环境 为什么 ...