DiagnosticSource DiagnosticListener 无侵入式分布式跟踪

ASP.NET Core 中的框架中发出大量诊断事件,包括当前请求进入请求完成事件,HttpClient发出收到与响应,EFCore查询等等。

我们可以利用DiagnosticListener来选择性地监听这些事件,然后通过自己的方式组织这些日志,实现无侵入的分布式跟踪。

下面我们通过DiagnosticSource监听EFCore,与HTTPClient,实现链路追踪。

创建监听

现在我们将配置一个DiagnosticListener来监听全部事件。 

首先,我们需要一个IObserver<DiagnosticListener>,我们将使用它来订阅所有事件。

public class TestDiagnosticObserver : IObserver<DiagnosticListener>
{
    public void OnNext(DiagnosticListener value)
{
        value.Subscribe(new TestKeyValueObserver());
    }
    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

其中重要的方法是OnNext。

然后我们传入另一个自定义类型TestKeyValueObserver,这是实际接收实例发出的事件的类DiagnosticListener。

该事件会接受KeyValuePair<string, object>参数,我们后续可针对此参数做业务相关的筛选。

public class TestKeyValueObserver : IObserver<KeyValuePair<string, object?>>
{
    public void OnNext(KeyValuePair<string, object?> value)
{
        var activity = Activity.Current;


        Console.WriteLine($"traceId {activity?.TraceId} Received event: {value.Key}");
    }
    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

最后一步是在应用程序中注册我们的程序TestDiagnosticObserver。

DiagnosticListener.AllListeners.Subscribe(new TestDiagnosticObserver());

创建HTTP请求与EFCore查询

我们新建一个接口,用来集成EF与HttpClient。并调用这个接口查看DiagnosticListener 监听到的内容

[HttpGet]
public async Task<string> GetAsync()
{
    //HTTP
    await _httpClient.GetAsync("https://www.baidu.com");


    //EF
    Item item = new Item()
    {
        Barcode = Guid.NewGuid().ToString(),
        Brand = "Milky Way",
        Name = "Milk",
        PruchasePrice = 20.5,
        SellingPrice = 25.5
    };
    _productsContext.Items.Add(item);
    _productsContext.SaveChanges();
    return "OK";
}

调用此接口来看看我们的DiagnosticListener的效果。

可以看到收到了很多Event,包括当前请求的各个阶段,HttpClient的各个阶段,与EFCore查询的各个阶段。

31e1fbfd34c2465044bf5bc1816f9ad3.png

解析Event

然后修改TestKeyValueObserver,我们从中挑选我们需要的HTTPClient与EFCore相关的事件。

public class TestKeyValueObserver : IObserver<KeyValuePair<string, object?>>
{
    public void OnNext(KeyValuePair<string, object?> value)
    {
        var activity = Activity.Current;


        //Console.WriteLine($"traceId {activity?.TraceId} Received event: {value.Key}");
        if (value.Key.StartsWith("System.Net.Http.Request"))
        {
            var cEventStr = JsonConvert.SerializeObject(value.Value);
            var cEvent = JsonConvert.DeserializeAnonymousType(cEventStr, new { Request = new { RequestUri = ""} , Timestamp = 2879029490722 });
            Console.WriteLine($"traceId {activity?.TraceId} Request.Start: {cEvent.Timestamp} ");
            Console.WriteLine($"traceId {activity?.TraceId} Request.Uri: {cEvent.Request.RequestUri} ");
        }
        if (value.Key.StartsWith("System.Net.Http.Response"))
        {
            var cEventStr = JsonConvert.SerializeObject(value.Value);
            var cEvent = JsonConvert.DeserializeAnonymousType(cEventStr, new { Request = new { RequestUri = "" }, Timestamp = 2879029490722 });
            Console.WriteLine($"traceId {activity?.TraceId} Http.Response: {cEvent.Timestamp} ");
        }




        if (value.Key.StartsWith("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpening"))
        {
            var cEvent = (Microsoft.EntityFrameworkCore.Diagnostics.ConnectionEventData)value.Value;
            Console.WriteLine($"traceId {activity?.TraceId} Connection.ConnectionOpening: {cEvent?.StartTime.ToString("yyyy-MM-dd HH:mm:ss:fff")} ");
        }
        if (value.Key.StartsWith("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting"))
        {
            var cEvent = (Microsoft.EntityFrameworkCore.Diagnostics.CommandEventData)value.Value;
            Console.WriteLine($"traceId {activity?.TraceId}  {cEvent?.Command.CommandText} ");
        }
        if (value.Key.StartsWith("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosed"))
        {
            var cEvent = (Microsoft.EntityFrameworkCore.Diagnostics.ConnectionEventData)value.Value;
            Console.WriteLine($"traceId {activity?.TraceId} Connection.ConnectionClosed: {cEvent?.StartTime.ToString("yyyy-MM-dd HH:mm:ss:fff")} ");
        }
    }
    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

再次启动,查看效果,可以看到已经获取到了http请求的开始结束事件,EF的查询语句,开始事件等。

4bb1470014e41ac76546c891e232169f.png

最后我们可以结构化这些数据,并将其持久化到自己的监控体系中,实现链路跟踪。

关注我获取技术分享

1e01ddfd711e4f73abdd25ae013ec7a8.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值