欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Asp.Net Core轻松学之利用日志监视进行服务遥测详解

程序员文章站 2022-04-10 09:58:06
前言 在 net core 2.2 中,官方文档表示,对 eventlistener 这个日志监视类的内容进行了扩充,同时赋予了跟踪 coreclr 事件的权限;通过...

前言

在 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 运行程序,看看输出结果如何

Asp.Net Core轻松学之利用日志监视进行服务遥测详解

可以看到,事件生产成功,实际上,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 再次运行程序,看下图输出结果

Asp.Net Core轻松学之利用日志监视进行服务遥测详解

从图中可以看出,这次我们跟踪到了 microsoft-aspnetcore-server-kestrel 事件源生产的开始和结束连接事件

结束语

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

示例代码下载:http://xiazai.jb51.net/201812/yuanma/ron.listenerdemo_jb51.rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。