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

目录

  • 前言
  • [使用 elastic 构建可观测性平台](#使用 elastic 构建可观测性平台)
  • [在 ASP.NET Core 应用中集成 OTel SDK](#在 ASP.NET Core 应用中集成 OTel SDK)

前言

本文将介绍如何在 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.HostingASP.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 中,我们需要添加以下代码:

csharp 复制代码
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.AspNetCoreASP.NET Core 的 Instrumentation
  • OpenTelemetry.Instrumentation.Http:HTTP 请求的 Instrumentation,如果想要跨进程传输 Baggage,也需要添加此 Instrumentation
csharp 复制代码
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"));
csharp 复制代码
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 发送给可观测性后端。

csharp 复制代码
tracerBuilder
    // 这边注册了 ActivitySource,OTel SDK 会去监听这个 ActivitySource 创建的 Activity
    .AddSource("FooActivitySource")
    .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
csharp 复制代码
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 的关键代码如下:

csharp 复制代码
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"));
    });
});
csharp 复制代码
[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<FooController> _logger;

    public FooController(
        IHttpClientFactory clientFactory,
        ILogger<FooController> logger)
    {
        _clientFactory = clientFactory;
        _logger = logger;
    }
    [HttpGet]
    public async Task<IActionResult> 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 的关键代码如下:

csharp 复制代码
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"));
    });
});
csharp 复制代码
[Route("/api/[controller]")]
public class BarController : ControllerBase
{
    private readonly ILogger<BarController> _logger;

    public BarController(ILogger<BarController> 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。

欢迎关注个人技术公众号

相关推荐
SRETalk4 天前
夜莺监控设计思考(五)告警原理和处理流程深度剖析
可观测性·监控告警·observability·nightingale·开源监控·夜莺监控
SRETalk6 天前
夜莺监控设计思考(四)关于机器那些事儿
开源软件·可观测性·监控告警·observability·nightingale·夜莺监控
SRETalk7 天前
夜莺监控设计思考(三)时序库、agent 的一些设计考量
prometheus·可观测性·监控告警·nightingale·opentelemetry·夜莺监控·categraf
Light602 个月前
领码方案|Linux 下 PLT → PDF 转换服务超级完整版:异步、权限、进度
linux·pdf·可观测性·异步队列·plt转pdf·权限治理·进度查询
SRETalk3 个月前
可观测性体系建设五步心法:明业务、立规范、采数据、显特征、获洞见
可观测性·observability
艾希逐月5 个月前
全栈监控系统架构
系统架构·可观测性
递归尽头是星辰5 个月前
SkyWalking架构深度解析:分布式系统监控的利器
skywalking·分布式链路追踪·可观测性·云原生监控·微服务监控
黑洞视界6 个月前
NCC Mocha v0.2.0 发布, 新增对 Metrics 的支持
c#·.net·可观测性·observability
在未来等你6 个月前
互联网大厂Java求职面试:AI与云原生下的系统设计挑战-3
分布式事务·可观测性·云原生架构·redis缓存·大模型集成·互联网大厂java面试·ai应用开发
西京刀客7 个月前
云原生之开源遥测框架OpenTelemetry(在 Gin 框架中使用 OpenTelemetry 进行分布式追踪和监控)
gin·trace·可观测性·opentelemetry