- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文将介绍 OpenTelemetry .NET SDK 核心组件的设计和使用,主要是为后续给大家介绍如何在 ASP.NET Core 应用程序中使用 OpenTelemetry 做铺垫.
为方便演示,本文使用的 Exporter 都是 Console Exporter,将数据输出到控制台.
我们在 OpenTelemetry 的 GitHub 仓库中搜索 dotnet,可以看到有三个仓库: https://github.com/open-telemetry?q=dotnet&type=all&language=&sort= 。
OTel SDK 的核心库,主要包括以下几个部分:
第三方贡献的 Instrumentation 和 Exporter,比如 InfluxDB、Elasticsearch、AWS 等 。
无侵入的 Instrumentation,用于在不修改代码的情况下,自动收集数据.
本文只介绍 OTel SDK 的基本使用,下面将创建一个 Console 应用程序,演示如何使用 OTel SDK.
创建一个 .NET Core Console 应用程序,然后安装下列依赖:
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
本文测试使用的是 1.6.0 版本,后期 OTel SDK 的版本可能会有所变化.
Resource 是 OTel 中的一个重要概念,用于标识应用程序的一些元数据,比如应用程序的名称、版本、运行环境等。 Resource 的信息会被添加到 Log、Span、Metric 等数据中,用于后续的查询和分析.
Resource 由 ResourceBuilder 构建,ResourceBuilder 有两个方法:
ResourceBuilder.CreateDefault():创建一个默认的 Resource,包含以下Attribute:
Environment.SetEnvironmentVariable("OTEL_SERVICE_NAME", "FooService");
// 可以直接在 OTEL_RESOURCE_ATTRIBUTES 中指定 service.name, 这样就不需要再指定 OTEL_SERVICE_NAME 了
Environment.SetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES", "service.version=1.0.0,service.namespace=TestNamespace");
Resource resource = ResourceBuilder
.CreateDefault()
.Build();
foreach (var attribute in resource.Attributes)
{
Console.WriteLine($"{attribute.Key}={attribute.Value}");
}
输出:
service.name=FooService
service.version=1.0.0
service.namespace=TestNamespace
telemetry.sdk.name=opentelemetry
telemetry.sdk.language=dotnet
telemetry.sdk.version=1.6.0
ResourceBuilder.CreateEmpty():创建一个空的 Resource,可以按需求添加Attribute.
Environment.SetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES", "test.attribute=foo");
Resource resource = ResourceBuilder
.CreateDefault()
.AddService("FooService", "TestNamespace", "1.0.0")
.AddTelemetrySdk()
.AddEnvironmentVariableDetector() // 可以识别 OTEL_RESOURCE_ATTRIBUTES 环境变量
.Build();
foreach (var attribute in resource.Attributes)
{
Console.WriteLine($"{attribute.Key} = {attribute.Value}");
}
输出:
test.attribute = foo
telemetry.sdk.name = opentelemetry
telemetry.sdk.language = dotnet
telemetry.sdk.version = 1.6.0
service.name = FooService
service.namespace = TestNamespace
service.version = 1.0.0
service.instance.id = 15ff37f1-5791-4afe-b130-cb947b895af3
有别于其他语言的 SDK,.NET SDK 的 Tracing 模块是通过 ActivitySource 实现的.
ActivitySource 的 API 和 OpenTelemetry 的 API 基本是一一对应的.
通过 ActivitySource.StartActivity() 创建的 Activity 对应 OTel 中的 Span,可以被 OTel SDK 的 Tracing 模块收集.
Activity 是 NET 以前就有的类,OTel 标准出来后,.NET 对 Activity 做了一些扩展,使其可以和 OTel 中的 Span 一一对应.
System.Diagnostics.ActivitySource 是 .NET Runtime 的一部分,如果编写的代码仅仅是一个收集数据的组件,可以直接使用 System.Diagnostics.ActivitySource ,不需要引入 OpenTelemetry 的依赖.
ActivitySource 本质是 System.Diagnostics 命名空间里一个发布/订阅模式的工具.
ActivitySource.AddActivityListener(new ActivityListener
{
// 只监听 TestSource1
ShouldListenTo = source => source.Name == "TestSource1",
// 采样率为 100%
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllDataAndRecorded,
// 监听 Activity 的开始和结束
ActivityStarted = activity =>
{
Console.WriteLine($"Activity started: {activity.OperationName}");
},
ActivityStopped = activity =>
{
Console.WriteLine($"Activity stopped: {activity.OperationName}");
}
});
using var activitySource1 = new ActivitySource("TestSource1");
using var activitySource2 = new ActivitySource("TestSource2");
using var activity1 = activitySource1.StartActivity("Activity1");
Console.WriteLine($"Activity1 created: {activity1 != null}");
// 如果设置 Listener,ActivitySource 将不会创建 Activity,StartActivity 返回 null
activity1?.SetTag("foo", 1);
using var activity2 = activitySource2.StartActivity("Activity2");
Console.WriteLine($"Activity2 created: {activity2 != null}");
activity2?.SetTag("bar", "Hello, World!");
输出:
Activity started: Activity1
Activity1 created: True
Activity2 created: False
Activity stopped: Activity1
ActivitySource 可以通过 Name 来关联 ActivityListener ,只有 ActivityListener 的 ShouldListenTo 返回 true 的 ActivitySource 才会被监听.
在上面的例子中,我们通过 ActivitySource.StartActivity() 创建了两个 Activity,但是只有一个 Activity 被监听到,这是因为我们设置了 ShouldListenTo,只监听 TestSource1.
如果没有设置 ActivityListener,ActivitySource.StartActivity() 将返回 null.
所以推荐使用 ActivitySource.StartActivity() 创建的 Activity 时,使用?.操作符来避免空指针异常.
而 OpenTelemetry SDK 的 Tracing 模块,其实就是一个 ActivityListener 的实现.
在使用 OTel 的 Tracing 模块时,我们需要通过 TracerProvider.AddSource() 告诉 OTel SDK 实现的 ActivityListener 需要监听哪些 ActivitySource.
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
// 创建 Span 是通过 ActivitySource.StartActivity() 实现的,
// 所以这边的 tracerProvider 不会被使用
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource("TestSource1")
.AddSource("TestSource2")
.AddConsoleExporter()
.Build();
using var activitySource1 = new ActivitySource("TestSource1");
using var activitySource2 = new ActivitySource("TestSource2");
using (var activity1 = activitySource1.StartActivity("Activity1"))
{
activity1?.SetTag("foo", 1);
activity1?.SetTag("bar", "Hello, World!");
using (var activity2 = activitySource2.StartActivity("Activity2"))
{
activity2?.SetTag("foo", 2);
activity2?.SetTag("bar", "Hello, OpenTelemetry!");
Debug.Assert(activity2?.ParentId == activity1?.Id);
}
}
输出:
Activity.TraceId: 7497970c0c05341cadbbbd2b87b4246b
Activity.SpanId: ce96499cd0c115fd
Activity.TraceFlags: Recorded
Activity.ParentSpanId: 1cfead09b114a264
Activity.ActivitySourceName: TestSource2
Activity.DisplayName: Activity2
Activity.Kind: Internal
Activity.StartTime: 2023-09-25T13:05:36.0415480Z
Activity.Duration: 00:00:00.0000240
Activity.Tags:
foo: 2
bar: Hello, OpenTelemetry!
Resource associated with Activity:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: 012ed685-54a3-4ec0-879b-aff9afcbd59c
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.6.0
Activity.TraceId: 7497970c0c05341cadbbbd2b87b4246b
Activity.SpanId: 1cfead09b114a264
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: TestSource1
Activity.DisplayName: Activity1
Activity.Kind: Internal
Activity.StartTime: 2023-09-25T13:05:36.0413000Z
Activity.Duration: 00:00:00.0110830
Activity.Tags:
foo: 1
bar: Hello, World!
Resource associated with Activity:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: 012ed685-54a3-4ec0-879b-aff9afcbd59c
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.6.0
两个 Activity 都有相同的 TraceId,表示它们属于同一个 Trace.
Activity1 在 Activity2 的外层作用域中创建,所以 Activity1 是 Activity2 的 Parent,Activity2 的 ParentId 等于 Activity1 的 Id.
Metrics 模块的使用和 Tracing 模块类似,通过 MeterProvider 来创建 Meter ,然后通过 Meter 创建 Counter 、 Gauge 、 Measure 等.
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("Meter1")
.SetResourceBuilder(resourceBuilder)
.AddConsoleExporter()
.Build();
var meter = new Meter(name: "Meter1", version: "1.0.0");
var counter = meter.CreateCounter<long>("counter");
counter.Add(100);
输出:
Resource associated with Metric:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: 8b4fd315-6a8f-4198-ab1a-a4d11a14a431
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.6.0
Export counter, Meter: Meter1/1.0.0
(2023-09-24T13:18:45.2247000Z, 2023-09-24T13:18:45.2277870Z] LongSum
Value: 100
OTel 定义了以下几种 Metric 类型:
下面是各个类型在 Meter 中对应的创建方法:
CreateCounter
CreateObservableCounter
CreateUpDownCounter
CreateObservableUpDownCounter
CreateHistogram
CreateObservableGauge
详细的介绍可以参考这几篇文章:
我们知道,.NET Core 有自己的 Logging 模块,可以通过 LoggerFactory 创建 ILogger ,然后通过 ILogger 记录日志.
OTel SDK 的 Logging 模块,是 ILoggerProvider 的一个实现,将其注册到 LoggerFactory 中,就可以通过 ILogger 收集日志.
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
using var loggerFactory = LoggerFactory.Create(
builder => builder.AddOpenTelemetry(
options =>
{
options.AddConsoleExporter();
options.SetResourceBuilder(resourceBuilder);
}));
var logger = loggerFactory.CreateLogger("MyLogger");
logger.LogInformation("Hello World!");
输出:
LogRecord.Timestamp: 2023-09-25T13:09:19.2702090Z
LogRecord.CategoryName: MyLogger
LogRecord.Severity: Info
LogRecord.SeverityText: Information
LogRecord.Body: Hello World!
LogRecord.Attributes (Key:Value):
OriginalFormat (a.k.a Body): Hello World!
Resource associated with LogRecord:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: 7f14c6d0-7d8b-490a-b4dc-bfb2275da108
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.6.0
在上面的例子中,我们单独使用了 Tracing、Metrics、Logging 模块,这三者的数据是相互独立的,没有关联.
我们把上面的例子放在一起看下 。
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource("TestSource1")
.AddSource("TestSource2")
.AddConsoleExporter()
.Build();
using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddMeter("Meter1")
.AddConsoleExporter()
.Build();
using var loggerFactory = LoggerFactory.Create(
builder => builder.AddOpenTelemetry(
options =>
{
options.AddConsoleExporter();
options.SetResourceBuilder(resourceBuilder);
}));
using var activitySource1 = new ActivitySource("TestSource1");
using var activitySource2 = new ActivitySource("TestSource2");
var logger = loggerFactory.CreateLogger("MyLogger");
var meter = new Meter("Meter1", "1.0.0");
var counter = meter.CreateCounter<long>("MyCounter");
using (var activity1 = activitySource1.StartActivity("Activity1"))
{
logger.LogInformation("Hello, Activity1!");
using (var activity2 = activitySource2.StartActivity("Activity2"))
{
logger.LogInformation("Hello, Activity2!");
counter.Add(100);
}
}
下面是输出内容的整理:
Resource associated with Metric:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: 9f8306cb-c4a6-42f9-8d5b-897ba7f5df72
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.6.0
Export MyCounter, Meter: Meter1/1.0.0
(2023-09-25T13:38:33.6109280Z, 2023-09-25T13:38:33.6342240Z] LongSum
Value: 100
下期将介绍如何在 ASP.NET Core 应用程序中使用 OpenTelemetry,并使用 Elastic APM 来收集数据.
最后此篇关于使用OpenTelemetry构建.NET应用可观测性(3):.NETSDK概览的文章就讲到这里了,如果你想了解更多关于使用OpenTelemetry构建.NET应用可观测性(3):.NETSDK概览的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
作者:@warm3snow https://github.com/warm3snow 微信公众号:密码应用技术实战 博客园首页:https://www.cnblogs.com/informati
我收到一条消息说: "To use xxx , the CSS on your browser must be enabled" 我按照在线说明阅读: Open up DevTools ( Comma
有谁知道 SSMS 插件的工作方式类似于 Sublime Text 上的“MiniMap”? IE。提供查询编辑器内容的缩小版本,如 this link 之后的版本: ( Click to enlar
希望我能在这里找到帮助。我试图让这个导航栏响应。我从 w3schools tut 开始,但我丢失了我的 css 文件的概述,并且无法找到为什么这不起作用的问题。感谢您的帮助! 实时页面:www.air
我有一个类似于电子表格的概述,其中有数百个单元格,每个单元格都是一个链接,可打开一个带有表单的模式来调整单元格的内容(以及一些附加数据,因此内联编辑并不可取)。 我对 JS 不是很熟悉,但是页面(带有
任何人都可以阐明 Glimpse 与 Dapper 的结合使用,还是仅适用于 Entity Framework ? 编辑 public List GetUserRoles(string userNa
我有一个很大的 html 页面,例如 4000x6000 像素的图像和文本。我想在一个小的 div 中包含此页面的 map 概览之类的内容。我可以用来导航的整个页面的缩放版本。有谁知道一些 js 脚本
我需要路线图 View 、总体错误图、一个屏幕上的多条信息 - 这可以与 Bugzilla 一起使用吗?基于 Eclipse 的插件等是可用的...但是像 yoxel 这样需要访问 Bugzilla
我需要弹出 View 。我遵循了这个说明。 初始化:在storyboard中创建两个UIViewController。 假设 FirstViewController 是正常的,SecondViewCo
我是一名优秀的程序员,十分优秀!