借助 Interceptor 实现拦截 IServiceProvider.CreateScope

借助 Interceptor 实现拦截 IServiceProvider.CreateScope

Intro

之前写过几篇后台服务的日志追踪的文章如 基于 Activity 来实现后台服务的日志追踪,之前我们提到过 IServiceProvider.CreateScope 是没办法拦截的,但是借助 C# 12 中的 Interceptor 拦截代码中的 CreateScope 就变得可能了,一起来看下吧

Sample

我们的实现目标是创建 scope 的时候自动创建一个 Activity,考虑一些自定义的需求,我们可以通过一个服务来创建,通过 config 来配置这个服务的行为,比如下面服务的 ActivitySource 的配置,以及自动附加一些 activity 的 tag 等等

public sealed class ActivityScope : IDisposable, IAsyncDisposable
{
    private static readonly ActivitySource ActivitySource = new(nameof(ActivityScope));

    private readonly Activity? _activity = ActivitySource.StartActivity(nameof(ActivityScope));

    public Activity? Activity => _activity;
    
    public void Dispose()
    {
        Console.WriteLine("Dispose ing...");
        _activity?.Dispose();
        Console.WriteLine("Dispose done");
    }

    public ValueTask DisposeAsync()
    {
        Console.WriteLine("DisposeAsync ing...");
        _activity?.Dispose();
        Console.WriteLine("DisposeAsync done");
        return ValueTask.CompletedTask;
    }
}

测试代码如下:

using var activityListener = new ActivityListener();
activityListener.ShouldListenTo = s => s.Name == nameof(ActivityScope);
activityListener.Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
    ActivitySamplingResult.PropagationData;
ActivitySource.AddActivityListener(activityListener);

var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<ActivityScope>();
await using var serviceProvider = serviceCollection.BuildServiceProvider();
{
    using var scope = serviceProvider.CreateScope();
    Console.WriteLine("activity scope activityId:");
    Console.WriteLine(scope.ServiceProvider.GetRequiredService<ActivityScope>().Activity?.Id);
    Console.WriteLine();
    Console.WriteLine("Current activityId:");
    Console.WriteLine(Activity.Current?.Id);
}
Console.WriteLine();
{
    await using var scope = serviceProvider.CreateAsyncScope();
    Console.WriteLine("CreateAsyncScope Current activityId:");
    Console.WriteLine(Activity.Current?.Id);
}

直接跑这个代码输出的结果如下:

eea4b697466f79e97d8bdee1aba4c57b.png

ActivityScope 实例创建之前 activityId 是空,在实例创建之后,Activity.Current?.Id 就有值了

我们可以通过 intercept CreateScope 方法,在 CreateScope 之后马上创建 ActivityScope 实例,这样我们就实现了自动创建 Activity

Interceptor 代码如下:

namespace CSharp12Sample.Generated
{
    public static partial class GeneratedActivityScope
    {
        [System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\projects\sources\SamplesInPractice\CSharp12Sample\ServiceScopeActivitySample.cs", 19, 47)]
        public static Microsoft.Extensions.DependencyInjection.IServiceScope ScopeActivityInterceptorMethod(this System.IServiceProvider provider)
        {
            System.Console.WriteLine("scope creating...");
            var scope = provider.CreateScope();
            _ = scope.ServiceProvider.GetRequiredService<CSharp12Sample.ActivityScope>();
            System.Console.WriteLine("scope created...");
            return scope;
        }

        [System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\projects\sources\SamplesInPractice\CSharp12Sample\ServiceScopeActivitySample.cs", 30, 53)]
        public static Microsoft.Extensions.DependencyInjection.AsyncServiceScope ScopeActivityInterceptorAsyncMethod(this System.IServiceProvider provider)
        {
            System.Console.WriteLine("async scope creating...");
            var scope = provider.CreateAsyncScope();
            _ = scope.ServiceProvider.GetRequiredService<CSharp12Sample.ActivityScope>();
            System.Console.WriteLine("async scope created...");
            return scope;
        }
    }
}

不要忘记添加 InterceptsLocationAttribute

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute;
}

使用 Interceptor 之后的输出结果如下:

ab73722c76b43060910148ff09de39a6.png

可以看到现在我们的输出 Activity.Current?.Id 始终是有值的,并且,第一个示例中的 activityId 始终是一致的,因为同在一个 scope 内,activity 只创建了一次,并且在创建 scope 的时候会打印 scope creating/scoped created

前面的 interceptor 代码可以通过 source generator 来生成,这里主要介绍一下实现思路,具体 source generator 的代码可以参考:https://github.com/WeihanLi/SamplesInPractice/blob/main/CSharp12Sample/ScopeActivityGenerator.cs

前面示例中使用了一个外部的 service,也可以直接在 interceptor 代码里直接 create 一个 activity 来代替这个 service,实现方式可以根据自己的需要选择

借助 interceptor 我们可以不再需要显式的修改 CreateScope 的代码,可以比较方便的拦截代码里的 CreatScope 方法调用来实现自动生成 activity,对于多个 CreateScope 和 内嵌的 CreateScope 也是支持的

例如这样的一个内嵌的 CreateScope 调用a811e41fbea1688ae0dc39e038dffc3d.png
生成的 interceptor 会多一个调用的地方

13957ba86282b0cffa86fb18f54f3cc7.png

运行代码可以看到下面这样的输出结果

02c39a40ae61ea14c58dc8df7b239000.png

References

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值