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

欢迎关注个人技术公众号

相关推荐
SRETalk3 个月前
无需推翻既有的建设,这个可观测性产品思路清奇
可观测性·商业监控·商业可观测性产品
SRETalk3 个月前
使用 SpanMetrics Connector 将 OpenTelemetry 跟踪转换为指标
可观测性·opentelemetry
夜莺云原生监控3 个月前
2024 年了,IT 运维监控系统都有哪些推荐?
运维·运维开发·prometheus·可观测性
SRETalk3 个月前
2024 年了,IT 运维监控系统都有哪些推荐?
可观测性·observability·开源监控
观测云5 个月前
Datadog Dash 2024 新功能解析
dash·可观测性
观测云5 个月前
APM Profile 在系统可观测体系中的应用
apm·可观测性·profile
观测云6 个月前
案例研究|为什么 CDG 会选择从 DataDog 迁移至观测云?
可观测性
林木森^~^8 个月前
【微服务篇】深入理解微服务可观测性原理(Log,Metric,Trace)
微服务·云原生·架构·可观测性
夜莺云原生监控8 个月前
Flashcat与出行科技企业一起实践多云可观测
运维·可观测性·flashcat
优维科技EasyOps10 个月前
优维全面可观测产品能力分解①:架构可观测
架构·系统架构·可观测性