前言

本文将介绍如何在 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的更多相关文章

  1. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  2. ASP.NET Core MVC中构建Web API

    在ASP.NET CORE MVC中,Web API是其中一个功能子集,可以直接使用MVC的特性及路由等功能. 在成功构建 ASP.NET CORE MVC项目之后,选中解决方案,先填加一个API的文 ...

  3. 构建可读性更高的 ASP.NET Core 路由

    原文:构建可读性更高的 ASP.NET Core 路由 一.前言 不知你在平时上网时有没有注意到,绝大多数网站的 URL 地址都是小写的英文字母,而我们使用 .NET/.NET Core MVC 开发 ...

  4. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  5. 006.Adding a controller to a ASP.NET Core MVC app with Visual Studio -- 【在asp.net core mvc 中添加一个控制器】

    Adding a controller to a ASP.NET Core MVC app with Visual Studio 在asp.net core mvc 中添加一个控制器 2017-2-2 ...

  6. 在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射

    一.前言 在实际项目开发过程中,我们使用到的各种 ORM 组件都可以很便捷的将我们获取到的数据绑定到对应的 List<T> 集合中,因为我们最终想要在页面上展示的数据与数据库实体类之间可能 ...

  7. 在 ASP.NET Core 项目中使用 MediatR 实现中介者模式

    一.前言  最近有在看 DDD 的相关资料以及微软的 eShopOnContainers 这个项目中基于 DDD 的架构设计,在 Ordering 这个示例服务中,可以看到各层之间的代码调用与我们之前 ...

  8. 在 ASP.NET Core 项目中使用 npm 管理你的前端组件包

    一.前言 在项目的前端开发中,对于绝大多数的小伙伴来说,当然,也包括我,不可避免的需要在项目中使用到一些第三方的组件包.这时,团队中的小伙伴是选择直接去组件的官网上下载,还是图省事直接在网上搜索,然后 ...

  9. 采用最简单的方式在ASP.NET Core应用中实现认证、登录和注销

    在安全领域,认证和授权是两个重要的主题.认证是安全体系的第一道屏障,是守护整个应用或者服务的第一道大门.当访问者请求进入的时候,认证体系通过验证对方的提供凭证确定其真实身份.认证体系只有在证实了访问者 ...

  10. 如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?

    我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供了扩展点使我们可以与第三方DI框架进行整合.对此比较了解的读 ...

随机推荐

  1. ThinkPHP6.0 链式SQL语句

    ThinkPHP6.0 链式SQL语句 查询单个数据 $user = Db::query('select * from `user`'); $user=Db::table('user')->wh ...

  2. PostgreSQL 12 文档: 部分 IV. 客户端接口

    部分 IV. 客户端接口 这一部分描述和PostgreSQL一起发布的客户端编程接口.这些章中的每一个都能被独立阅读.注意,还有很多用于客户端程序的其他编程接口是被独立发布的并且包含它们自己的文档(附 ...

  3. Python与MySQL如何保持长连接

    Python与MySQL如何保持长连接 介绍 在python后端开发中,时常会与数据库交互,重复的断开.连接 会大大消耗数据库资源. 所以一般都是定义全局变量,来弥补这个缺陷. 但是 Python 与 ...

  4. 体细胞突变检测分析流程-系列1( WES&Panel)

    Sentieon●体细胞变异检测-系列1   Sentieon 致力于解决生物信息数据分析中的速度与准确度瓶颈,通过算法的深度优化和企业级的软件工程,大幅度提升NGS数据处理的效率.准确度和可靠性. ...

  5. 软件设计 day1

    Software Design Methodology 软件设计方法学 中国石油大学(华东)2022-2023-3 国际周课程 Advanced software design 张晓东老师邀请在日本广 ...

  6. Asp-Net-Core学习笔记:gRPC快速入门

    前言 此前,我在做跨语言调用时,用的是 Facebook 的 Thrift,挺轻量的,还不错. Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务.它被当作一个远程过程调用 ...

  7. Flutter系列文章-Flutter基础

    Flutter是Google推出的一种新的移动应用开发框架,允许开发者使用一套代码库同时开发Android和iOS应用.它的设计理念.框架结构.以及对Widget的使用,都让开发者能更有效率地创建高质 ...

  8. 结合ReentrantLock来看AQS的原理

    AQS的定义 ​ 队列同步器 AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 F ...

  9. ubuntu 终端选中黏贴、自带截图

    鼠标选中 -- 复制 鼠标中键 -- 粘贴 注意,在tmux中,这个操作需要加上 Shift 键. PrtSc:截图整个桌面保存到Pictures Ctrl + PrtSc:截图整个桌面到剪贴板 Sh ...

  10. 2021-6-16 TcpIp

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...