经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
使用 OpenTelemetry 构建 .NET 应用可观测性(4):ASP.NET Core 应用中集成 OTel
来源:cnblogs  作者:黑洞视界  时间:2023/10/13 8:41:16  对本文有异议

前言

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

  1. builder.Services.AddOpenTelemetry()
  2. // 这边配置的 Resource 是全局的,Log、Metric、Trace 都会使用这个 Resource
  3. .ConfigureResource(resourceBuilder =>
  4. {
  5. resourceBuilder
  6. .AddService("FooService", "TestNamespace", "1.0.0")
  7. .AddTelemetrySdk();
  8. })
  9. .WithTracing(tracerBuilder =>
  10. {
  11. tracerBuilder
  12. .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
  13. }).WithMetrics(meterBuilder =>
  14. {
  15. meterBuilder
  16. .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
  17. });
  18. builder.Services.AddLogging(loggingBuilder =>
  19. {
  20. loggingBuilder.AddOpenTelemetry(options =>
  21. {
  22. options.IncludeFormattedMessage = true;
  23. options.AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
  24. });
  25. });

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
  1. tracerBuilder
  2. // ASP.NET Core 的 Instrumentation
  3. .AddAspNetCoreInstrumentation(options =>
  4. {
  5. // 配置 Filter,忽略 swagger 的请求
  6. options.Filter =
  7. httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
  8. })
  9. // HTTP 请求的 Instrumentation,如果想要跨进程传输 Baggage,也需要添加此 Instrumentation
  10. .AddHttpClientInstrumentation()
  11. .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
  1. meterBuilder
  2. .AddAspNetCoreInstrumentation()
  3. .AddHttpClientInstrumentation()
  4. .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 发送给可观测性后端。

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

完整的代码演示

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

FooService 的关键代码如下:

  1. builder.Services.AddHttpClient();
  2. builder.Services.AddOpenTelemetry()
  3. // 这边配置的 Resource 是全局的,Log、Metric、Trace 都会使用这个 Resource
  4. .ConfigureResource(resourceBuilder =>
  5. {
  6. resourceBuilder
  7. .AddService("FooService", "TestNamespace", "1.0.0")
  8. .AddTelemetrySdk();
  9. })
  10. .WithTracing(tracerBuilder =>
  11. {
  12. tracerBuilder
  13. .AddAspNetCoreInstrumentation(options =>
  14. {
  15. // 配置 Filter,忽略 swagger 的请求
  16. options.Filter =
  17. httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
  18. })
  19. .AddHttpClientInstrumentation()
  20. .AddSource("FooActivitySource")
  21. .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
  22. }).WithMetrics(meterBuilder =>
  23. {
  24. meterBuilder
  25. .AddAspNetCoreInstrumentation()
  26. .AddHttpClientInstrumentation()
  27. .AddMeter("FooMeter")
  28. .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
  29. });
  30. builder.Services.AddLogging(loggingBuilder =>
  31. {
  32. loggingBuilder.AddOpenTelemetry(options =>
  33. {
  34. options.IncludeFormattedMessage = true;
  35. options.AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:8200"));
  36. });
  37. });
  1. [Route("/api/[controller]")]
  2. public class FooController : ControllerBase
  3. {
  4. private static readonly ActivitySource FooActivitySource
  5. = new ActivitySource("FooActivitySource");
  6. private static readonly Counter<int> FooCounter
  7. = new Meter("FooMeter").CreateCounter<int>("FooCounter");
  8. private readonly IHttpClientFactory _clientFactory;
  9. private readonly ILogger<FooController> _logger;
  10. public FooController(
  11. IHttpClientFactory clientFactory,
  12. ILogger<FooController> logger)
  13. {
  14. _clientFactory = clientFactory;
  15. _logger = logger;
  16. }
  17. [HttpGet]
  18. public async Task<IActionResult> Get()
  19. {
  20. _logger.LogInformation("/api/foo called");
  21. Baggage.SetBaggage("FooBaggage1", "FooValue1");
  22. Baggage.SetBaggage("FooBaggage2", "FooValue2");
  23. var client = _clientFactory.CreateClient();
  24. var result = await client.GetStringAsync("http://localhost:5002/api/bar");
  25. using var activity = FooActivitySource.StartActivity("FooActivity");
  26. activity?.AddTag("FooTag", "FooValue");
  27. activity?.AddEvent(new ActivityEvent("FooEvent"));
  28. await Task.Delay(100);
  29. FooCounter.Add(1);
  30. return Ok(result);
  31. }
  32. }

BarService 的关键代码如下:

  1. builder.Services.AddOpenTelemetry()
  2. .ConfigureResource(resourceBuilder =>
  3. {
  4. resourceBuilder
  5. .AddService("BarService", "TestNamespace", "1.0.0")
  6. .AddTelemetrySdk();
  7. })
  8. .WithTracing(options =>
  9. {
  10. options
  11. .AddAspNetCoreInstrumentation(options =>
  12. {
  13. // 配置 Filter,忽略 swagger 的请求
  14. options.Filter =
  15. httpContent => httpContent.Request.Path.StartsWithSegments("/swagger") == false;
  16. })
  17. .AddHttpClientInstrumentation()
  18. .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
  19. }).WithMetrics(options =>
  20. {
  21. options
  22. .AddAspNetCoreInstrumentation()
  23. .AddHttpClientInstrumentation()
  24. .AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
  25. });
  26. builder.Services.AddLogging(loggingBuilder =>
  27. {
  28. loggingBuilder.AddOpenTelemetry(options =>
  29. {
  30. options.IncludeFormattedMessage = true;
  31. options.AddOtlpExporter(otlpOptions => otlpOptions.Endpoint = new Uri("http://localhost:8200"));
  32. });
  33. });
  1. [Route("/api/[controller]")]
  2. public class BarController : ControllerBase
  3. {
  4. private readonly ILogger<BarController> _logger;
  5. public BarController(ILogger<BarController> logger)
  6. {
  7. _logger = logger;
  8. }
  9. [HttpGet]
  10. public async Task<string> Get()
  11. {
  12. _logger.LogInformation("/api/bar called");
  13. var baggage1 = Baggage.GetBaggage("FooBaggage1");
  14. var baggage2 = Baggage.GetBaggage("FooBaggage2");
  15. _logger.LogInformation($"FooBaggage1: {baggage1}, FooBaggage2: {baggage2}");
  16. return "Hello from Bar";
  17. }
  18. }

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。

欢迎关注个人技术公众号

原文链接:https://www.cnblogs.com/eventhorizon/p/17760641.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号