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

使用,opentelemetry,构建,net,应用,可观,测性,asp,core,集成,otel · 浏览次数 : 13

小编点评

**生成内容时需要带简单的排版** **以下是一些需要带简单的排版的内容:** * **API 代码** * **日志** * **数据** * **图示** * **文档** **排版时需要注意以下几点:** * **使用缩进** * **使用格式化** * **使用排版符号** * **使用空白行** **示例:** **API 代码** ```csharp builder.Services.AddHttpClient(); builder.Services.AddOpenTelemetry(); ``` **日志** ```csharp loggingBuilder.AddOpenTelemetry(options => options.IncludeFormattedMessage = true; options.AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("...") ); ``` **数据** ```csharp // 数据对象 ``` **图示** ```csharp // 图示对象 ``` **文档** ```csharp // 文档对象 ```

正文

前言

本文将介绍如何在 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<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 的关键代码如下:

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<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。

欢迎关注个人技术公众号

与使用 OpenTelemetry 构建 .NET 应用可观测性(4):ASP.NET Core 应用中集成 OTel相似的内容:

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

目录前言使用 elastic 构建可观测性平台在 ASP.NET Core 应用中集成 OTel SDK安装依赖基础配置Instrumentation 配置创建自定义 Span 和 Metric完整的代码演示kibana 中查看数据TracingMetricsTracing 和 Logs 的关联 前

使用 OpenTelemetry 构建 .NET 应用可观测性(1):什么是可观测性

[TOC] # 什么是系统的可观测性(Observability) 对软件行业来说,可观测性(Observability)是一个舶来词,出自控制论(Control Theory)。 **可观测性是系统的一个属性**,它是指系统的状态能否被观测,也就是说,系统的状态能否被监控、收集、分析、查询、可视化

使用 OpenTelemetry 构建 .NET 应用可观测性(2):OpenTelemetry 项目简介

[TOC] # 前世今生 ## OpenTracing OpenTracing 项目启动于 2016 年,旨在提供一套分布式追踪标准,以便开发人员可以更轻松地实现分布式追踪。 OpenTracing 定义了一套 Tracing 模型,以及一套 API,用于在应用程序中创建和管理这些数据模型。 下面是

使用 OpenTelemetry 构建 .NET 应用可观测性(3):.NET SDK 概览

目录前言概览opentelemetry-dotnetopentelemetry-dotnet-contribopentelemetry-dotnet-instrumentationSDK 的基本使用安装依赖ResourcesResourceBuilder.CreateDefault()Resourc

.NET周刊【9月第2期 2023-09-10】

国内文章 使用 OpenTelemetry 构建 .NET 应用可观测性(2):OpenTelemetry 项目简介 https://www.cnblogs.com/eventhorizon/p/17678251.html 目录 前世今生 OpenTracing OpenCensus OpenTel

.NET 使用 OpenTelemetry metrics 监控应用程序指标

上一次我们讲了 OpenTelemetry Logs 与 OpenTelemetry Traces。今天继续来说说 OpenTelemetry Metrics。 随着现代应用程序的复杂性不断增加,对于性能监控和故障排除的需求也日益迫切。在 .NET 生态系统中,OpenTelemetry Metri

.NET 中使用 OpenTelemetry Traces 追踪应用程序

上一次我们讲了 OpenTelemetry Logs。今天继续来说说 OpenTelemetry Traces。 在今天的微服务和云原生环境中,理解和监控系统的行为变得越来越重要。在当下我们实现一个功能可能需要调用了 N 个方法,涉及到 N 个服务。方法之间的调用如蜘蛛网一样。分布式追踪这个时候就至

OpenTelemetry agent 对 Spring Boot 应用的影响:一次 SPI 失效的案例

背景 前段时间公司领导让我排查一个关于在 JDK21 环境中使用 Spring Boot 配合一个 JDK18 新增的一个 SPI(java.net.spi.InetAddressResolverProvider) 不生效的问题。 但这个不生效的前置条件有点多: JDK 的版本得在 18+ Spri

OpenTelemetry agent 对 Spring Boot 应用的影响:一次 SPI 失效的

背景 前段时间公司领导让我排查一个关于在 JDK21 环境中使用 Spring Boot 配合一个 JDK18 新增的一个 SPI(java.net.spi.InetAddressResolverProvider) 不生效的问题。 但这个不生效的前置条件有点多: JDK 的版本得在 18+ Spri

使用Cloudflare Worker加速docker镜像

前言 开发者越来越难了,现在国内的docker镜像也都️了,没有镜像要使用docker太难了,代理又很慢 现在就只剩下自建镜像的办法了 GitHub上有开源项目可以快速搭建自己的镜像库,不过还是有点麻烦,还好Cloudflare暂时还活着‍ 本文记录一下使用 Cloudf