• 使用 OpenTelemetry 构建 .NET 应用可观测性(4):ASP.NET Core 应用中集成 OTel


    前言

    本文将介绍如何在 ASP.NET Core 应用中集成 OTel SDK,并使用 elastic 构建可观测性平台展示 OTel 的数据。

    本文只是使用 elastic 做基本的数据展示,详细的使用方式同学可以参考 elastic 的官方文档,后面也会介绍其他的对 OTel 支持较好的可观测性后端。

    示例代码已经上传到了 github,地址为:
    https://github.com/eventhorizon-cli/otel-demo

    使用 elastic 构建可观测性平台

    elastic 提供了一套完整的可观测性平台,并支持 OpenTelemetry protocol (OTLP) 协议。

    elastic apm 部署相对比较复杂,如果有同学想在生产环境中使用,可以参考 elastic 的官方文档进行部署或直接购买 elastic cloud。

    https://www.elastic.co/cn/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment

    为方便同学们学习,我准备好了一个 elastic 的 docker-compose 文件,包含了以下组件:

    • elasticsearch:用于存储数据
    • kibana:用于展示数据
    • apm-server:处理 OTel 的数据
    • fleet-server:用于管理 apm-agent,apm-agent 可以接收 OTLP 的数据,并将数据发送给 apm-server

    docker-compose 文件已经上传到了 github,地址为:

    https://github.com/eventhorizon-cli/otel-demo/blob/main/ElasticAPM/docker-compose.yml

    docker-compose 启动的过程中可能会遇到部分容器启动失败的情况,可以手动重启这部分容器。

    启动完成后,我们还需要一点配置,才能启用 apm-server。

    打开 http://localhost:5601 ,进入 kibana 的管理界面,用户名 admin,密码是 changeme。

    进入后会提示你添加集成。

    点击 Add integrations,选择 APM。

    然后一路确定,就可以了。



    在 ASP.NET Core 应用中集成 OTel SDK

    安装依赖#

    创建一个 ASP.NET Core 项目,然后安装以下依赖:

    • OpenTelemetry:OpenTelemetry 的核心库,包含了 OTel 的数据模型和 API。
    • OpenTelemetry.Extensions.Hosting:ASP.NET Core 的扩展,用于在 ASP.NET Core 应用中集成 OTel。
    • OpenTelemetry.Exporter.OpenTelemetryProtocol:OTel 的 OTLP exporter,用于将 OTel 的数据发送给可观测性后端。
    • OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs:OTel Logs 的 OTLP exporter,用于将 OTel 的 Logs 数据发送给可观测性后端。

    基础配置#

    在 Program.cs 中,我们需要添加以下代码:

    builder.Services.AddOpenTelemetry()
        // 这边配置的 Resource 是全局的,Log、Metric、Trace 都会使用这个 Resource
        .ConfigureResource(resourceBuilder =>
        {
            resourceBuilder
                .AddService("FooService", "TestNamespace", "1.0.0")
                .AddTelemetrySdk();
        })
        .WithTracing(tracerBuilder =>
        {
            tracerBuilder
                .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
        }).WithMetrics(meterBuilder =>
        {
            meterBuilder
                .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
        });
    
    builder.Services.AddLogging(loggingBuilder =>
    {
        loggingBuilder.AddOpenTelemetry(options =>
        {
            options.IncludeFormattedMessage = true;
            options.AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
        });
    });
    

    Instrumentation 配置#

    ASP.NET Core 以及 Entity Framework Core 等框架中有很多预置的埋点(通过 DiagnosticSource 实现),通过这些预置的埋点,我们可以收集到大量的数据,并借此创建出 Trace、Metric。

    比如,通过 ASP.NET Core 中 HTTP 请求 的埋点,可以创建出代表此次 HTTP 请求的 Span,并记录下各个 API 的耗时、请求频率等 Metrics。

    下面我们在应用中添加两个 Instrumentation

    • OpenTelemetry.Instrumentation.AspNetCore:ASP.NET Core 的 Instrumentation
    • OpenTelemetry.Instrumentation.Http:HTTP 请求的 Instrumentation,如果想要跨进程传输 Baggage,也需要添加此 Instrumentation
    tracerBuilder
        // ASP.NET Core 的 Instrumentation
        .AddAspNetCoreInstrumentation(options =>
        {
            // 配置 Filter,忽略 swagger 的请求
            options.Filter =
                httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
        })
        // HTTP 请求的 Instrumentation,如果想要跨进程传输 Baggage,也需要添加此 Instrumentation
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
    
    meterBuilder
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
    

    除了上面介绍的两个两个 Instrumentation,OTel SDK 还提供了很多 Instrumentation,可以在下面的链接中查看:

    https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src

    https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/main/src

    创建自定义 Span 和 Metric#

    前一篇文章中,我们介绍了利用 ActivitySource 创建 自定义Span 和利用 Meter 创建 自定义Metric 的方法。

    在 ASP.NET Core 中集成了 OTel SDK 后,我们可以将这些自定义的 Span 和 Metric 通过 OTel SDK 的 Exporter 发送给可观测性后端。

    tracerBuilder
        // 这边注册了 ActivitySource,OTel SDK 会去监听这个 ActivitySource 创建的 Activity
        .AddSource("FooActivitySource")
        .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
    
    meterBuilder
        // 这边注册了 Meter,OTel SDK 会去监听这个 Meter 创建的 Metric
        .AddMeter("FooMeter")
        .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
    

    完整的代码演示#

    下面我们创建两个 API 项目,一个叫做 FooService,一个叫做 BarService。两个服务都配置了 OTel SDK,其中 FooService 会调用 BarService。

    FooService 的关键代码如下:

    builder.Services.AddHttpClient();
    builder.Services.AddOpenTelemetry()
        // 这边配置的 Resource 是全局的,Log、Metric、Trace 都会使用这个 Resource
        .ConfigureResource(resourceBuilder =>
        {
            resourceBuilder
                .AddService("FooService", "TestNamespace", "1.0.0")
                .AddTelemetrySdk();
        })
        .WithTracing(tracerBuilder =>
        {
            tracerBuilder
                .AddAspNetCoreInstrumentation(options =>
                {
                    // 配置 Filter,忽略 swagger 的请求
                    options.Filter =
                        httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
                })
                .AddHttpClientInstrumentation()
                .AddSource("FooActivitySource")
                .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
        }).WithMetrics(meterBuilder =>
        {
            meterBuilder
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddMeter("FooMeter")
                .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
        });
    
    builder.Services.AddLogging(loggingBuilder =>
    {
        loggingBuilder.AddOpenTelemetry(options =>
        {
            options.IncludeFormattedMessage = true;
            options.AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
        });
    });
    
    [Route("/api/[controller]")]
    public class FooController : ControllerBase
    {
        private static readonly ActivitySource FooActivitySource
            = new ActivitySource("FooActivitySource");
        private static readonly Counter<int> FooCounter
            = new Meter("FooMeter").CreateCounter<int>("FooCounter");
    
        private readonly IHttpClientFactory _clientFactory;
        private readonly ILogger _logger;
    
        public FooController(
            IHttpClientFactory clientFactory,
            ILogger logger)
        {
            _clientFactory = clientFactory;
            _logger = logger;
        }
        [HttpGet]
        public async Task Get()
        {
            _logger.LogInformation("/api/foo called");
    
            Baggage.SetBaggage("FooBaggage1", "FooValue1");
            Baggage.SetBaggage("FooBaggage2", "FooValue2");
    
            var client = _clientFactory.CreateClient();
            var result = await client.GetStringAsync("http://localhost:5002/api/bar");
    
            using var activity = FooActivitySource.StartActivity("FooActivity");
            activity?.AddTag("FooTag", "FooValue");
            activity?.AddEvent(new ActivityEvent("FooEvent"));
            await Task.Delay(100);
    
            FooCounter.Add(1);
    
            return Ok(result);
        }
    }
    

    BarService 的关键代码如下:

    builder.Services.AddOpenTelemetry()
        .ConfigureResource(resourceBuilder =>
        {
            resourceBuilder
                .AddService("BarService", "TestNamespace", "1.0.0")
                .AddTelemetrySdk();
        })
        .WithTracing(options =>
        {
            options
                .AddAspNetCoreInstrumentation(options =>
                {
                    // 配置 Filter,忽略 swagger 的请求
                    options.Filter =
                        httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
                })
                .AddHttpClientInstrumentation()
                .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
        }).WithMetrics(options =>
        {
            options
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
        });
    
    builder.Services.AddLogging(loggingBuilder =>   
    {
        loggingBuilder.AddOpenTelemetry(options =>
        {
            options.IncludeFormattedMessage = true;
            options.AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
        });
    });
    
    [Route("/api/[controller]")]
    public class BarController : ControllerBase
    {
        private readonly ILogger _logger;
    
        public BarController(ILogger logger)
        {
            _logger = logger;
        }
    
        [HttpGet]
        public async Task<string> Get()
        {
            _logger.LogInformation("/api/bar called");
    
            var baggage1 = Baggage.GetBaggage("FooBaggage1");
            var baggage2 = Baggage.GetBaggage("FooBaggage2");
            
            _logger.LogInformation($"FooBaggage1: {baggage1}, FooBaggage2: {baggage2}");
    
            return "Hello from Bar";
        }
    }
    

    kibana 中查看数据#

    启动 FooService 和 BarService,然后访问 FooService 的 /api/foo。

    接下来我们就可以在 kibana 中查看数据了。

    如果查看数据时,时区显示有问题,可以在 kibana 的 Management -> Advanced Settings 中修改时区。

    Tracing#

    在 kibana 中,选择 APM,然后选择 Services 或者 Traces 选项卡,就可以看到 FooService 和 BarService 的 Trace 了。

    随意点开一个 Trace,就可以看到这个 Trace 的详细信息了。
    Timeline 中的每一段都是一个 Span,还可以看到我们之前创建的自定义 Span FooActivity。

    点击 Span,可以看到 Span 的详细信息。

    Metrics#

    可以在 kibana 中选择 Metrics Explorer 查看 Metrics 数据。

    详细的使用方式可以参考 elastic 的官方文档:

    https://www.elastic.co/guide/en/observability/current/explore-metrics.html

    Tracing 和 Logs 的关联#

    在 trace 界面,我们点击边上的 Logs 选项卡,就可以看到这个 Trace 所关联的 Logs 了。

    我们也可以在 Discover 中查看所有的 Logs,并根据 log 中的 trace.id 去查询相关的 trace。

    欢迎关注个人技术公众号

  • 相关阅读:
    Linux内核之读写锁机制
    Beyong compare 介绍
    三甲川荧光染料Cy3DIGE NHS ester,Cy3DIGE琥珀酰亚胺活化酯,Cyanine3DIGE 活化酯,Ex:555nmEm:569nm
    Mybatis入门相关API
    IDEA集成Docker实现一键部署
    HashMap遍历之EntrySet————小练习
    【C++】侯捷:C++面向对象-笔记-01
    3D移动 translate3d
    基于javaweb的技能交换分享网站系统-计算机毕业设计
    使用网关和Spring Security进行认证和授权
  • 原文地址:https://www.cnblogs.com/eventhorizon/p/17760641.html